<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: David Bond</title>
    <description>The latest articles on DEV Community by David Bond (@davidsbond).</description>
    <link>https://dev.to/davidsbond</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F91645%2F4f2dc4e9-6f2c-44e7-86d6-b9cc6547d31e.jpg</url>
      <title>DEV Community: David Bond</title>
      <link>https://dev.to/davidsbond</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/davidsbond"/>
    <language>en</language>
    <item>
      <title>Go: Creating Dynamic Kubernetes Informers</title>
      <dc:creator>David Bond</dc:creator>
      <pubDate>Wed, 04 Aug 2021 00:11:28 +0000</pubDate>
      <link>https://dev.to/davidsbond/go-creating-dynamic-kubernetes-informers-1npi</link>
      <guid>https://dev.to/davidsbond/go-creating-dynamic-kubernetes-informers-1npi</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Recently, I published v1.0.0 of &lt;a href="https://github.com/davidsbond/kollect"&gt;Kollect&lt;/a&gt;, a dynamic Kubernetes informer that&lt;br&gt;
publishes changes in cluster resources to a configurable event bus. At the heart of this project is a dynamic informer,&lt;br&gt;
a method of handling add/update/delete notifications of arbitrary cluster resources (including those added as a &lt;code&gt;CustomResourceDefinition&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;This kind of tooling is quite powerful, as you can perform operations on arbitrary resources without knowing their structure.&lt;br&gt;
This is especially useful in situations where you cannot import the canonical types for those resources from public&lt;br&gt;
repositories, or they're too large and complex to write your own types without a lot of time and effort.&lt;/p&gt;

&lt;p&gt;For example, using Kollect (or your own informer), you can track changes to your resources in real time, or check that&lt;br&gt;
resources follow best practices as they change, using a tool like &lt;a href="https://www.openpolicyagent.org/"&gt;Open Policy Agent&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In this post, I'll attempt to get you started writing a dynamic Kubernetes informer that will allow you to perform operations&lt;br&gt;
when any resources of your choosing change.&lt;/p&gt;
&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;Every go project starts with initialising a new &lt;a href="https://blog.golang.org/using-go-modules"&gt;Go module&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go mod init github.com/myname/myinformer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And a &lt;code&gt;main.go&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Cluster Authentication
&lt;/h2&gt;

&lt;p&gt;In order to start handling notifications, we're going to need to authenticate with the cluster that we're running in or&lt;br&gt;
against. This means we have two separate methods of authentication:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Kubeconfig - Pointing directly to a kubeconfig file that our application accesses on startup. This would be used typically
when your program is not running in the cluster it is watching.&lt;/li&gt;
&lt;li&gt;In-cluster - Obtaining permissions based on the &lt;a href="https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/"&gt;ServiceAccount&lt;/a&gt; 
associated with the &lt;a href="https://kubernetes.io/docs/concepts/workloads/pods/"&gt;Pod&lt;/a&gt; that our program is running in within the cluster we want to watch.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We're going to use the &lt;code&gt;k8s.io/client-go&lt;/code&gt; package, so you'll need to run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go get k8s.io/client-go
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we've downloaded the dependency, let's update our &lt;code&gt;main.go&lt;/code&gt; to create a Kubernetes API cluster config based on where &lt;br&gt;
our application is running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"log"&lt;/span&gt;
    &lt;span class="s"&gt;"os"&lt;/span&gt;

    &lt;span class="s"&gt;"k8s.io/client-go/dynamic"&lt;/span&gt;
    &lt;span class="s"&gt;"k8s.io/client-go/rest"&lt;/span&gt;
    &lt;span class="s"&gt;"k8s.io/client-go/tools/clientcmd"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;kubeConfig&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"KUBECONFIG"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;clusterConfig&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;rest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Config&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;kubeConfig&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;clusterConfig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;clientcmd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BuildConfigFromFlags&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kubeConfig&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;clusterConfig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InClusterConfig&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatalln&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;clusterClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;dynamic&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewForConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clusterConfig&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatalln&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code above checks for the presence of the &lt;code&gt;KUBECONFIG&lt;/code&gt; environment variable, if it is present, we create our cluster&lt;br&gt;
configuration using the &lt;code&gt;clientcmd&lt;/code&gt; package. Otherwise, we use the &lt;code&gt;rest&lt;/code&gt; package to assume credentials from the &lt;code&gt;Pod&lt;/code&gt; &lt;br&gt;
we're running in. Then, we create a new dynamic client.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;dynamic&lt;/code&gt; package, allows us to query cluster resources as &lt;code&gt;unstructured.Unstructured&lt;/code&gt; types. These are basically &lt;br&gt;
wrappers around &lt;code&gt;map[string]interface{}&lt;/code&gt; that have helper methods for obtaining Kubernetes resource specifics such as the&lt;br&gt;
API version, group, kind, labels, annotations etc.&lt;/p&gt;
&lt;h2&gt;
  
  
  Monitoring Resources
&lt;/h2&gt;

&lt;p&gt;Now that we've authenticated against the cluster, we can start monitoring resources. We'll do this with the &lt;code&gt;dynamicinformer&lt;/code&gt;&lt;br&gt;
package. We're also going to need to decide which resources we want to watch and create an informer for each one. In this&lt;br&gt;
example, we'll create a single informer that watches &lt;code&gt;Deployment&lt;/code&gt; resources, but you can easily extend it to watch&lt;br&gt;
multiple resources.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"log"&lt;/span&gt;
    &lt;span class="s"&gt;"os"&lt;/span&gt;
    &lt;span class="s"&gt;"time"&lt;/span&gt;

    &lt;span class="s"&gt;"k8s.io/client-go/dynamic"&lt;/span&gt;
    &lt;span class="s"&gt;"k8s.io/client-go/rest"&lt;/span&gt;
    &lt;span class="s"&gt;"k8s.io/client-go/tools/clientcmd"&lt;/span&gt;
    &lt;span class="s"&gt;"k8s.io/client-go/dynamic/dynamicinformer"&lt;/span&gt;
    &lt;span class="s"&gt;"k8s.io/apimachinery/pkg/runtime/schema"&lt;/span&gt;
    &lt;span class="n"&gt;corev1&lt;/span&gt; &lt;span class="s"&gt;"k8s.io/api/core/v1"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;kubeConfig&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"KUBECONFIG"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;clusterConfig&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;rest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Config&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;kubeConfig&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;clusterConfig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;clientcmd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BuildConfigFromFlags&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kubeConfig&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;clusterConfig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InClusterConfig&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatalln&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;clusterClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;dynamic&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewForConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clusterConfig&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatalln&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;resource&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;schema&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GroupVersionResource&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Group&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="s"&gt;"apps"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Version&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="s"&gt;"v1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Resource&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"deployments"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;factory&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;dynamicinformer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewFilteredDynamicSharedInformerFactory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clusterClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Minute&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;corev1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NamespaceAll&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;informer&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;factory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ForResource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Informer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that when we call &lt;code&gt;NewFilteredDynamicSharedInformerFactory&lt;/code&gt;, we pass in &lt;code&gt;corev1.NamespaceAll&lt;/code&gt; as the namespace to&lt;br&gt;
watch resources in. This causes the informer to watch over all namespaces within the cluster. You can modify this to only&lt;br&gt;
a specific namespace, or filter by namespace in the handler methods.&lt;/p&gt;

&lt;p&gt;Now that we've created a new informer that will watch for changes in &lt;code&gt;Deployment&lt;/code&gt; resources, we need to register handler&lt;br&gt;
functions for add, update and delete events. This is done via the &lt;code&gt;informer.AddEventHandler&lt;/code&gt; method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"log"&lt;/span&gt;
    &lt;span class="s"&gt;"os"&lt;/span&gt;
    &lt;span class="s"&gt;"time"&lt;/span&gt;
    &lt;span class="s"&gt;"os/signal"&lt;/span&gt;
    &lt;span class="s"&gt;"context"&lt;/span&gt;

    &lt;span class="s"&gt;"k8s.io/client-go/dynamic"&lt;/span&gt;
    &lt;span class="s"&gt;"k8s.io/client-go/rest"&lt;/span&gt;
    &lt;span class="s"&gt;"k8s.io/client-go/tools/clientcmd"&lt;/span&gt;
    &lt;span class="s"&gt;"k8s.io/client-go/dynamic/dynamicinformer"&lt;/span&gt;
    &lt;span class="s"&gt;"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"&lt;/span&gt;
    &lt;span class="s"&gt;"k8s.io/apimachinery/pkg/runtime/schema"&lt;/span&gt;
    &lt;span class="n"&gt;corev1&lt;/span&gt; &lt;span class="s"&gt;"k8s.io/api/core/v1"&lt;/span&gt;
    &lt;span class="s"&gt;"k8s.io/client-go/tools/cache"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;kubeConfig&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"KUBECONFIG"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;clusterConfig&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;rest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Config&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;kubeConfig&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;clusterConfig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;clientcmd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BuildConfigFromFlags&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kubeConfig&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;clusterConfig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InClusterConfig&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatalln&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;clusterClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;dynamic&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewForConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clusterConfig&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatalln&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;resource&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;schema&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GroupVersionResource&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Group&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="s"&gt;"apps"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Version&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="s"&gt;"v1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Resource&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"deployments"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;factory&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;dynamicinformer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewFilteredDynamicSharedInformerFactory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clusterClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Minute&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;corev1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NamespaceAll&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;informer&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;factory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ForResource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Informer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;informer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddEventHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResourceEventHandlerFuncs&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;AddFunc&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;unstructured&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unstructured&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;UpdateFunc&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;oldObj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;newObj&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
        &lt;span class="n"&gt;DeleteFunc&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cancel&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NotifyContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Background&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Interrupt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;cancel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;informer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Done&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that for &lt;code&gt;AddFunc&lt;/code&gt;, &lt;code&gt;UpdateFunc&lt;/code&gt; and &lt;code&gt;DeleteFunc&lt;/code&gt; that the parameters are passed as &lt;code&gt;interface{}&lt;/code&gt;, because we're&lt;br&gt;
using the &lt;code&gt;dynamicinformer&lt;/code&gt; package, we can assume these are instances of &lt;code&gt;*unstructured.Unstructured&lt;/code&gt; and safely cast them.&lt;/p&gt;

&lt;p&gt;We're also creating a &lt;code&gt;context.Context&lt;/code&gt; that is cancelled on an &lt;code&gt;os.Interrupt&lt;/code&gt; signal. This allows us to prevent the application&lt;br&gt;
from exiting until it receives an interrupt signal. Its &lt;code&gt;Done&lt;/code&gt; channel is passed to &lt;code&gt;informer.Run&lt;/code&gt;, to keep the informer&lt;br&gt;
alive until execution is cancelled.&lt;/p&gt;

&lt;p&gt;From here, your handling logic is your own, do what you want when resources are added, updated or changed. Further sections&lt;br&gt;
in this post will cover additional considerations regarding cache syncing and using RBAC to give your &lt;code&gt;Pod&lt;/code&gt; access to&lt;br&gt;
the Kubernetes API.&lt;/p&gt;
&lt;h2&gt;
  
  
  Cache Syncing
&lt;/h2&gt;

&lt;p&gt;When an informer starts, it will build a cache of all resources it currently watches which is lost when the application&lt;br&gt;
restarts. This means that on startup, each of your handler functions will be invoked as the initial state is built. If this&lt;br&gt;
is not desirable for your use case, you can wait until the caches are synced before performing any updates using the&lt;br&gt;
&lt;code&gt;cache.WaitForCacheSync&lt;/code&gt; function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"log"&lt;/span&gt;
    &lt;span class="s"&gt;"os"&lt;/span&gt;
    &lt;span class="s"&gt;"sync"&lt;/span&gt;
    &lt;span class="s"&gt;"time"&lt;/span&gt;
    &lt;span class="s"&gt;"os/signal"&lt;/span&gt;
    &lt;span class="s"&gt;"context"&lt;/span&gt;

    &lt;span class="s"&gt;"k8s.io/client-go/dynamic"&lt;/span&gt;
    &lt;span class="s"&gt;"k8s.io/client-go/rest"&lt;/span&gt;
    &lt;span class="s"&gt;"k8s.io/client-go/tools/clientcmd"&lt;/span&gt;
    &lt;span class="s"&gt;"k8s.io/client-go/dynamic/dynamicinformer"&lt;/span&gt;
    &lt;span class="s"&gt;"k8s.io/apimachinery/pkg/runtime/schema"&lt;/span&gt;
    &lt;span class="n"&gt;corev1&lt;/span&gt; &lt;span class="s"&gt;"k8s.io/api/core/v1"&lt;/span&gt;
    &lt;span class="s"&gt;"k8s.io/client-go/tools/cache"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;kubeConfig&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"KUBECONFIG"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;clusterConfig&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;rest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Config&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;kubeConfig&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;clusterConfig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;clientcmd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BuildConfigFromFlags&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kubeConfig&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;clusterConfig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InClusterConfig&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatalln&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;clusterClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;dynamic&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewForConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clusterConfig&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatalln&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;resource&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;schema&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GroupVersionResource&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Group&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"apps"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Version&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"v1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Resource&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"deployments"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;factory&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;dynamicinformer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewFilteredDynamicSharedInformerFactory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clusterClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Minute&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;corev1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NamespaceAll&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;informer&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;factory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ForResource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Informer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;mux&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;sync&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RWMutex&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="n"&gt;synced&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
    &lt;span class="n"&gt;informer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddEventHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResourceEventHandlerFuncs&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;AddFunc&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;mux&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RLock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;mux&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RUnlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;synced&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="c"&gt;// Handler logic&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;UpdateFunc&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;oldObj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;newObj&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;mux&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RLock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;mux&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RUnlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;synced&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="c"&gt;// Handler logic&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;DeleteFunc&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;mux&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RLock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;mux&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RUnlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;synced&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="c"&gt;// Handler logic&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cancel&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NotifyContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Background&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Interrupt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;cancel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="n"&gt;informer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Done&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

    &lt;span class="n"&gt;isSynced&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WaitForCacheSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Done&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;informer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HasSynced&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;mux&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Lock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;synced&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;isSynced&lt;/span&gt;
    &lt;span class="n"&gt;mux&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;isSynced&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"failed to sync"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Done&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the code above, we use a boolean &lt;code&gt;synced&lt;/code&gt; to indicate that the caches are finished syncing and that our handler functions&lt;br&gt;
are only being invoked once the initial state of the watched resources has been built. We've had to make some modifications,&lt;br&gt;
like starting the informer asynchronously using a &lt;code&gt;go&lt;/code&gt; statement, as the caches will not start building until &lt;code&gt;informer.Run&lt;/code&gt;&lt;br&gt;
is called.&lt;/p&gt;

&lt;p&gt;It may seem unintuitive at first, but we also don't directly assign the return value of &lt;code&gt;WaitForCacheSync&lt;/code&gt; to the &lt;code&gt;synced&lt;/code&gt;&lt;br&gt;
variable within a mutex lock. This is because the handler functions are being invoked while the cache is syncing and will&lt;br&gt;
effectively be queued. If we lock that mutex initially, the updates that occurred while the cache was syncing will still trigger&lt;br&gt;
our handler functions. This means we need to only reassign &lt;code&gt;synced&lt;/code&gt; once we're sure the cache sync is complete.&lt;/p&gt;
&lt;h2&gt;
  
  
  RBAC
&lt;/h2&gt;

&lt;p&gt;Finally, when running within a cluster, we're going to need to use RBAC to provide the &lt;code&gt;ServiceAccount&lt;/code&gt; the appropriate&lt;br&gt;
permissions to monitor resources of our choosing. This is done using the &lt;code&gt;Role&lt;/code&gt;/&lt;code&gt;RoleBinding&lt;/code&gt; resources (if you're handling&lt;br&gt;
things at the namespace level) or the &lt;code&gt;ClusteRole&lt;/code&gt;/&lt;code&gt;ClusterRoleBinding&lt;/code&gt; resources (if you're handling things at the cluster&lt;br&gt;
level). You can view full documentation for these resources &lt;a href="https://kubernetes.io/docs/reference/access-authn-authz/rbac/"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's create a &lt;code&gt;ServiceAccount&lt;/code&gt;, &lt;code&gt;ClusterRole&lt;/code&gt; and &lt;code&gt;ClusterRoleBinding&lt;/code&gt; to match our code above. It will allow us to watch&lt;br&gt;
all &lt;code&gt;Deployment&lt;/code&gt; resources in all namespaces:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ServiceAccount&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;myinformer&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mynamespace&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rbac.authorization.k8s.io/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ClusterRole&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deployment-informer&lt;/span&gt;
&lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;apiGroups&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;apps/v1"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;deployments"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;verbs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;get"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;watch"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;list"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rbac.authorization.k8s.io/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ClusterRoleBinding&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read-secrets-global&lt;/span&gt;
&lt;span class="na"&gt;subjects&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ServiceAccount&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;myinformer&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mynamespace&lt;/span&gt;
&lt;span class="na"&gt;roleRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ClusterRole&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deployment-informer&lt;/span&gt;
  &lt;span class="na"&gt;apiGroup&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rbac.authorization.k8s.io&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you deploy your application within the cluster, use the &lt;code&gt;serviceAccountName&lt;/code&gt; field in the pod specification to&lt;br&gt;
the &lt;code&gt;myinformer&lt;/code&gt; one created above. This will provide the &lt;code&gt;Pod&lt;/code&gt; with access to the Kubernetes API, specifically to perform&lt;br&gt;
&lt;code&gt;get&lt;/code&gt;, &lt;code&gt;list&lt;/code&gt; and &lt;code&gt;watch&lt;/code&gt; request on &lt;code&gt;Deployment&lt;/code&gt; resources.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;Hopefully this post has given you enough insight into the world of Kubernetes informers to implement your own. As said&lt;br&gt;
at the start, I used code like this to implement &lt;a href="https://github.com/davidsbond/kollect"&gt;Kollect&lt;/a&gt;, and it works as well&lt;br&gt;
as you would expect.&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/davidsbond/kollect"&gt;https://github.com/davidsbond/kollect&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.openpolicyagent.org"&gt;https://www.openpolicyagent.org&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.golang.org/using-go-modules"&gt;https://blog.golang.org/using-go-modules&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account"&gt;https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kubernetes.io/docs/concepts/workloads/pods"&gt;https://kubernetes.io/docs/concepts/workloads/pods&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kubernetes.io/docs/reference/access-authn-authz/rbac/"&gt;https://kubernetes.io/docs/reference/access-authn-authz/rbac&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>go</category>
      <category>kubernetes</category>
      <category>informer</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Homelab: Accessing my k3s cluster securely from anywhere with Tailscale</title>
      <dc:creator>David Bond</dc:creator>
      <pubDate>Wed, 30 Dec 2020 00:00:00 +0000</pubDate>
      <link>https://dev.to/davidsbond/networking-accessing-my-k3s-cluster-securely-from-anywhere-with-tailscale-3k5n</link>
      <guid>https://dev.to/davidsbond/networking-accessing-my-k3s-cluster-securely-from-anywhere-with-tailscale-3k5n</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;At home, I run my own &lt;a href="https://github.com/davidsbond/homelab" rel="noopener noreferrer"&gt;k3s cluster&lt;/a&gt; on 4 Raspberry Pi 4Bs. In order to access the&lt;br&gt;
services I run from anywhere without exposing my cluster to the open internet I use &lt;a href="https://tailscale.com/" rel="noopener noreferrer"&gt;Tailscale&lt;/a&gt;, &lt;br&gt;
a service designed to make a private VPN really easy to set up. I run a bunch of services, including (but not &lt;br&gt;
limited to) a password manager, Google Photos alternative, finance management tools etc.&lt;/p&gt;

&lt;p&gt;This post aims to describe how my cluster is set up to use Tailscale, allowing me to resolve DNS via Cloudflare restricting&lt;br&gt;
access solely to me (or anyone I share my Tailscale machines with). This allows me to go to &lt;code&gt;https://bitwarden.homelab.dsb.dev&lt;/code&gt;&lt;br&gt;
on any device I have connected to my Tailscale network and access my own password manager instance.&lt;/p&gt;
&lt;h2&gt;
  
  
  Cluster Setup
&lt;/h2&gt;

&lt;p&gt;My &lt;a href="https://k3s.io" rel="noopener noreferrer"&gt;k3s&lt;/a&gt; cluster consists of four nodes. Each one is a &lt;a href="https://www.raspberrypi.org/products/raspberry-pi-4-model-b/" rel="noopener noreferrer"&gt;Raspberry Pi 4B+&lt;/a&gt;, &lt;br&gt;
the 8GB model. I've been pleasantly surprised with how much you can run on these small machines, every year they seem to &lt;br&gt;
pack more and more power into a credit card's worth of space. The &lt;a href="https://github.com/davidsbond/homelab" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;&lt;br&gt;
has a full overview of the setup you can view for yourself.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.dsb.dev%2Fimages%2F2020-12-30-accessing-my-k3s-cluster-from-anywhere-with-tailscale%2F1.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fblog.dsb.dev%2Fimages%2F2020-12-30-accessing-my-k3s-cluster-from-anywhere-with-tailscale%2F1.jpg" alt="Physical Cluster"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Installing Tailscale
&lt;/h3&gt;

&lt;p&gt;Each node in the cluster is running &lt;a href="https://ubuntu.com/raspberry-pi" rel="noopener noreferrer"&gt;Ubuntu for Raspberry Pi&lt;/a&gt;, so installing Tailscale is&lt;br&gt;
as simple as following the &lt;a href="https://tailscale.com/kb/1039/install-ubuntu-2004" rel="noopener noreferrer"&gt;instructions for ubuntu&lt;/a&gt;.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Add Tailscale’s package signing key and repository
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://pkgs.Tailscale.com/stable/ubuntu/focal.gpg | &lt;span class="nb"&gt;sudo &lt;/span&gt;apt-key add -
curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://pkgs.Tailscale.com/stable/ubuntu/focal.list | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; /etc/apt/sources.list.d/Tailscale.list
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Personally, I use the &lt;code&gt;unstable&lt;/code&gt; repository instead, because I like to be bleeding edge. It's worth adding that I keep &lt;br&gt;
regular backups of everything on my cluster, just in case my bleeding-edge tendencies end up with me breaking my cluster&lt;br&gt;
or losing access to things I need.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Install Tailscale
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get update
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get &lt;span class="nb"&gt;install &lt;/span&gt;Tailscale
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ol&gt;
&lt;li&gt;Authenticate and connect your machine to your Tailscale network
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;Tailscale up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;I did this for each node, you could use a terminal multiplexer (like &lt;a href="https://github.com/tmux/tmux" rel="noopener noreferrer"&gt;tmux&lt;/a&gt;) to speed things&lt;br&gt;
up a bit.&lt;/p&gt;
&lt;h3&gt;
  
  
  Installing K3S
&lt;/h3&gt;

&lt;p&gt;Next, we need to install k3s on each node to get a cluster running. The &lt;a href="https://rancher.com/docs/k3s/latest/en/quick-start/" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;&lt;br&gt;
is the authoritative source for this, but I'm also going to outline the quickstart steps here.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Install the k3s server for the control plane node. Including setting the node's advertise address as the Tailscale IP,
rather than the local network IP, this is done via the &lt;code&gt;--bind-address&lt;/code&gt; flag. This is optional, but saves you having to 
set up a static IP address for your machine. It also means the cluster nodes will communicate via the Tailscale network.
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-sfL&lt;/span&gt; https://get.k3s.io | sh &lt;span class="nt"&gt;-s&lt;/span&gt; - &lt;span class="nt"&gt;--bind-address&lt;/span&gt; &amp;lt;TAILSCALE_IP&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Rancher have provided a simple script to get things up and running quickly. I'd advise you take a look at it first rather&lt;br&gt;
than just running some script off the internet. Once complete, grab the token required for your agent nodes to join the&lt;br&gt;
cluster. This is stored at &lt;code&gt;/var/lib/rancher/k3s/server/node-token&lt;/code&gt;.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Install the k3s agent on all the other nodes&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here, we set up each agent node in the cluster. Once again, rancher have provided a simple script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-sfL&lt;/span&gt; https://get.k3s.io | &lt;span class="nv"&gt;K3S_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://&amp;lt;SERVER_TAILSCALE_IP&amp;gt;:6443 &lt;span class="nv"&gt;K3S_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;NODE_TOKEN&amp;gt; sh -
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;SERVER_TAILSCALE_IP&lt;/code&gt; is the Tailscale IP address of your control-plane node. &lt;code&gt;NODE_TOKEN&lt;/code&gt; is the token mentioned in the&lt;br&gt;
previous step. After following these steps, you've now got a k3s cluster running, where all nodes communicate via &lt;br&gt;
Tailscale. Go you!&lt;/p&gt;
&lt;h3&gt;
  
  
  Ingresses
&lt;/h3&gt;

&lt;p&gt;By default, k3s comes with &lt;a href="https://traefik.io/" rel="noopener noreferrer"&gt;Traefik&lt;/a&gt; already deployed. Because I'm using a &lt;code&gt;.dev&lt;/code&gt; domain, I also&lt;br&gt;
needed to ensure everything I serve on my domain was using &lt;code&gt;https&lt;/code&gt;. To do this, I've added &lt;a href="https://cert-manager.io/" rel="noopener noreferrer"&gt;cert-manager&lt;/a&gt;&lt;br&gt;
to my cluster. Cert-manager allows me to generate TLS certificates for my ingresses automatically via &lt;a href="https://letsencrypt.org/" rel="noopener noreferrer"&gt;letsencrypt&lt;/a&gt;.&lt;br&gt;
All I have to do is add additional annotations to my &lt;code&gt;Ingress&lt;/code&gt; resources.&lt;/p&gt;

&lt;p&gt;If you also want to use cert-manager, it's easiest for you to follow &lt;a href="https://cert-manager.io/docs/installation/kubernetes/" rel="noopener noreferrer"&gt;their instructions&lt;/a&gt;, &lt;br&gt;
as explaining it all here would be out of scope for this blog post, since you may not even care about using HTTPS at all.&lt;br&gt;
In brief, my cert-manager deployment authenticates with cloudflare using an API key with limited permissions using a &lt;br&gt;
DNS-01 challenge. You can read more about DNS-01 challenges &lt;a href="https://letsencrypt.org/docs/challenge-types/#dns-01-challenge" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;br&gt;
You can also see my cert-manager deployment &lt;a href="https://github.com/davidsbond/homelab/tree/master/manifests/cert-manager" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here's my &lt;code&gt;Ingress&lt;/code&gt; resource for my Bitwarden deployment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;networking.k8s.io/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Ingress&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bitwarden&lt;/span&gt;
  &lt;span class="na"&gt;annotations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;kubernetes.io/ingress.class&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;traefik&lt;/span&gt;
    &lt;span class="na"&gt;traefik.ingress.kubernetes.io/router.tls&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true"&lt;/span&gt;
    &lt;span class="na"&gt;traefik.ingress.kubernetes.io/router.entrypoints&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https&lt;/span&gt;
    &lt;span class="na"&gt;cert-manager.io/cluster-issuer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cloudflare&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;tls&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;hosts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;bitwarden.homelab.dsb.dev&lt;/span&gt;
    &lt;span class="na"&gt;secretName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bitwarden-tls&lt;/span&gt;
  &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bitwarden.homelab.dsb.dev&lt;/span&gt;
    &lt;span class="na"&gt;http&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;backend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bitwarden&lt;/span&gt;
            &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;number&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
        &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/&lt;/span&gt;
        &lt;span class="na"&gt;pathType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Prefix&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here I'm telling Traefik that any inbound requests for &lt;code&gt;bitwarden.homelab.dsb.dev&lt;/code&gt; should route to a &lt;code&gt;Service&lt;/code&gt; resource&lt;br&gt;
named &lt;code&gt;bitwarden&lt;/code&gt;, and that TLS certificates should be stored in a secret named &lt;code&gt;bitwarden-tls&lt;/code&gt;, issued via cert-manager.&lt;/p&gt;
&lt;h2&gt;
  
  
  Cloudflare DNS
&lt;/h2&gt;

&lt;p&gt;The last step is to set up appropriate DNS records to route requests to the cluster when connected to the Tailscale&lt;br&gt;
network. For my use-case, I want any subdomain of &lt;code&gt;homelab.dsb.dev&lt;/code&gt; to go straight to the cluster. This way, I don't&lt;br&gt;
need DNS records for each individual application I want to expose.&lt;/p&gt;

&lt;p&gt;I manage these records using &lt;a href="https://www.terraform.io/" rel="noopener noreferrer"&gt;Terraform&lt;/a&gt;, and the setup is fairly straightforward. To start&lt;br&gt;
with, I needed to set up the cloudflare provider:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"cloudflare"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;email&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cloudflare_email&lt;/span&gt;
  &lt;span class="nx"&gt;api_key&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cloudflare_api_key&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All you need is to provide the email address you use for your cloudflare account, and the API key. Next, you need to&lt;br&gt;
be able to grab the zone identifier for your domain. Since I have a single domain &lt;code&gt;dsb.dev&lt;/code&gt;, I just created a simple&lt;br&gt;
data source that returns all my cloudflare zones:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;data&lt;/span&gt; &lt;span class="s2"&gt;"cloudflare_zones"&lt;/span&gt; &lt;span class="s2"&gt;"dsb_dev"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;filter&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you have multiple domains, you're going to want to modify that filter to return the one you care about. You can see&lt;br&gt;
the documentation for that &lt;a href="https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/data-sources/zones#filter" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Lastly, I needed to create a DNS record for each node in the cluster, using its Tailscale IP address. The name of each&lt;br&gt;
record is &lt;code&gt;*.homelab&lt;/code&gt;, which specifies that any requests to a subdomain of &lt;code&gt;homelab.dsb.dev&lt;/code&gt; gets sent straight to the&lt;br&gt;
cluster, providing you have access to the Tailscale network:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"cloudflare_record"&lt;/span&gt; &lt;span class="s2"&gt;"homelab_0"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;zone_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;lookup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cloudflare_zones&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dsb_dev&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;zones&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s2"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"*.homelab"&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;homelab_0_ip&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"A"&lt;/span&gt;
  &lt;span class="nx"&gt;ttl&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3600&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"cloudflare_record"&lt;/span&gt; &lt;span class="s2"&gt;"homelab_1"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;zone_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;lookup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cloudflare_zones&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dsb_dev&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;zones&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s2"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"*.homelab"&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;homelab_1_ip&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"A"&lt;/span&gt;
  &lt;span class="nx"&gt;ttl&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3600&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"cloudflare_record"&lt;/span&gt; &lt;span class="s2"&gt;"homelab_2"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;zone_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;lookup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cloudflare_zones&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dsb_dev&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;zones&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s2"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"*.homelab"&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;homelab_2_ip&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"A"&lt;/span&gt;
  &lt;span class="nx"&gt;ttl&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3600&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"cloudflare_record"&lt;/span&gt; &lt;span class="s2"&gt;"homelab_3"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;zone_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;lookup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cloudflare_zones&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dsb_dev&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;zones&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s2"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"*.homelab"&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;homelab_3_ip&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"A"&lt;/span&gt;
  &lt;span class="nx"&gt;ttl&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3600&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can view the full terraform configuration &lt;a href="https://github.com/davidsbond/homelab/tree/master/terraform/cloudflare" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I run Traefik as a &lt;code&gt;DaemonSet&lt;/code&gt; in my cluster, meaning whichever node receives the request can route it to the appropriate&lt;br&gt;
service regardless of the node its running on. This allows me to do some basic load balancing. The main caveat here, is for&lt;br&gt;
each new node I add, I also need a new DNS record, but since this is my homelab, I'm not planning on increasing the node size&lt;br&gt;
to a larger size where I'd need to automate this.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;The above setup allows me to access all the applications I have running on my home k3s cluster from anywhere providing&lt;br&gt;
I have a connection to the Tailscale network. This works great for me, especially since Tailscale also has an android&lt;br&gt;
app, which allows me to access my password manager and other applications on my phone, all without exposing my cluster &lt;br&gt;
to the public internet!&lt;/p&gt;

&lt;p&gt;Combining it with cert-manager also gives me the ability to secure everything with HTTPS and use a FQDN on a domain&lt;br&gt;
that I own.&lt;/p&gt;

&lt;h3&gt;
  
  
  Links
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/davidsbond/homelab" rel="noopener noreferrer"&gt;https://github.com/davidsbond/homelab&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tailscale.com/" rel="noopener noreferrer"&gt;https://tailscale.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://k3s.io" rel="noopener noreferrer"&gt;https://k3s.io&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.raspberrypi.org/products/raspberry-pi-4-model-b/" rel="noopener noreferrer"&gt;https://www.raspberrypi.org/products/raspberry-pi-4-model-b/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ubuntu.com/raspberry-pi" rel="noopener noreferrer"&gt;https://ubuntu.com/raspberry-pi&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tailscale.com/kb/1039/install-ubuntu-2004" rel="noopener noreferrer"&gt;https://tailscale.com/kb/1039/install-ubuntu-2004&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/tmux/tmux" rel="noopener noreferrer"&gt;https://github.com/tmux/tmux&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://rancher.com/docs/k3s/latest/en/quick-start/" rel="noopener noreferrer"&gt;https://rancher.com/docs/k3s/latest/en/quick-start/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://traefik.io/" rel="noopener noreferrer"&gt;https://traefik.io/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cert-manager.io/" rel="noopener noreferrer"&gt;https://cert-manager.io/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://letsencrypt.org/" rel="noopener noreferrer"&gt;https://letsencrypt.org/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cert-manager.io/docs/installation/kubernetes/" rel="noopener noreferrer"&gt;https://cert-manager.io/docs/installation/kubernetes/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://letsencrypt.org/docs/challenge-types/#dns-01-challenge" rel="noopener noreferrer"&gt;https://letsencrypt.org/docs/challenge-types/#dns-01-challenge&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/davidsbond/homelab/tree/master/manifests/cert-manager" rel="noopener noreferrer"&gt;https://github.com/davidsbond/homelab/tree/master/manifests/cert-manager&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.terraform.io/" rel="noopener noreferrer"&gt;https://www.terraform.io/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/data-sources/zones#filter" rel="noopener noreferrer"&gt;https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/data-sources/zones#filter&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/davidsbond/homelab/tree/master/terraform/cloudflare" rel="noopener noreferrer"&gt;https://github.com/davidsbond/homelab/tree/master/terraform/cloudflare&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>k3s</category>
      <category>tailscale</category>
      <category>cloudflare</category>
      <category>certmanager</category>
    </item>
    <item>
      <title>Go: Structuring repositories with protocol buffers</title>
      <dc:creator>David Bond</dc:creator>
      <pubDate>Sun, 01 Mar 2020 00:00:00 +0000</pubDate>
      <link>https://dev.to/davidsbond/golang-structuring-repositories-with-protocol-buffers-3012</link>
      <guid>https://dev.to/davidsbond/golang-structuring-repositories-with-protocol-buffers-3012</guid>
      <description>&lt;h3&gt;
  
  
  Introduction
&lt;/h3&gt;

&lt;p&gt;In my current position at &lt;a href="https://www.utilitywarehouse.co.uk/"&gt;Utility Warehouse&lt;/a&gt;, my team keeps our go code for all our services within a monorepo. This includes all our protocol buffer definitions that are used to generate client/service code to allow our services to interact.&lt;/p&gt;

&lt;p&gt;This post aims to outline our way of organising our protocol buffer code and how we perform code generation to ensure allservices are up-to-date with the latest contracts when things change. This posts expects you to already be familiar withprotocol buffers.&lt;/p&gt;

&lt;p&gt;You could also use the structure explained in this post to create a single repository that contains all proto definitions for all services (or whatever else you use them for) and serve it as a &lt;a href="https://blog.golang.org/using-go-modules"&gt;go module&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  What are protocol buffers?
&lt;/h3&gt;

&lt;p&gt;Taken from &lt;a href="https://developers.google.com/protocol-buffers"&gt;Google’s documentation&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Protocol buffers are Google’s language-neutral, platform-neutral, extensible mechanism for serializing structured data – think XML, but smaller, faster, and simpler. You define how you want your data to be structured once, then you can use special generated source code to easily write and read your structured data to and from a variety of data streams and using a variety of languages.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;On top of using them for service-to-service communication, we also use them as our serialization format forthe event-sourced aspects of our systems, where proto messages are sent over the wire via &lt;a href="https://kafka.apache.org/"&gt;Apache Kafka&lt;/a&gt; and &lt;a href="https://nats.io/"&gt;NATS&lt;/a&gt;. Which also allows systems that consume/produceevents to always have the most up-to-date definitions.&lt;/p&gt;

&lt;h3&gt;
  
  
  The ‘proto’ directory
&lt;/h3&gt;

&lt;p&gt;At the top level of our repository lives the &lt;code&gt;proto&lt;/code&gt; directory. This is where all &lt;code&gt;.proto&lt;/code&gt; files live, as well as third-party definitions (such as those &lt;a href="https://github.com/googleapis/googleapis/tree/master/google/type"&gt;provided by google&lt;/a&gt; or from other teams within the business).&lt;/p&gt;

&lt;p&gt;Our team is the partner platform, so our specific proto definitions are found in a subdirectory named &lt;code&gt;partner&lt;/code&gt;. Below this are the different domains we deal with. Subdirectories here include aspects such as &lt;code&gt;identity&lt;/code&gt;, or &lt;code&gt;document&lt;/code&gt; for services that deal with authentication or the management of individual partner’s documents.&lt;/p&gt;

&lt;p&gt;Below here are either versioned or service directories. Let’s say we have a gRPC API that serves documents for a partner,the proto definitions will are found under &lt;code&gt;partner/document/service/v*&lt;/code&gt; (where &lt;code&gt;*&lt;/code&gt; is the major version number for the service). Alternatively, if we have domain objects we want to share across multiple proto packages, we keep those under &lt;code&gt;partner/document/v*&lt;/code&gt;. Using versioned directories like this allows us to version our proto packages easily and have the package names reflect the location of those files within the repository.&lt;/p&gt;

&lt;p&gt;Here’s a full example of what this looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.
└── proto
    ├── partner
    │   └── document
    │   ├── service
    │   │   └── v1
    | | └── service.proto # gRPC service definitions, DTOs etc
    │   └── v1
    |   └── models.proto # Shared domain objects

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Writing protocol buffer definitions
&lt;/h3&gt;

&lt;p&gt;Next, lets take a look at how we actually define our protocol buffers. There’s nothing particularly out of the ordinary here that you wouldn’t see in most other definitions. The most important part is the &lt;code&gt;package&lt;/code&gt; declaration. We make sureour package names reflect the relative location of the protocol buffer files. In the example above, the packages are named&lt;code&gt;partner.document.service.v1&lt;/code&gt; and &lt;code&gt;partner.document.v1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here’s an example of what the top of our &lt;code&gt;.proto&lt;/code&gt; files look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;syntax = "proto3";

// Additional imports go here

package partner.document.service.v1;

option go_package = "github.com/utilitywarehouse/&amp;lt;repo&amp;gt;/proto/gen/go/partner/document/service/v1;document";

// Service &amp;amp; message definitions go here

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We’re also using the &lt;a href="https://buf.build/"&gt;buf&lt;/a&gt; tool in order to lint our files and check for breaking changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Generating code from protocol buffers
&lt;/h3&gt;

&lt;p&gt;Finally, we need to generate our code so we can use it in our go services. We commit and keep all our generated source code within the repository along with the definitions. This means that when code is regenerated, all services that depend on that generated code are updated at once.&lt;/p&gt;

&lt;p&gt;To achieve our code generation, we use a bash script that finds all directories containing at least one &lt;code&gt;.proto&lt;/code&gt; fileand runs the &lt;code&gt;protoc&lt;/code&gt; command. This will output our generated code in directories relative to the respective &lt;code&gt;.proto&lt;/code&gt; files within a &lt;code&gt;proto/gen/go&lt;/code&gt; subdirectory. If we wanted to extend this to other languages (Java, TypeScript etc), these would be kept underneath &lt;code&gt;proto/gen/&amp;lt;language_name&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The script lives at &lt;code&gt;proto/generate.sh&lt;/code&gt;, the important part looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!/usr/bin/env bash

# Get current directory.
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &amp;gt;/dev/null 2&amp;gt;&amp;amp;1 &amp;amp;&amp;amp; pwd )"

