TechTalk: A Quick Intro to Golang Sidecars in Kubernetes

They're pretty nifty.


Authored by Patrick Webster, Sr. DevOps Engineer

"TechTalk" is a technical blog series written by the engineers at Help.com. The series explores the ins and outs of different programming languages, how to overcome common (and niche) obstacles, infrastructure, new technology and features, and everything in between. In this post, Senior DevOps Engineer Pat Webster gives the low-down on sidecars in Kubernetes. 

The other day I was working on a project that ultimately touches all of the inbound traffic to our core infrastructure. I found myself in an interesting position because I am not a backend developer. I had no idea what all of our requests generally look like, let alone how they may vary. Obviously I could have tried to pester and pry a few of the backend engineers, but everyone here at Help.com is pretty busy! Besides, it seemed far more fun to play with one of my favorite features of Kubernetes: sidecars!

At the most basic level, a sidecar is a second (or third, or fourth) container that sits inside a pod with a main service. Since they share the same network and cgroup namespace, there is a lot one can do with them. What I needed to do was incredibly simple, however, so let's start with an example service handling http traffic:

package main

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

func main() {
  p := os.Getenv("PORT")
  if p == "" {
    log.Fatal("PORT env var is missing.")
  }

  http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello!")
  })

  http.ListenAndServe(fmt.Sprintf(":%v", p), nil)
}

And of course the service / deployment manifest:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: http-server
spec:
  selector:
    matchLabels:
      app: http-server
  replicas: 1
  template:
    metadata:
      labels:
        app: http-server
    spec:
      containers:
      - name: http-server
        image: gcr.io/public-examples/example-k8s-sidecar:http-server
        ports:
        - containerPort: 8080
        env:
        - name: PORT
          value: "8080"
---
apiVersion: v1
kind: Service
metadata:
  name: http-server
  labels:
    app: http-server
spec:
  ports:
    - port: 8080
      name: http
      protocol: TCP
  selector:
    app: http-server
  type: LoadBalancer

 

There's nothing more needed--just a simple web server that takes a PORT environment variable to start up. Something interesting to note is that there aren't any viable logs that this will return, unless startup fails or the program ultimately panics and the container restarts. So how do we intercept the http traffic to inspect it? We need a reverse proxy. In GO, this is incredibly simple and part of the core `httputil` package via httputil.NewSingleHostReverseProxy():

package main

import (
  "fmt"
  "log"
  "net/http"
  "net/http/httputil"
  "net/url"
  "os"
)

func main() {
  fp := os.Getenv("FE_PORT")
  if fp == "" {
    log.Fatal("FE_PORT env var is missing.")
  }

  bp := os.Getenv("BE_PORT")
  if bp == "" {
    log.Fatal("BE_PORT env var is missing.")
  }

  url, err := url.Parse("http://localhost:" + bp)
  if err != nil {
    log.Fatalf("Error parsing backend url: %v", err)
  }

  proxy := httputil.NewSingleHostReverseProxy(url)
  http.HandleFunc("/", func(rw http.ResponseWriter, req *http.Request) {
    log.Printf("request dump: %v", req)
    proxy.ServeHTTP(rw, req)
  })

  http.ListenAndServe(fmt.Sprintf(":%v", fp), nil)
}

 

When we add the above container as a sidecar, the sidecar takes the ingress port the service was originally using (8080), so we need to change the service to use a different port. It can be any of them, but we'll use port 8000. Our deployment now looks like this:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: http-server
spec:
  selector:
    matchLabels:
      app: http-server
  replicas: 1
  template:
    metadata:
      labels:
        app: http-server
    spec:
      containers:
      - name: http-server
        image: gcr.io/public-examples/example-k8s-sidecar:http-server
        ports:
        - containerPort: 8000
        env:
        - name: PORT
          value: "8000"
      - name: http-sidecar
        image: gcr.io/public-examples/example-k8s-sidecar:http-sidecar
        ports:
        - containerPort: 8080
        env:
        - name: FE_PORT
          value: "8080"
        - name: BE_PORT
          value: "8000"
---
apiVersion: v1
kind: Service
metadata:
  name: http-server
  labels:
    app: http-server
spec:
  ports:
    - port: 8080
      name: http
      protocol: TCP
  selector:
    app: http-server

 

With all of this in place, our sidecar now dumps out the logs so we can analyze:

$ kubectl logs http-server-6f4597f9bf-tvkqm http-sidecar -f
2019/02/14 19:33:07 request dump: &{GET / HTTP/1.1 1 1 map[Connection:[keep-alive] Upgrade-Insecure-Requests:[1] User-Agent:[Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.96 Safari/537.36] Accept:[text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8] Accept-Encoding:[gzip, deflate] Accept-Language:[en-US,en;q=0.9]] {} <nil> 0 [] false 192.168.1.7:30034 map[] map[] <nil> map[] 10.8.9.1:59760 / <nil> <nil> <nil> 0xc0000a22c0}
2019/02/14 19:33:07 request dump: &{GET /favicon.ico HTTP/1.1 1 1 map[Referer:[http://192.168.1.7:30034/] Accept-Encoding:[gzip, deflate] Accept-Language:[en-US,en;q=0.9] Connection:[keep-alive] User-Agent:[Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.96 Safari/537.36] Accept:[image/webp,image/apng,image/*,*/*;q=0.8]] {} <nil> 0 [] false 104.155.139.7:30034 map[] map[] <nil> map[] 10.8.9.1:59760 /favicon.ico <nil> <nil> <nil> 0xc00004c300}

 

That's all there is to it! Obviously this is just the tip of the iceberg with what you can do with sidecars, but hopefully this serves as an entry point to anyone who might be completely lost as to how this works. Feel free to leave comments below, and view the entire source on our Github page!

 





Leave a Reply

Your email address will not be published.


Comment


Name

Email

Url