# Find all directories containing at least one prototfile.
# Based on: https://buf.build/docs/migration-prototool#prototool-generate.
for dir in $(find ${DIR}/partner -name '*.proto' -print0 | xargs -0 -n1 dirname | sort | uniq); do
  files=$(find "${dir}" -name '*.proto')

  # Generate all files with protoc-gen-go.
  protoc -I ${DIR} --go_out=plugins=grpc,paths=source_relative:${DIR}/gen/go ${files}
done

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We also have some extra utilities, such as running additional generators when certain &lt;code&gt;import&lt;/code&gt; directives are used withinthe proto definitions. For example, if &lt;a href="https://github.com/mwitkow/go-proto-validators"&gt;go-proto-validators&lt;/a&gt; are used within a definition. We will also generate code using &lt;code&gt;--govalidators_out&lt;/code&gt;. Rinse and repeat for some additional tooling and some internal ones.&lt;/p&gt;

&lt;h3&gt;
  
  
  Generated package names
&lt;/h3&gt;

&lt;p&gt;If you’re anal like myself, you may not like the go package names you get as a result of this. In the example above, you end up with a package name of &lt;code&gt;partner_document_v1&lt;/code&gt;, which isn’t pretty to look at unless you alias it when importing it.&lt;/p&gt;

&lt;p&gt;To solve this, you can specify &lt;code&gt;option go_package&lt;/code&gt; in order to override the generated package name. This is purely optional, but it allows us to have package names like &lt;code&gt;document&lt;/code&gt; instead. You can read more about this option&lt;a href="https://developers.google.com/protocol-buffers/docs/reference/go-generated"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Links
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.utilitywarehouse.co.uk/"&gt;https://www.utilitywarehouse.co.uk/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.golang.org/using-go-modules"&gt;https://blog.golang.org/using-go-modules&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/protocol-buffers"&gt;https://developers.google.com/protocol-buffers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kafka.apache.org/"&gt;https://kafka.apache.org/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nats.io/"&gt;https://nats.io/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://buf.build/"&gt;https://buf.build/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/googleapis/googleapis/tree/master/google/type"&gt;https://github.com/googleapis/googleapis/tree/master/google/type&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/mwitkow/go-proto-validators"&gt;https://github.com/mwitkow/go-proto-validators&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/protocol-buffers/docs/reference/go-generated"&gt;https://developers.google.com/protocol-buffers/docs/reference/go-generated&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>go</category>
      <category>protobuf</category>
      <category>structure</category>
      <category>grpc</category>
    </item>
    <item>
      <title>Go: Creating gRPC interceptors</title>
      <dc:creator>David Bond</dc:creator>
      <pubDate>Fri, 14 Jun 2019 00:00:00 +0000</pubDate>
      <link>https://dev.to/davidsbond/golang-creating-grpc-interceptors-5el5</link>
      <guid>https://dev.to/davidsbond/golang-creating-grpc-interceptors-5el5</guid>
      <description>&lt;h3&gt;
  
  
  Introduction
&lt;/h3&gt;

&lt;p&gt;Just like when building HTTP APIs, sometimes you need middleware that applies to your HTTP handlers for things like request validation, authentication etc. In &lt;a href="https://grpc.io/"&gt;gRPC&lt;/a&gt; this is no different. Methods for authentication need to be applied to both servers and clients in an ‘all or none’ fashion. For the uninitiated, gRPC describes itself as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A modern open source high performance RPC framework that can run in any environment. It can efficiently connect services in and across data centers with pluggable support for load balancing, tracing, health checking and authentication. It is also applicable in last mile of distributed computing to connect devices, mobile applications and browsers to backend services.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The key difference here is that in HTTP we create middleware for handlers (purely on the server side). With gRPC we can create middleware for both inbound calls on the server side and outbound calls on the client side. This post aims to outline how you can create simple gRPC interceptors that act as middleware for your clients and servers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Interceptor Types
&lt;/h3&gt;

&lt;p&gt;In gRPC there are two kinds of interceptors, &lt;strong&gt;unary&lt;/strong&gt; and &lt;strong&gt;stream&lt;/strong&gt;. Unary interceptors handle single request/response RPC calls whereas stream interceptors handle RPC calls where streams of messages are written in either direction. You can get more in-depth details on the differences between them &lt;a href="https://grpc.io/docs/guides/concepts/#rpc-life-cycle"&gt;here&lt;/a&gt;. On top of this, you can create interceptors that apply to both servers and clients.&lt;/p&gt;

&lt;h4&gt;
  
  
  Unary Client Interceptors
&lt;/h4&gt;

&lt;p&gt;In situations where we have a simple call &amp;amp; response, we need to create a unary client interceptor. This is a function that matches the signature of &lt;code&gt;grpc.UnaryClientInterceptor&lt;/code&gt; and looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func Interceptor(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
  // Do some things and invoke `invoker` to finish the request
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This signature has a lot of parameters, so lets look at each one and what they’re for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ctx context.Context&lt;/code&gt; - This is the request context and will be used primarily for timeouts. It can also be used to add/read request metadata.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;method string&lt;/code&gt; - The name of the RPC method being called.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;req interface{}&lt;/code&gt; - The request instance, this is an &lt;code&gt;interface{}&lt;/code&gt; as reflection is used for the marshalling&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;reply interface{}&lt;/code&gt; - The response instance, works the same way as the &lt;code&gt;req&lt;/code&gt; parameter&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cc *grpc.ClientConn&lt;/code&gt; - The underlying client connection to the server.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;invoker grpc.UnaryInvoker&lt;/code&gt; - The RPC invocation method. Similarly to &lt;a href="https://gist.github.com/gbbr/935f26e50080ae99eedc822d8c273a89#file-middleware_funcs-go"&gt;HTTP middleware&lt;/a&gt; where you call &lt;code&gt;ServeHTTP&lt;/code&gt;, this needs to be invoked for the RPC call to be made.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;opts ...grpc.CallOption&lt;/code&gt; - The &lt;code&gt;grpc.CallOption&lt;/code&gt; instances used to configure the gRPC call.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With all of these, we get a lot of information about the call being made. This makes it quite straightforward to create things like &lt;a href="https://github.com/grpc-ecosystem/go-grpc-middleware/blob/master/logging/logrus/client_interceptors.go"&gt;logging middleware&lt;/a&gt; that will write out RPC call information.&lt;/p&gt;

&lt;h4&gt;
  
  
  Unary Server Interceptors
&lt;/h4&gt;

&lt;p&gt;Server interceptors look fairly similar to the client, with the exception that they allow us to modify the response returned from the gRPC call. Here’s the function signature, it’s defined as &lt;code&gt;grpc.UnaryServerInterceptor&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func Interceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
    // Invoke 'handler' to use your gRPC server implementation and get
    // the response.
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Like with the client, there’s a few different params here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ctx context.Context&lt;/code&gt; - This is the request context and will be used primarily for timeouts. It can also be used to add/read request metadata.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;req interface{}&lt;/code&gt; - The inbound request&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;info *grpc.UnaryServerInfo&lt;/code&gt; - Information on the gRPC server that is handling the request&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;handler grpc.UnaryHandler&lt;/code&gt; - The handler for the inbound request, you’ll need to invoke this otherwise you won’t be getting your response to the client.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Stream Client Interceptors
&lt;/h4&gt;

&lt;p&gt;Working with streams works pretty much the same, here’s the signature of &lt;code&gt;grpc.StreamClientInterceptor&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func Interceptor(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) {
    // Call 'streamer' to write messages to the stream before this function returns
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ctx context.Context&lt;/code&gt; - This is the request context and will be used primarily for timeouts. It can also be used to add/read request metadata.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;desc *grpc.StreamDesc&lt;/code&gt; - Represents a streaming RPC service’s method specification.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cc *grpc.ClientConn&lt;/code&gt; - The underlying client connection to the server.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;method string&lt;/code&gt; - The name of the gRPC method being called.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;streamer grpc.Streamer&lt;/code&gt; - Called by the interceptor to create a stream.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Stream Server Interceptors
&lt;/h4&gt;

&lt;p&gt;Below is the signature of &lt;code&gt;grpc.StreamServerInterceptor&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func Interceptor(srv interface{}, stream grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
    // Call 'handler' to invoke the stream handler before this function returns
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;srv interface{}&lt;/code&gt; - The server implementation&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;stream grpc.ServerStream&lt;/code&gt; - Defines the server-side behavior of a streaming RPC.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;info *grpc.StreamServerInfo&lt;/code&gt; - Various information about the streaming RPC on server side&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;handler grpc.StreamHandler&lt;/code&gt; - The handler called by gRPC server to complete the execution of a streaming RPC&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Creating an interceptor
&lt;/h3&gt;

&lt;p&gt;For this post, lets say we have a gRPC client and server that authenticate via a JWT token that we obtain via an HTTP API. If the provided JWT token is no longer valid, the server will return an appropriate status code that will be detected by the interceptor, triggering a call to the HTTP API to refresh the token. We’re going to use a unary client interceptor to achieve this, but the code can be easily ported for client streams and servers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; There are plenty of open-source implementations for token-based authentication on gRPC, the code in this post is just to serve as an example. Ideally, you’ll want something stronger than just a username and password combo. You can check out lots of different interceptor implementations in the &lt;a href="https://github.com/grpc-ecosystem/go-grpc-middleware"&gt;grpc-ecosystem/go-grpc-middleware&lt;/a&gt; repository&lt;/p&gt;

&lt;p&gt;To start, we’ll need a type to store our JWT token and authentication details, we’re going to use basic auth to obtain the token.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;type (
    JWTInterceptor struct {
        http *http.Client // The HTTP client for calling the token-serving API
        token string // The JWT token that will be used in every call to the server
        username string // The username for basic authentication
        password string // The password for basic authentication
        endpoint string // The HTTP endpoint to hit to obtain tokens
    }
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we’ll need our unary client interceptor that will add the JWT token to the request metadata for each outbound call, we’re following the &lt;a href="https://oauth.net/2/bearer-tokens/"&gt;bearer token&lt;/a&gt; approach:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func (jwt *JWTInterceptor) UnaryClientInterceptor(ctx context.Context, method string, req interface{}, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
    // Add the current bearer token to the metadata and call the RPC
    // command
    ctx = metadata.AppendToOutgoingContext(ctx, "authorization", "bearer "+t.token)
    return invoker(ctx, method, req, reply, cc, opts...)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above will work for as long as the JWT token is valid. If the token has an expiry, we will eventually no longer be able to make calls to the server. So we need a method that can call the HTTP API that serves us tokens. The API accepts a JSON body and returns the token in the response body, we’ll also need some types to represent those&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;type(
    authResponse struct {
        Token string `json:"token"`
    }

    authRequest struct {
        Username string `json:"username"`
        Password string `json:"password"`
    }
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here are the functions for obtaining new JWT tokens. The API called will give back a 200 response with a JSON encoded body containing the token. It returns errors using &lt;code&gt;http.Error&lt;/code&gt; so those are just string responses. Once we have the token, we set it on the &lt;code&gt;JWT&lt;/code&gt; struct for later use.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func (jwt *JWTInterceptor) refreshBearerToken() error {
    resp, err := jwt.performAuthRequest()

    if err != nil {
        return err
    }

    var respBody authResponse
    if err = json.NewDecoder(resp.Body).Decode(&amp;amp;respBody); err != nil {
        return err
    }

    jwt.token = respBody.Token

    return resp.Body.Close()
}

func (jwt *JWTInterceptor) performAuthRequest() (*http.Response, error) {
    body := authRequest{
        Username: jwt.username,
        Password: jwt.password,
    }

    data, err := json.Marshal(body)

    if err != nil {
        return nil, err
    }

    buff := bytes.NewBuffer(data)
    resp, err := jwt.http.Post(jwt.endpoint, "application/json", buff)

    if err != nil {
        return resp, err
    }

    if resp.StatusCode != http.StatusOK {
        out := make([]byte, resp.ContentLength)
        if _, err = resp.Body.Read(out); err != nil {
            return resp, err
        }

        return resp, fmt.Errorf("unexpected authentication response: %s", string(out))
    }

    return resp, nil
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With these defined, we can update our interceptor logic like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func (jwt *JWTInterceptor) UnaryClientInterceptor(ctx context.Context, method string, req interface{}, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
    // Create a new context with the token and make the first request
    authCtx := metadata.AppendToOutgoingContext(ctx, "authorization", "bearer "+jwt.token)
    err := invoker(authCtx, method, req, reply, cc, opts...)

    // If we got an unauthenticated response from the gRPC service, refresh the token
    if status.Code(err) == codes.Unauthenticated {
        if err = jwt.refreshBearerToken(); err != nil {
            return err
        }

        // Create a new context with the new token. We don't want to reuse 'authCtx' here
        // because we've already appended the invalid token. We're appending metadata to
        // a slice here rather than a map like HTTP headers, so the first one will be picked
        // up and invalid.
        updatedAuthCtx := metadata.AppendToOutgoingContext(ctx, "authorization", "bearer "+jwt.token)
        err = invoker(updatedAuthCtx, method, req, reply, cc, opts...)
    }

    return err
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Testing an interceptor
&lt;/h3&gt;

&lt;p&gt;Now that we’ve written the interceptor, we need some tests. It can be a little tricky asserting values within a context when your packages don’t define the keys that are used. Luckily the &lt;code&gt;google.golang.org/grpc/metadata&lt;/code&gt; contains methods we can use to get the information we need and assert that it is what we expect. We’re going to implement our own version of the &lt;code&gt;invoker&lt;/code&gt; method that will assert the existence of the JWT token in the metadata. We can then just call the &lt;code&gt;JWTInterceptor.UnaryClientInterceptor&lt;/code&gt; method directly in our test, without connecting to or mocking a gRPC service.&lt;/p&gt;

&lt;p&gt;I normally write using &lt;a href="https://github.com/golang/go/wiki/TableDrivenTests"&gt;table driven tests&lt;/a&gt;, but for the sake of brevity I’ll just go through the steps you can take to pull the token out from the context and check its value.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In your custom invoker function, pull the outgoing metadata using &lt;code&gt;metadata.FromOutgoingContext(ctx)&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Convert your outbound context into an inbound one using &lt;code&gt;metadata.NewIncomingContext(ctx, md)&lt;/code&gt; with the metadata from above.&lt;/li&gt;
&lt;li&gt;Extract the JWT token using &lt;code&gt;github.com/grpc-ecosystem/go-grpc-middleware/auth&lt;/code&gt; and the &lt;code&gt;AuthFromMD&lt;/code&gt; method.&lt;/li&gt;
&lt;li&gt;If the token isn’t what you expect or is blank, return &lt;code&gt;codes.Unauthenticated&lt;/code&gt; using the &lt;code&gt;google.golang.org/grpc/codes&lt;/code&gt; package.&lt;/li&gt;
&lt;li&gt;Use a HTTP mock to catch the request for a token and handle it. (Either using the standard library or an HTTP mocking package like &lt;a href="https://github.com/h2non/gock"&gt;gock&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Using an interceptor
&lt;/h3&gt;

&lt;p&gt;With our interceptor written, we can apply it using the &lt;code&gt;grpc.With...&lt;/code&gt; methods like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Create a new interceptor
jwt := &amp;amp;JWTInterceptor{
    // Set up all the members here
}

conn, err := grpc.Dial("localhost:5000", grpc.WithUnaryInterceptor(jwt.UnaryClientInterceptor))

// Perform the rest of your client setup
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works the same for servers as well. When you create your server you’ll have the option on providing unary/stream server interceptors.&lt;/p&gt;

&lt;h3&gt;
  
  
  Links
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://grpc.io/"&gt;https://grpc.io/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://grpc.io/docs/guides/concepts/#rpc-life-cycle"&gt;https://grpc.io/docs/guides/concepts/#rpc-life-cycle&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/grpc-ecosystem/go-grpc-middleware/blob/master/logging/logrus/client_interceptors.go"&gt;https://github.com/grpc-ecosystem/go-grpc-middleware/blob/master/logging/logrus/client_interceptors.go&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://gist.github.com/gbbr/935f26e50080ae99eedc822d8c273a89#file-middleware_funcs-go"&gt;https://gist.github.com/gbbr/935f26e50080ae99eedc822d8c273a89#file-middleware_funcs-go&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://oauth.net/2/bearer-tokens/"&gt;https://oauth.net/2/bearer-tokens/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/grpc-ecosystem/go-grpc-middleware"&gt;https://github.com/grpc-ecosystem/go-grpc-middleware&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/golang/go/wiki/TableDrivenTests"&gt;https://github.com/golang/go/wiki/TableDrivenTests&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/h2non/gock"&gt;https://github.com/h2non/gock&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>go</category>
      <category>grpc</category>
      <category>middleware</category>
      <category>interceptors</category>
    </item>
    <item>
      <title>Go: Creating distributed systems using memberlist</title>
      <dc:creator>David Bond</dc:creator>
      <pubDate>Sun, 14 Apr 2019 00:00:00 +0000</pubDate>
      <link>https://dev.to/davidsbond/golang-creating-distributed-systems-using-memberlist-2fa9</link>
      <guid>https://dev.to/davidsbond/golang-creating-distributed-systems-using-memberlist-2fa9</guid>
      <description>&lt;h3&gt;
  
  
  Introduction
&lt;/h3&gt;

&lt;p&gt;As scaling requirements have increased steadily throughout enterprise software the need to create distributed systems has increased. Leading to a variety of incredibly scalable products that rely on a distributed architecture. Wikipedia describes a distributed system as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A system whose components are located on different networked computers, which communicate and coordinate their actions by passing messages to one another.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Examples of these systems range from data stores to event buses and so on. There are many applications for distributed systems. Because there are so many applications, there are also many off-the-shelf implementations of these distributed communications protocols that allow us to easily build self-discovering, distributed systems. This post aims to go into detail on the &lt;a href="https://github.com/hashicorp/memberlist"&gt;memberlist&lt;/a&gt; package and demonstrate how you can start building a distributed system using it.&lt;/p&gt;

&lt;p&gt;I’ve currently used the library to create &lt;a href="https://github.com/davidsbond/sse-cluster"&gt;sse-cluster&lt;/a&gt;, a scalable &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events"&gt;Server Sent Events&lt;/a&gt; broker. It utilises memberlist in order to discover new nodes and propagate events to clients spread across different nodes. It was born from a need to scale an existing SSE implementation. It’s a half decent reference for using the package. I have yet to delve much into the fine-tuning aspect of the configuration.&lt;/p&gt;

&lt;p&gt;So what is &lt;a href="https://github.com/hashicorp/memberlist"&gt;memberlist&lt;/a&gt;?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Memberlist is a Go library that manages cluster membership and member failure detection using a gossip-based protocol.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Sounds great, but what is a gossip-based protocol?&lt;/p&gt;

&lt;p&gt;Imagine a team of developers who like to spread rumours about their coworkers. Let’s say every hour the developers congregate around the water cooler (or some equally banal office space). Each developer pairs off with another randomly and shares their new rumours with each other.&lt;/p&gt;

&lt;p&gt;At the start of the day, Chris starts a new rumour: commenting to Alex that he believes that Mick is paid twice as much as everyone else. At the next meeting, Alex tells Marc, while Chris repeats the idea to David. After each rendezvous, the number of developers who have heard the rumour doubles (except in scenarios where a rumour has already been heard via another developer and has effectively been spread twice). Distributed systems typically implement this type of protocol with a form of random “peer selection”: with a given frequency, each machine picks another machine at random and shares any hot, spicy rumours.&lt;/p&gt;

&lt;p&gt;This is a loose description of how an implementation of a gossip protocol may work. The memberlist package utilises &lt;a href="https://prakhar.me/articles/swim/"&gt;SWIM&lt;/a&gt; but has been modified to increase propagation speeds, convergence rates and general robustness in the face of processing issues (like networking delays). &lt;a href="https://www.hashicorp.com/"&gt;Hashicorp&lt;/a&gt; have released a paper on this named &lt;a href="https://arxiv.org/abs/1707.00788"&gt;Lifeguard: SWIM-ing with Situational Awareness&lt;/a&gt;, which goes into full detail on these modifications.&lt;/p&gt;

&lt;p&gt;With this package, we’re able to create a self-aware cluster of nodes that can perform whatever tasks we see fit.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating a simple cluster
&lt;/h3&gt;

&lt;p&gt;To start, we’ll need to define our configuration. The package contains some methods for generating default configuration based on the environment you intend to run your cluster in. Here they are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/hashicorp/memberlist/blob/master/config.go#L226"&gt;DefaultLANConfig&lt;/a&gt; (Best for local networks): 

&lt;ul&gt;
&lt;li&gt;Uses the hostname as the node name&lt;/li&gt;
&lt;li&gt;Uses &lt;code&gt;7946&lt;/code&gt; as the port for gossip communication&lt;/li&gt;
&lt;li&gt;Has a 10 second TCP timeout&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/hashicorp/memberlist/blob/master/config.go#L283"&gt;DefaultLocalConfig&lt;/a&gt; (Best for loopback environments): 

&lt;ul&gt;
&lt;li&gt;Based on &lt;code&gt;DefaultLANConfig&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Has a 1 second TCP timeout&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/hashicorp/memberlist/blob/master/config.go#L267"&gt;DefaultWANConfig&lt;/a&gt; (Best for nodes on WAN environments): 

&lt;ul&gt;
&lt;li&gt;Based on &lt;code&gt;DefaultLANConfig&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Has a 1 second TCP timeout&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We’re going to run a 3 node cluster on a development machine, so we currently only need &lt;code&gt;DefaultLocalConfig&lt;/code&gt;. We can initialize it like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;config := memberlist.DefaultLocalConfig()

list, err := memberlist.Create(c)

if err != nil {
  panic(err)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we want, we can also broadcast some custom metadata for each node in the cluster. This is useful if you want to use slightly varying configuration between nodes but still want them to communicate. This does not impact the operation of the memberlist itself, but can be used when building applications on top of it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;node := list.LocalNode()

// You can provide a byte representation of any metadata here. You can broadcast the
// config for each node in some serialized format like JSON. By default, this is
// limited to 512 bytes, so may not be suitable for large amounts of data.
node.Meta = []byte("some metadata")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gets us as far as running a single node cluster. In order to join an existing cluster, we can use the &lt;code&gt;list.Join()&lt;/code&gt; method to connect to one or more existing nodes. We can extend the example above to connect to an existing cluster.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Create an array of nodes we can join. If you're using a loopback
// environment you'll need to make sure each node is using its own
// port. This can be set with the configuration's BindPort field.
nodes := []string{
  "0.0.0.0:7946"
}

if _, err := list.Join(nodes); err != nil {
  panic(err)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From here, we’ve successfully configured the client and joined an existing cluster. The package will output some logs so you can see the nodes syncing with each other as well as any errors they run into. On top of this, we need to gracefully leave the memberlist once we’re done. If we don’t handle a graceful exit, the other nodes in the cluster will treat it as a dead node, rather than one that has left.&lt;/p&gt;

&lt;p&gt;To do this, we need to listen for a signal to exit the application, catch it and leave the cluster:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Create a channel to listen for exit signals
stop := make(chan os.Signal, 1)

// Register the signals we want to be notified, these 3 indicate exit
// signals, similar to CTRL+C
signal.Notify(stop, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP)

&amp;lt;-stop

// Leave the cluster with a 5 second timeout. If leaving takes more than 5
// seconds we return.
if err := ml.Leave(time.Second * 5); err != nil {
  panic(err)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Communication between members
&lt;/h3&gt;

&lt;p&gt;Now that we can join and leave the cluster, we can use the member list to perform distributed operations.&lt;/p&gt;

&lt;p&gt;Let’s create a simple messaging system. We could take a message via HTTP on a single node and propagate it to the next node in the cluster. This gives us an eventually consistent system that could be adapted into some sort of event bus.&lt;/p&gt;

&lt;p&gt;This is by no means an optimal solution but demonstrates the power of service discovery in a clustered environment.&lt;/p&gt;

&lt;p&gt;Let’s start with a node:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;type (
  // The Node type represents a single node in the cluster, it contains
  // the list of other members in the cluster and an HTTP client for
  // directly messaging other nodes.
  Node struct {
    memberlist *memberlist.Memberlist
    http *http.Client
  }
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Imagine this node receives a message from an HTTP handler that just takes the entire request body and forwards it to another node. We can implement a method that will iterate over members in the list and attempt to forward a message. Once the message has been successfully forwarded to a single node, it stops handling it. This means we have eventual consistency where &lt;strong&gt;eventually&lt;/strong&gt; all nodes receive all messages.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func (n *Node) HandleMessage(msg []byte) {
  // Iterate over all members in the cluster
  for _, member := range n.memberlist.Members() {
    // We also need to make sure we don't send the message to the node
    // currently processing it
    if member == n.memberlist.LocalNode() {
      continue
    }

    // Memberlist gives us the IP address of every member. In this example,
    // they all handle HTTP traffic on port 8080. You can also provide custom
    // metadata for your node to provide interoperability between nodes with
    // varying configurations.
    url := fmt.Sprintf("http://%s:8080/publish", member.Addr)
    resp, err := n.http.Post(url, "application/json", bytes.NewBuffer(msg))

    if err != nil {
      // handle error and try next node
      continue
    }

    if resp.StatusCode != http.StatusOK {
      // handle unexpected status code and try next node
      continue
    }

    // Otherwise, we've forwarded the message and can do
    // something else.
    break
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hopefully, this post has outlined how you can use the &lt;code&gt;memberlist&lt;/code&gt; package to implement a clustered application. The library is very powerful and allows you to focus on the actual logic your cluster depends on rather than the underlying network infrastructure. In my experience, the time taken for members to synchronise is negligible, but you should keep in mind the protocol is eventual.&lt;/p&gt;

&lt;p&gt;In the example above, we can’t guarantee that our message will be propagated to every single node if there is a lot of traffic in terms of nodes joining/leaving. Ideally, new members should join in a controlled manner and only when necessary.&lt;/p&gt;

&lt;h3&gt;
  
  
  Links
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Distributed_computing"&gt;https://en.wikipedia.org/wiki/Distributed_computing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/hashicorp/memberlist"&gt;https://github.com/hashicorp/memberlist&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/davidsbond/sse-cluster"&gt;https://github.com/davidsbond/sse-cluster&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events"&gt;https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://prakhar.me/articles/swim/"&gt;https://prakhar.me/articles/swim/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.hashicorp.com/"&gt;https://www.hashicorp.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://arxiv.org/abs/1707.00788"&gt;https://arxiv.org/abs/1707.00788&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>go</category>
      <category>hashicorp</category>
      <category>memberlist</category>
      <category>clusters</category>
    </item>
    <item>
      <title>Go: Reverse engineering an AKAI MPD26 using gousb</title>
      <dc:creator>David Bond</dc:creator>
      <pubDate>Fri, 14 Dec 2018 00:00:00 +0000</pubDate>
      <link>https://dev.to/davidsbond/golang-reverse-engineering-an-akai-mpd26-using-gousb-3b49</link>
      <guid>https://dev.to/davidsbond/golang-reverse-engineering-an-akai-mpd26-using-gousb-3b49</guid>
      <description>&lt;h3&gt;
  
  
  Introduction
&lt;/h3&gt;

&lt;p&gt;The other day, I discovered &lt;a href="https://github.com/google/gousb"&gt;Google’s gousb&lt;/a&gt; package. A low level interface for interacting with USB devices in Golang. At the time of writing, it’s fairly one-of-a-kind. I haven’t seen many golang packages attempt to tackle interfacing with USB devices and was keen to give it a try.&lt;/p&gt;

&lt;p&gt;I perused the pile of dead tech sitting around my flat. After some solid thought, I decided to reverse engineer an old &lt;a href="http://www.akaipro.com/products/legacy/mpd-26"&gt;AKAI MPD26&lt;/a&gt; sampler. These things were a super popular choice back when they were first released. Nowadays, there are far fancier samplers available which much more feature-rich interfaces. Unfortunately, I never really got deep into creating electronic music/getting good at using a sampler. This seemed like a way to make it a worthwhile purchase.&lt;/p&gt;

&lt;p&gt;To start, lets examine the different parts of the sampler we want to be able to read from. It provides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;6 faders, these are used for things like managing volume of various channels. You would assign these to something in your DAW that they can manipulate.&lt;/li&gt;
&lt;li&gt;6 knobs, these are more for manipulating automation that you’ve applied to audio tracks, but could easily also be used like a fader and vice versa&lt;/li&gt;
&lt;li&gt;16 pressure sensitive pads, these are used to trigger the sounds you want to hear.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It’s a fairly simple setup. There are a lot more buttons and knobs that modify the output of the aforementioned controls. For example, a ‘note repeat’ button which will cause pads to keep triggering if pressure is maintained on them.&lt;/p&gt;

&lt;p&gt;I decided to set out some goals for how I’d like my interface to the sampler to work:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;I want to implement it in Golang&lt;/li&gt;
&lt;li&gt;It should provide a way to read values from individual aspects of the sampler using channels&lt;/li&gt;
&lt;li&gt;It should abstract away as much of the nastiness of interfacing with USB devices as possible&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Connecting to the USB interface
&lt;/h3&gt;

&lt;p&gt;For honesty, I had never done any programming work related to USB devices before, so I didn’t really know what I was getting myself in to. Luckily, the gousb library provides a really simple interface. However, it requires some background reading on how connections with USB devices work.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://godoc.org/github.com/google/gousb"&gt;godoc page&lt;/a&gt; for the library has a pretty good explanation of how it works under the hood. I wish I’d read it first before trying to bruteforce my way in.&lt;/p&gt;

&lt;h4&gt;
  
  
  Figuring out which USB device to use
&lt;/h4&gt;

&lt;p&gt;First challenge is figuring out which of the USB ports on the host machine is actually connected to the sampler. To do this, we need to know the product and vendor identifiers for the usb device.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://electronics.stackexchange.com/questions/80815/what-is-a-product-id-in-usb-and-do-i-need-to-buy-it-for-my-project"&gt;This question&lt;/a&gt; on stack overflow has a good explanation of what these identifiers are:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The Vendor ID or VID is a 16-bit number which you have to buy from the USB Foundation. If you want to make USB device (and fully play by the rules) the VID identifies your organisation.&lt;/p&gt;

&lt;p&gt;The Product ID or PID is also a 16-bit number but is under your control. When you purchase a VID you have the right to use that with every possible PID so this gives you 65536 possible VID:PID combinations. The intention is that a VID:PID combination should uniquely identify a particular poduct globally.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The AKAI MPD26 will already have a product and vendor identifier, so how do we find those? It’s actually fairly simple if you use the &lt;code&gt;lsusb&lt;/code&gt; command on UNIX systems. After plugging in the device, I was able to locate it pretty easy.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; lsusb -v
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using this command, I was able to determine the product and vendor identifiers: &lt;code&gt;0x0078&lt;/code&gt; and &lt;code&gt;0x09e8&lt;/code&gt;. Using these, we can use the &lt;code&gt;gousb.Context.OpenDevices()&lt;/code&gt; method. This method takes an argument of &lt;code&gt;func(desc *gousb.DeviceDesc) bool&lt;/code&gt;. For each connected USB device, the provided method is executed and should return &lt;code&gt;true&lt;/code&gt; if we’ve found a device we’re interested in accessing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const (
  product = 0x0078
  vendor = 0x09e8
)

func example() {
  ctx := gousb.NewContext()
  devices, _ := ctx.OpenDevices(findMPD26(product, vendor))

  // Do something with the device.
}

func findMPD26(product, vendor uint16) func(desc *gousb.DeviceDesc) bool {
  return func(desc *gousb.DeviceDesc) bool {
    return desc.Product == gousb.ID(product) &amp;amp;&amp;amp; desc.Vendor == gousb.ID(vendor)
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using this code, we get back an array of devices with one element, the sampler!&lt;/p&gt;

&lt;h3&gt;
  
  
  Reading from the USB device
&lt;/h3&gt;

&lt;p&gt;When dealing with a USB device, we need to obtain three things: a configuration, an interface and an endpoint.&lt;/p&gt;

&lt;p&gt;The library defines USB configuration as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A config descriptor determines the list of available USB interfaces on the device.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Interfaces are defined too:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Each interface is a virtual device within the physical USB device and its active config. There can be many interfaces active concurrently. Interfaces are enumerated sequentially starting from zero.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And finally, endpoints:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;An endpoint can be considered similar to a UDP/IP port, except the data transfers are unidirectional.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;What we’re after is that endpoint, that is where we will be able to read data from the device and react to it. To get it, we need to figure out the correct configuration, obtain the interface and then the endpoint.&lt;/p&gt;

&lt;p&gt;My first attempt at connecting to the USB device failed for a couple of reasons. I tried to use some of the convenience methods available in the &lt;code&gt;gousb&lt;/code&gt; library. Mainly, the &lt;code&gt;DefaultInterface&lt;/code&gt; and &lt;code&gt;ActiveConfigNum&lt;/code&gt; methods.&lt;/p&gt;

&lt;p&gt;Here’s the documentation for &lt;code&gt;DefaultInterface&lt;/code&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;DefaultInterface opens interface #0 with alternate setting #0 of the currently active config. It’s intended as a shortcut for devices that have the simplest interface of a single config, interface and alternate setting. The done func should be called to release the claimed interface and config.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And &lt;code&gt;ActiveConfigNum&lt;/code&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;ActiveConfigNum returns the config id of the active configuration. The value corresponds to the ConfigInfo.Config field of one of the ConfigInfos of this Device.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;code&gt;DefaultInterface&lt;/code&gt; should allow you to skip finding an appropriate configuration so you can just get straight to your desired endpoint. I’m not sure if it’s something to do with my machine, or the device itself, but this would return an error for me each time. I had the same issue with the &lt;code&gt;ActiveConfigNum&lt;/code&gt; method.&lt;/p&gt;

&lt;p&gt;However, when trying to connect to the device, I’d get the following error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;libusb: device or resource busy [code -6]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is because the kernel has already assigned a driver to the USB device. In this case, &lt;code&gt;pulseaudio&lt;/code&gt; was claiming the USB device as soon as it was plugged in since its an audio interface. I was able to debug this using the &lt;code&gt;journalctl&lt;/code&gt; command while reconnecting the USB device.&lt;/p&gt;

&lt;p&gt;This command is used to view &lt;code&gt;Systemd&lt;/code&gt; logs and should let us know what is happening to our USB device whenever it is plugged in. Using the &lt;code&gt;-f&lt;/code&gt; flag allows us to just read the most recent logs in real time. From this, I found that the &lt;code&gt;pulseaudio&lt;/code&gt; driver would claim the device as soon as it was plugged in, so we can’t use it!&lt;/p&gt;

&lt;p&gt;The fix is nice and easy, the &lt;code&gt;gousb&lt;/code&gt; library provides a method on the &lt;code&gt;Device&lt;/code&gt; type called &lt;code&gt;SetAutoDetach&lt;/code&gt; that will take the device away from &lt;code&gt;pulseaudio&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;SetAutoDetach enables/disables automatic kernel driver detachment. When autodetach is enabled gousb will automatically detach the kernel driver on the interface and reattach it when releasing the interface. Automatic kernel driver detachment is disabled on newly opened device handles by default.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const (
  product = 0x0078
  vendor = 0x09e8
)

func example() {
  ctx := gousb.NewContext()
  devices, _ := ctx.OpenDevices(findMPD26(product, vendor))

  // Detach the device from whichever process already
  // has it.
  devices[0].SetAutoDetach(true)
}

func findMPD26(product, vendor uint16) func(desc *gousb.DeviceDesc) bool {
  return func(desc *gousb.DeviceDesc) bool {
    return desc.Product == gousb.ID(product) &amp;amp;&amp;amp; desc.Vendor == gousb.ID(vendor)
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The next issue I faced was in the &lt;code&gt;ActiveConfigNum&lt;/code&gt; and &lt;code&gt;DefaultInterface&lt;/code&gt; methods. The configuration that the USB device was using would not allow me to use these methods. This means we have to make our own decisions on which config and interface to use.&lt;/p&gt;

&lt;p&gt;To work around this, I decided to manually loop through configurations, then available interfaces. Once we get an interface we can use, we find the &lt;code&gt;IN&lt;/code&gt; endpoint we can read from.&lt;/p&gt;

&lt;p&gt;This code is a little bit ugly and I have excluded the error handling code for brevity. I’m sure there’s a nicer way of doing this but for the sake of learning it serves its purpose:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Iterate through configurations
for num := range devices[0].Desc.Configs {
  config, _ := devices[0].Config(num)

  // In a scenario where we have an error, we can continue
  // to the next config. Same is true for interfaces and
  // endpoints.
  defer config.Close()

  // Iterate through available interfaces for this configuration
  for _, desc := range config.Desc.Interfaces {
    intf, _ := config.Interface(desc.Number, 0)

    // Iterate through endpoints available for this interface.
    for _, endpointDesc := range intf.Setting.Endpoints {
      // We only want to read, so we're looking for IN endpoints.
      if endpointDesc.Direction == gousb.EndpointDirectionIn {
        endpoint, _ := intf.InEndpoint(endpointDesc.Number)

        // When we get here, we have an endpoint where we can
        // read data from the USB device
      }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To stitch this all together we need a type that can hold all the contextual information about the USB device we’re interacting with. This is the aptly named &lt;code&gt;MPD26&lt;/code&gt; type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;type MPD26 struct {
  // Fields for interacting with the USB connection
  context *gousb.Context
  device *gousb.Device
  intf *gousb.Interface
  endpoint *gousb.InEndpoint

  // Additional fields we'll get to later
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What we need now is a method that will constantly read from the endpoint and write values to channels. I’ve created an unexported method named &lt;code&gt;read&lt;/code&gt; that runs an infinite loop in its own goroutine once the connection to the USB device is successful. Once again, error handling is redacted for clarity.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func (mpd *MPD26) read(interval time.Duration, maxSize int) {
  ticker := time.NewTicker(interval)
  defer ticker.Stop()

  for {
    select {
    case &amp;lt;-ticker.C:
      buff := make([]byte, maxSize)
      n, _ := mpd.endpoint.Read(buff)

      data := buff[:n]
      // Do something with this data
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You’ll notice this method takes in two paramters, &lt;code&gt;interval&lt;/code&gt; and &lt;code&gt;maxSize&lt;/code&gt;. The &lt;code&gt;interval&lt;/code&gt; parameter determines how often we should be attempting to read data from the USB device. It’s important to note that calling the &lt;code&gt;mpd.endpoint.Read&lt;/code&gt; method halts further execution if there’s no data to read, so using this interval just ensures we don’t read too often from the device. The &lt;code&gt;maxSize&lt;/code&gt; parameter determines the maximum size of the buffer we should use when reading data. Both of these values can be obtained from the device configuration we looked at earlier:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mpd := &amp;amp;MPD26{
  context: ctx,
  device: devices[0],
  intf: intf,
  endpoint: endpoint,
}

// The endpoint description defines the poll interval and max packet
// size.
go mpd.read(endpointDesc.PollInterval, endpointDesc.MaxPacketSize)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To start with, lets just print the contents of the byte array to &lt;code&gt;stdout&lt;/code&gt; so that we can see the difference in values based on the controls we’re using. Below are some samples:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[11, 176, 1, 127] # Output when moving the first fader
[11, 176, 11, 127] # Output when moving the first knob
[9, 144, 36, 127] # Output when triggering a pad
[8, 144, 26, 0] # Output when releasing a pad
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Reverse engineering serial data
&lt;/h3&gt;

&lt;p&gt;We’re going to use the output we get reading the raw USB data to make some assumptions about which values mean what. Luckily, the values we’re getting are MIDI. So any variance between 0-127 is usually a good candidate for the value of the control you’re looking at. Based on the console output, it seems that the last byte in the array is always the MIDI value of the control.&lt;/p&gt;

&lt;p&gt;This means the first 3 bytes should indicate the control we’re using. I’ve still yet to figure out what all bytes in the array represent, but there are consistent values for certain controls, so we can use these to update the respective state of a control in the library.&lt;/p&gt;

&lt;h4&gt;
  
  
  Faders &amp;amp; Knobs
&lt;/h4&gt;

&lt;p&gt;The faders and knobs were the easiest controls to get working. They only have a number to identify them and a value between 0 and 127. After playing with all of them, the first two bytes are consistently &lt;code&gt;[11, 176]&lt;/code&gt;. We can use this information to create a method to identify if a message is for the value of a control:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func isControl(data []byte) bool {
  // Knobs and faders all share the same two bytes in common, first and second
  // are always 11 and 176
  return data[0] == 11 &amp;amp;&amp;amp; data[1] == 176
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Easy enough. The next challenge is to determine if we’re handling the change of a knob or a fader. This can be determined using the third byte in the array, which contains values from 1 to 6 for faders and 11 to 16 for the knobs. Using these, we can create two new helper methods to identify the types of control we’re getting a message for:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func isFader(data []byte) bool {
  // A fader is a control where the value of the third byte is always
  // 1 to 6
  return isControl(data) &amp;amp;&amp;amp; data[2] &amp;gt;= 1 &amp;amp;&amp;amp; data[2] &amp;lt;= 6
}

func isKnob(data []byte) bool {
  // A knob is a control where the value of the third byte is always
  // 11 to 16
  return isControl(data) &amp;amp;&amp;amp; data[2] &amp;gt;= 11 &amp;amp;&amp;amp; data[2] &amp;lt;= 16
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Pads
&lt;/h4&gt;

&lt;p&gt;The pads have a little more logic to them, but work the same way. The first byte determines whether or not the pad has been pressed or released, the second byte is always 144 and the third byte is a number between 26 and 51 that identifies the unique pad being pressed/released. Here’s our method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func isPad(data []byte) bool {
 return (data[0] == 9 || data[0] == 8) &amp;amp;&amp;amp; data[1] == 144 &amp;amp;&amp;amp; (data[2] &amp;gt;= 36 &amp;amp;&amp;amp; data[2] &amp;lt;= 51)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Creating the Golang API
&lt;/h3&gt;

&lt;p&gt;Now we need to expose this data in a nice way so that people can build things in Go using an MPD26. Earlier we saw code for reading the serial data, but we need a way to get that data out in a format that would make sense to someone looking directly at the sampler. We also want things to work asynchronously, waiting to read from a pad shouldn’t block a read from a fader.&lt;/p&gt;

&lt;p&gt;For the asynchronous output, we’re going to use channels, I’ve added the following fields to the &lt;code&gt;MPD26&lt;/code&gt; type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Channels for various components
faders map[int]chan int
knobs map[int]chan int
pads map[int]chan int
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I’ve also updated the &lt;code&gt;read&lt;/code&gt; method to make a call to a &lt;code&gt;paseMessage&lt;/code&gt; function that classifies the type of input and writes to the correct channel:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func (mpd *MPD26) parseMessage(msg []byte) {
 defer mpd.waitGroup.Done()

 // Discard invalid messages.
 if len(msg) &amp;lt; 4 {
  return
 }

 mpd.waitGroup.Add(1)

 if isFader(msg) {
  go mpd.handleFader(msg)
  return
 }

 if isKnob(msg) {
  go mpd.handleKnob(msg)
  return
 }

 if isPad(msg) {
  go mpd.handlePad(msg)
 }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, we now have 3 more functions for handling each kind of input &lt;code&gt;handlePad&lt;/code&gt;, &lt;code&gt;handleKnob&lt;/code&gt; and &lt;code&gt;handleFader&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func (mpd *MPD26) handlePad(data []byte) {
 defer mpd.waitGroup.Done()

 num := int(data[2]) - 35
 val := int(data[3])

 channel, ok := mpd.pads[num]

 if !ok {
  return
 }

 channel &amp;lt;- val
}

func (mpd *MPD26) handleKnob(data []byte) {
 defer mpd.waitGroup.Done()

 var num int
 val := int(data[3])

 switch data[2] {
 case 12:
  num = 6
 case 11:
  num = 5
 case 14:
  num = 4
 case 13:
  num = 3
 case 16:
  num = 2
 case 15:
  num = 1
 default:
  return
 }

 // Check if there's a channel already listening
 // to this knob, if so, write to it. Otherwise
 // ignore the message.
 channel, ok := mpd.knobs[num]

 if !ok {
  return
 }

 channel &amp;lt;- val
}

func (mpd *MPD26) handleFader(data []byte) {
 defer mpd.waitGroup.Done()

 num := int(data[2])
 val := int(data[3])

 // Check if there's a channel already listening
 // to this fader, if so, write to it. Otherwise
 // ignore the message.
 channel, ok := mpd.faders[num]

 if !ok {
  return
 }

 channel &amp;lt;- val
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, we just need some exported functions on the &lt;code&gt;MPD26&lt;/code&gt; type that someone can use to get the pad/fader/knob they want to read from:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func (mpd *MPD26) Fader(id int) &amp;lt;-chan int {
    channel, ok := mpd.faders[id]

    if !ok {
        channel = make(chan int)
        mpd.faders[id] = channel
    }

    return channel
}

func (mpd *MPD26) Pad(id int) &amp;lt;-chan int {
    channel, ok := mpd.pads[id]

    if !ok {
        channel = make(chan int)
        mpd.pads[id] = channel
    }

    return channel
}

func (mpd *MPD26) Knob(id int) &amp;lt;-chan int {
    channel, ok := mpd.knobs[id]

    if !ok {
        channel = make(chan int)
        mpd.knobs[id] = channel
    }

    return channel
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With all these in place, we can now connect and read from the sampler. In future, I’d like to hook this up to an audio library like &lt;a href="https://github.com/faiface/beep"&gt;beep&lt;/a&gt; in order to get some actual output. But for now, we’ve got a working interface with the sampler!&lt;/p&gt;

&lt;h3&gt;
  
  
  Links
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/google/gousb"&gt;https://github.com/google/gousb&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.akaipro.com/products/legacy/mpd-26"&gt;http://www.akaipro.com/products/legacy/mpd-26&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://electronics.stackexchange.com/questions/80815/what-is-a-product-id-in-usb-and-do-i-need-to-buy-it-for-my-project"&gt;https://electronics.stackexchange.com/questions/80815/what-is-a-product-id-in-usb-and-do-i-need-to-buy-it-for-my-project&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://godoc.org/github.com/google/gousb"&gt;https://godoc.org/github.com/google/gousb&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/faiface/beep"&gt;https://github.com/faiface/beep&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>go</category>
      <category>usb</category>
      <category>akai</category>
      <category>mpd26</category>
    </item>
    <item>
      <title>Go: Implementing kafka consumers using sarama-cluster</title>
      <dc:creator>David Bond</dc:creator>
      <pubDate>Wed, 22 Aug 2018 00:00:00 +0000</pubDate>
      <link>https://dev.to/davidsbond/golang-implementing-kafka-consumers-using-sarama-cluster-4fko</link>
      <guid>https://dev.to/davidsbond/golang-implementing-kafka-consumers-using-sarama-cluster-4fko</guid>
      <description>&lt;h3&gt;
  
  
  Introduction
&lt;/h3&gt;

&lt;p&gt;Nowadays it seems as though more and more companies are using event-based architectures to provide communication between services across various domains. &lt;a href="http://confluent.io/"&gt;Confluent&lt;/a&gt; maintain a &lt;a href="https://cwiki.apache.org/confluence/display/KAFKA/Powered+By"&gt;huge list&lt;/a&gt; of companies actively using &lt;a href="https://kafka.apache.org/"&gt;Apache Kafka&lt;/a&gt;, a high performance messaging system and the subject of this post.&lt;/p&gt;

&lt;p&gt;Kafka has been so heavily adopted in part due to its high performance and the large number of client libraries available in a multitude of languages.&lt;/p&gt;

&lt;p&gt;The concept is fairly simple, clients either produce or consume events that are categorised under “topics”. For example, a company like LinkedIn may produce an event against a &lt;code&gt;user_created&lt;/code&gt; topic after a successful sign-up, allowing multiple services to asynchronously react and perform respective processing regarding that user. One service might handle sending me a welcome email, whereas another will attempt to identify other users I may want to connect with.&lt;/p&gt;

&lt;p&gt;Kafka events are divided into “partitions”. These are parallel event streams that allow multiple consumers to process events from the same topic. Every event contains what is called an “offset”, a number that represents where an event resides in the sequence of all events in a partition. Imagine all events for a topic partition are stored as an array, the offset would be the index where a particular event is located in time. This allows consumers to specify a starting point from which to consume events, granting the ability to avoid duplication of events processed, or the consumption of events produced earlier in time.&lt;/p&gt;

&lt;p&gt;Consumers can then form “groups”, where each consumer reads one or more unique partitions to spread the consumption of a topic across multiple consumers. This is especially useful when running replicated services and can increase event throughput.&lt;/p&gt;

&lt;h3&gt;
  
  
  Implementing a Kafka consumer
&lt;/h3&gt;

&lt;p&gt;There aren’t a huge number of viable options when it comes to implementing a Kafka consumer in Go. This tutorial focuses on &lt;a href="https://github.com/bsm/sarama-cluster"&gt;sarama-cluster&lt;/a&gt;, a balanced consumer implementation built on top the existing &lt;a href="https://github.com/shopify/sarama"&gt;sarama&lt;/a&gt; client library by &lt;a href="https://www.shopify.com"&gt;Shopify&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The library has a concise API that makes getting started fairly simple. The first step is to define our consumer configuration. We can use the &lt;code&gt;NewConfig&lt;/code&gt; method which creates a default configuration with some sensible starting values&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Create a configuration with some sane default values
config := cluster.NewConfig()

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Authentication
&lt;/h4&gt;

&lt;p&gt;If you’re sensible, the Kafka instance you’re connecting to will have some form of authentication. The &lt;code&gt;sarama-cluster&lt;/code&gt; library supports both TLS and SASL authentication methods.&lt;/p&gt;

&lt;p&gt;If you’re using TLS certificates, you can populate the &lt;code&gt;config.TLS&lt;/code&gt; struct field:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;config := cluster.NewConfig()

// Load an X509 certificate pair like you would for any other TLS
// configuration
cert, err := tls.LoadX509KeyPair("cert.pem", "cert.key")

if err != nil {
  panic(err)
}

ca, err := ioutil.ReadFile("ca.pem")

if err != nil {
  panic(err)
}

pool := x509.NewCertPool()
pool.AppendCertsFromPEM(ca)

tls := &amp;amp;tls.Config{
  Certificates: []tls.Certificate{cert},
  RootCAs: pool,
}

kafkaConfig.Net.TLS.Config = tls

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It’s important to note that if you’re running your consumer within a docker image, you’ll need to install &lt;code&gt;ca-certificates&lt;/code&gt; in order to create an x509 certificate pool. In a Dockerfile based on alpine this looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FROM alpine

RUN apk add --update ca-certificates

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Alternatively, if you’re using SASL for authentication, you can populate the &lt;code&gt;config.SASL&lt;/code&gt; struct field like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;config := cluster.NewConfig()

// Set your SASL username and password
config.SASL.User = "username"
config.SASL.Password = "password"

// Enable SASL
config.SASL.Enable = true

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Implementing the consumer
&lt;/h4&gt;

&lt;p&gt;Now that we’ve created a configuration with our authentication method of choice, we can create a consumer that will allow us to handle events for specified topics. You’re going to need to know the addresses of your Kafka brokers, the name of your consumer group and each topic you wish to consume:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;consumer, err := cluster.NewConsumer(
  []string{"broker-address-1", "broker-address-2"},
  "group-id",
  []string{"topic-1", "topic-2", "topic-3"},
  kafkaConfig)

if err != nil {
  panic(err)
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;sarama-cluster&lt;/code&gt; library allows you to specify a consumer mode within the config. It’s important to understand the difference as your implementation will differ based on what you’ve chosen. This can be modified via the &lt;code&gt;config.Group.Mode&lt;/code&gt; struct field and has two options. These are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ConsumerModeMultiplex&lt;/code&gt; - By default, messages and errors from the subscribed topics and partitions are all multiplexed and made available through the consumer’s &lt;code&gt;Messages()&lt;/code&gt; and &lt;code&gt;Errors()&lt;/code&gt; channels.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ConsumerModePartitions&lt;/code&gt; - Users who require low-level access can enable &lt;code&gt;ConsumerModePartitions&lt;/code&gt; where individual partitions are exposed on the &lt;code&gt;Partitions()&lt;/code&gt; channel. Messages and errors must then be consumed on the partitions themselves.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When using &lt;code&gt;ConsumerModeMultiplex&lt;/code&gt;, all messages come from a single channel exposed via the &lt;code&gt;Messages()&lt;/code&gt; method. Reading these messages looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// The loop will iterate each time a message is written to the underlying channel
for msg := range consumer.Messages() {
  // Now we can access the individual fields of the message and react
  // based on msg.Topic
  switch msg.Topic {
    case "topic-1":
      handleTopic1(msg.Value)
      break;
    // ...
  }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you want a more low-level implementation where you can react to partition changes yourself, you’re going to want to use &lt;code&gt;ConsumerModePartitions&lt;/code&gt;. This provides you the individual partitions via the &lt;code&gt;consumer.Partitions()&lt;/code&gt; method. This exposes an underlying channel that partitions are written to when the consumer group rebalances. You can then use each partition to read messages and errors:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Every time the consumer is balanced, we'll get a new partition to read from
for partition := range consumer.Partitions() {
  // From here, we know exactly which topic we're consuming via partition.Topic(). So won't need any
  // branching logic based on the topic.
  for msg := range consumer.Messages() {
    // Now we can access the individual fields of the message
    handleTopic1(msg.Value)   
  }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;ConsumerModePartitions&lt;/code&gt; way of doing things will require you to code more oversight into your consumer. For one, you’re going to need to gracefully handle the situation where the partition closes in a rebalance situation. These will occur when adding new consumers to the group. You’re also going to need to manually call the &lt;code&gt;partition.Close()&lt;/code&gt; method when you’re done consuming.&lt;/p&gt;

&lt;h3&gt;
  
  
  Handling errors &amp;amp; rebalances
&lt;/h3&gt;

&lt;p&gt;Should you add more consumers to the group, the existing ones will experience a rebalance. This is where the assignment of partitions to each consumer changes for an optimal spread across consumers. The &lt;code&gt;consumer&lt;/code&gt; instance we’ve created already exposes a &lt;code&gt;Notifications()&lt;/code&gt; channel from which we can log/react to these changes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  for notification := range consumer.Notifications() {
    // The type of notification we've received, will be
    // rebalance start, rebalance ok or error
    fmt.Println(notification.Type)

    // The topic/partitions that are currently read by the consumer
    fmt.Println(notification.Current)

    // The topic/partitions that were claimed in the last rebalance
    fmt.Println(notification.Claimed)

    // The topic/partitions that were released in the last rebalance
    fmt.Println(notification.Released)
  }

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Errors are just as easy to read and are made available via the &lt;code&gt;consumer.Errors()&lt;/code&gt; channel. They return a standard &lt;code&gt;error&lt;/code&gt; implementation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  for err := range consumer.Errors() {
    // React to the error
  }

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In order to enable the reading of notification and errors, we need to make some small changes to our configuration like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;config.Consumer.Return.Errors = true
config.Group.Return.Notifications = true

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Committing offsets
&lt;/h3&gt;

&lt;p&gt;The last step in implementing the consumer is to commit our offsets. In short, we’re telling Kafka that we have finished processing a message and we do not want to consume it again. This should be done once you no longer require the message data for any processing. If you commit offsets too early, you may lose the ability to easily reconsume the event if something goes wrong. Let’s say you’re writing the event contents straight to a database, don’t commit offsets before you’ve written the contents of the event to your database successfully. That way, should the database operation fail, you can just reconsume the event to try again.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// The loop will iterate each time a message is written to the underlying channel
for msg := range consumer.Messages() {
  // Now we can access the individual fields of the message and react
  // based on msg.Topic
  switch msg.Topic {
    case "topic-1":
      // Do everything we need for this topic
      handleTopic1(msg.Value)

      // Mark the message as processed. The sarama-cluster library will
      // automatically commit these.
      // You can manually commit the offsets using consumer.CommitOffsets()
      consumer.MarkOffset(msg)
      break;
      // ...
  }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is everything you need in order to implement a simple Kafka consumer group. The &lt;code&gt;sarama-cluster&lt;/code&gt; library provides a lot more configuration options to suit your needs based on how you maintain your Kafka brokers. I’d recommend browsing through all the config values yourself to determine if you need to tweak any.&lt;/p&gt;

&lt;h3&gt;
  
  
  Links
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="http://confluent.io/"&gt;http://confluent.io/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cwiki.apache.org/confluence/display/KAFKA/Powered+By"&gt;https://cwiki.apache.org/confluence/display/KAFKA/Powered+By&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kafka.apache.org/"&gt;https://kafka.apache.org/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/bsm/sarama-cluster"&gt;https://github.com/bsm/sarama-cluster&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/shopify/sarama"&gt;https://github.com/shopify/sarama&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.shopify.com"&gt;https://www.shopify.com&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>go</category>
      <category>kafka</category>
      <category>sarama</category>
      <category>cluster</category>
    </item>
    <item>
      <title>Go: Debugging memory leaks using pprof</title>
      <dc:creator>David Bond</dc:creator>
      <pubDate>Wed, 08 Aug 2018 00:00:00 +0000</pubDate>
      <link>https://dev.to/davidsbond/golang-debugging-memory-leaks-using-pprof-5di8</link>
      <guid>https://dev.to/davidsbond/golang-debugging-memory-leaks-using-pprof-5di8</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;I work as a software engineer at &lt;a href="https://www.ovoenergy.com/" rel="noopener noreferrer"&gt;OVO Energy&lt;/a&gt; where my team are implementing the CRM solution used by customer services. We’re currently building a new set of microservices to replace the existing services. One of our microservices is responsible for migrating data from the old system into the new one.&lt;/p&gt;

&lt;p&gt;A few days after deploying a new version of the service, I opened the relevant monitoring dashboard and saw this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdavidsbond.github.io%2Fassets%2F2018-08-08-debugging-memory-leaks-using-pprof%2F1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdavidsbond.github.io%2Fassets%2F2018-08-08-debugging-memory-leaks-using-pprof%2F1.png" alt="Memory usage graph"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;According to this graph, we have a memory leak somewhere. This is most likely due to an issue with the management of goroutines within the service. However, the service relies heavily on concurrency, so finding where the leak is might not be so easy. Luckily, goroutines are lightweight, allowing a reasonable amount of time to figure out where the leak is before it becomes a real/expensive problem. The two spikes on the 12pm marks are times when migrations occurred.&lt;/p&gt;

&lt;h3&gt;
  
  
  Background
&lt;/h3&gt;

&lt;p&gt;Over the course of a few weeks I designed and implemented the service and hosted it in our &lt;a href="https://kubernetes.io" rel="noopener noreferrer"&gt;Kubernetes&lt;/a&gt; cluster on &lt;a href="https://cloud.google.com/" rel="noopener noreferrer"&gt;GCP&lt;/a&gt;, ensuring that I added monitoring functionality in order to make it ready for production. This included an HTTP endpoint for health checks, log-based metrics and uptime checks using &lt;a href="https://cloud.google.com/stackdriver/" rel="noopener noreferrer"&gt;Stackdriver&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This service has to communicate with a handful of external dependencies, these are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://kafka.apache.org/" rel="noopener noreferrer"&gt;Apache Kafka&lt;/a&gt; - Kafka allows services to publish and subscribe to streams of records, similar to a message queue or enterprise messaging system. An event is published from another service to signify that a customer is ready for us to migrate.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.confluent.io/current/schema-registry/docs/index.html" rel="noopener noreferrer"&gt;Confluent Schema Registry&lt;/a&gt; - The registry allows us to apply versioned schemas to our Kafka events and is used to decode messages from &lt;a href="https://avro.apache.org/" rel="noopener noreferrer"&gt;Apache Avro&lt;/a&gt; format into its JSON counterpart.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.postgresql.org/" rel="noopener noreferrer"&gt;PostgreSQL&lt;/a&gt; - A relational database used to store information on the migration of a customer (records created, any errors and warnings etc).&lt;/li&gt;
&lt;li&gt;Two &lt;a href="https://www.salesforce.com/" rel="noopener noreferrer"&gt;Salesforce&lt;/a&gt; instances - These are where the customer support staff work on a day-to-day basis. One containing the source of the V1 data and one to store the new V2 data.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;From all these dependencies, we have a health check that looks something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "status": "UP",
    "uptime": "22h54m27.491102074s",
    "goroutines": 24,
    "version": "go1.9.7",
    "sf1": {
        "status": "UP"
    },
    "sf2": {
        "status": "UP"
    },
    "database": {
        "status": "UP"
    },
    "kafka": {
        "status": "UP"
    },
    "registry": {
        "status": "UP"
    }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First thing I would note, using &lt;code&gt;runtime.NumGoroutine()&lt;/code&gt; to see the number of running goroutines is extremely helpful in identifying the source of the memory leak. I recommend having some way to monitor this in your production environments. In this scenario, our HTTP health check returns the number of running goroutines.&lt;/p&gt;

&lt;p&gt;On the day of the leak, I saw the number of goroutines exceed 100000 and keep rising steadily with each health check request. Below are the steps I took in debugging this issue.&lt;/p&gt;

&lt;h3&gt;
  
  
  Enabling pprof output via HTTP
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;pprof&lt;/code&gt; tool describes itself as “a tool for visualization and analysis of profiling data”, you can view the GitHub repository for it &lt;a href="https://github.com/google/pprof" rel="noopener noreferrer"&gt;here&lt;/a&gt;. This tool allows us to obtain various metrics on the low-level operations of a Go program. For our purposes, it allows us to get detailed information on running goroutines. The only problem here is that &lt;code&gt;pprof&lt;/code&gt; is a binary. This means we would have to run commands against the service in production for meaningful results. The application also runs within a &lt;a href="https://www.docker.com/" rel="noopener noreferrer"&gt;Docker&lt;/a&gt; container based on a &lt;code&gt;scratch&lt;/code&gt; image, which makes using the binary somewhat invasive. How then can we get the profiling data we need?&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;net/http/pprof&lt;/code&gt; package within the standard library exposes &lt;code&gt;pprof&lt;/code&gt; methods for providing profiling data via HTTP endpoints. This project uses &lt;a href="https://github.com/gorilla/mux" rel="noopener noreferrer"&gt;mux&lt;/a&gt; as its url router, so exposing the endpoints can be done using the &lt;code&gt;HandleFunc&lt;/code&gt; and &lt;code&gt;Handle&lt;/code&gt; methods:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Create a new router
router := mux.NewRouter()

// Register pprof handlers
router.HandleFunc("/debug/pprof/", pprof.Index)
router.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
router.HandleFunc("/debug/pprof/profile", pprof.Profile)
router.HandleFunc("/debug/pprof/symbol", pprof.Symbol)

router.Handle("/debug/pprof/goroutine", pprof.Handler("goroutine"))
router.Handle("/debug/pprof/heap", pprof.Handler("heap"))
router.Handle("/debug/pprof/threadcreate", pprof.Handler("threadcreate"))
router.Handle("/debug/pprof/block", pprof.Handler("block"))

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once I had added these handlers, I span up a local instance of the service and navigated to the &lt;code&gt;/debug/pprof/goroutine&lt;/code&gt; endpoint.&lt;/p&gt;

&lt;h3&gt;
  
  
  Understanding pprof output
&lt;/h3&gt;

&lt;p&gt;The response I got from &lt;code&gt;/debug/pprof/goroutine&lt;/code&gt; was fairly easy to interpret, here’s a sample that shows the routines span upby the Kafka consumer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;goroutine profile: total 25
2 @ 0x434420 0x4344e5 0x404747 0x40451b 0x8a25af 0x8f2486 0x8ee88c 0x461d61
#   0x8a25ae    /vendor/github.com/Shopify/sarama.(*Broker).responseReceiver+0xfe
            /vendor/github.com/Shopify/sarama/broker.go:682
#   0x8f2485    /vendor/github.com/Shopify/sarama.(*Broker).(/vendor/github.com/Shopify/sarama.responseReceiver)-fm+0x35
            /vendor/github.com/Shopify/sarama/broker.go:149
#   0x8ee88b    /vendor/github.com/Shopify/sarama.withRecover+0x4b
            /vendor/github.com/Shopify/sarama/utils.go:45

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first line tells us the total number of running goroutines. In this example, I was running a version of the servicewhich had fixed the memory leak. As you can see we have a total of 25 running goroutines. The following lines tell us how manygoroutines belong to specific package methods. In this example, we can see the &lt;code&gt;.responseReceiver&lt;/code&gt; method from the &lt;code&gt;Broker&lt;/code&gt; type in the &lt;code&gt;sarama&lt;/code&gt; package is currently using 2 goroutines. This was the silver bullet in locating the culprit of the leak.&lt;/p&gt;

&lt;p&gt;In the leaking version of the service, two particular lines stand out that have an ever increasing number of active goroutines.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;14 @ 0x434420 0x444c4e 0x7c87fd 0x461d61
#   0x7c87fc    net/http.(*persistConn).writeLoop+0x15c C:/Go/src/net/http/transport.go:1822

14 @ 0x434420 0x444c4e 0x7c761e 0x461d61
#   0x7c761d    net/http.(*persistConn).readLoop+0xe9d  C:/Go/src/net/http/transport.go:1717

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Somewhere in the code we’re creating HTTP connections that are stuck in a read/write loop. I decided to take a look into the source code of the standard library to understand this behavior. The first place I looked was the location at which these routines are spawned. This is within the &lt;code&gt;net/http/transport.go&lt;/code&gt; file, by the &lt;code&gt;dialConn&lt;/code&gt; method. The full contents of which can be viewed &lt;a href="https://golang.org/src/net/http/transport.go" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// transport.go:1234

pconn.br = bufio.NewReader(pconn)
pconn.bw = bufio.NewWriter(persistConnWriter{pconn})
go pconn.readLoop() // &amp;lt;- Here is the source of our leak
go pconn.writeLoop()
return pconn, nil

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that we’ve identified where our leak is coming from, we need to understand what scenario is causing these goroutines to never return. I noticed that the number of goroutines only increased after a health check. In the production system, this was happening approximately once a minute using Stackdriver’s uptime checks from different regions.&lt;/p&gt;

&lt;p&gt;After a little bit of searching, I determined the source of the leak was during our request to the Confluent schema registry to assert its availability. I had made some rather naive mistakes when writing this package. First off, here’s the &lt;code&gt;New&lt;/code&gt; method that creates the client for the registry:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func New(baseURL, user, pass string, cache *cache.Cache) Registry {
    return &amp;amp;registry{
        baseURL: baseURL,
        username: user,
        password: pass,
        cache: cache,
        client: &amp;amp;http.Client{},
    }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Error number one, &lt;strong&gt;always configure your http clients with sensible values&lt;/strong&gt;. This issue can be half resolved by the inclusion of a timeout:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func New(baseURL, user, pass string, cache *cache.Cache) Registry {
    return &amp;amp;registry{
        baseURL: baseURL,
        username: user,
        password: pass,
        cache: cache,
        client: &amp;amp;http.Client{
            Timeout: time.Second * 10,
        },
    }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this change in place, the leaking routines were cleaned up after about 10 seconds.&lt;/p&gt;

&lt;p&gt;While this works, there was one more one-line change to resolve this issue within the method that generates the HTTP requests. I was looking through the definition of the &lt;code&gt;http.Request&lt;/code&gt; type and found the &lt;code&gt;Close&lt;/code&gt; flag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// request.go:197

// Close indicates whether to close the connection after
// replying to this request (for servers) or after sending this
// request and reading its response (for clients).
//
// For server requests, the HTTP server handles this automatically
// and this field is not needed by Handlers.
//
// For client requests, setting this field prevents re-use of
// TCP connections between requests to the same hosts, as if
// Transport.DisableKeepAlives were set.
Close bool

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I decided to check what would happen if I set this flag to true and if it would prevent the locking of these goroutines. Here’s what it looked like in code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func (r *registry) buildRequest(method, url string) (*http.Request, error) {
    req, err := http.NewRequest(method, url, nil)

    if err != nil {
        return nil, errors.Annotate(err, "failed to create http request")
    }

    req.SetBasicAuth(r.username, r.password)
    req.Close = true

    return req, nil
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After implementing these changes and deploying it to production, the memory usage of the service stayed at a healthy amount forever more:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdavidsbond.github.io%2Fassets%2F2018-08-08-debugging-memory-leaks-using-pprof%2F2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdavidsbond.github.io%2Fassets%2F2018-08-08-debugging-memory-leaks-using-pprof%2F2.png" alt="Memory usage graph"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Lessons learned
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Monitor your number of active goroutines, especially in services that rely on concurrency patterns&lt;/li&gt;
&lt;li&gt;Add functionality to your services to expose profiling data using &lt;code&gt;pprof&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Set reasonable configuration values for your HTTP clients and requests&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Links
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.ovoenergy.com/" rel="noopener noreferrer"&gt;https://www.ovoenergy.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kubernetes.io" rel="noopener noreferrer"&gt;https://kubernetes.io&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cloud.google.com/" rel="noopener noreferrer"&gt;https://cloud.google.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cloud.google.com/stackdriver/" rel="noopener noreferrer"&gt;https://cloud.google.com/stackdriver/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kafka.apache.org/" rel="noopener noreferrer"&gt;https://kafka.apache.org/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.confluent.io/current/schema-registry/docs/index.html" rel="noopener noreferrer"&gt;https://docs.confluent.io/current/schema-registry/docs/index.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://avro.apache.org/" rel="noopener noreferrer"&gt;https://avro.apache.org/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.postgresql.org/" rel="noopener noreferrer"&gt;https://www.postgresql.org/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.salesforce.com/" rel="noopener noreferrer"&gt;https://www.salesforce.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/google/pprof" rel="noopener noreferrer"&gt;https://github.com/google/pprof&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.docker.com/" rel="noopener noreferrer"&gt;https://www.docker.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/gorilla/mux" rel="noopener noreferrer"&gt;https://github.com/gorilla/mux&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://golang.org/src/net/http/transport.go" rel="noopener noreferrer"&gt;https://golang.org/src/net/http/transport.go&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>go</category>
      <category>http</category>
      <category>debugging</category>
      <category>pprof</category>
    </item>
  </channel>
</rss>
