In an attempt to learn more about kubernetes controllers & resources, I decided to make a Custom Resource and a kubernetes custom controller to manage it.
Here are some of my notes:
What does a kubernetes controller do?
A controller is a loop, that compares the current state and the desired state of a resource, and attempts to bring the current state closer to the desired state
For example:
A ReplicaSet resource is managed by a replica_set controller, when the number of pods running is less than the desired number of replicas, the replica_set controller will create new pods to match the desired number of replicas
You can find the replica set code here
What this project should do
A simple custom resource with
kind: Envoy
and a controller to handle this custom resourceWhen created, this controller should deploy and manage a fleet of envoy proxy pods
It should generate a bootstrap envoy config, and mount it as configmap on these pods
It should allow setting an XDS server to allow dynamically configuring the envoy proxy pods using XDS protocol
Building the controller
I experimented with a few frameworks that help in building controllers, like Kubebuilder & Operator SDK, but decided against using them here.
First, I created a type, for this resource and generated the clientsets,
this is how a resource ofkind: Envoy
would look:1
apiVersion: example.com/v1
2
kind: Envoy
3
metadata:
4
name: edge-envoy
5
spec:
6
name: "webserver-beta"
7
configMapName: "envoy-cfg-1"
8
replicas: 3
9
xds:
10
name: "xds_cluster"
11
host: "xds-service.default"
12
port: 19000
Project structure
1
|
2
├── crds
3
│ └── envoy-crd.yaml
4
├── go.mod
5
├── go.sum
6
├── LICENSE
7
├── main.go
8
├── pkg
9
│ ├── api
10
│ │ └── example.com
11
│ │ └── v1
12
│ │ ├── doc.go
13
│ │ ├── register.go
14
│ │ ├── types.go
15
│ │ └── zz_generated.deepcopy.go
16
│ ├── client
17
│ │ ├── clientset
18
│ │ │ └── versioned
19
│ │ │ ├── clientset.go
20
│ │ │ ├── doc.go
21
│ │ │ ├── fake
22
│ │ │ ├── scheme
23
│ │ │ └── typed
24
│ │ ├── informers
25
│ │ │ └── externalversions
26
│ │ │ ├── example.com
27
│ │ │ ├── factory.go
28
│ │ │ ├── generic.go
29
│ │ │ └── internalinterfaces
30
│ │ └── listers
31
│ │ └── example.com
32
│ │ └── v1
33
│ └── envoy
34
│ ├── bootstrap.go
35
│ └── utils.go
36
├── README.md
37
└── sample
38
└── envoy.yaml
A queue is created using
workqueue
from theclient-go
library, this queue has rate limiting and exponential backoffI then create a SharedInformer for the envoys resource:
1
sharedFactory = factory.NewSharedInformerFactory(clientset, time.Second*30)
2
informer := sharedFactory.Example().V1().Envoys().Informer()
The informer contains a in-memory cache, a listerWatcher (functions that can list your custom resources, and watch the custom resource)
It also has a bunch of event handlers, that are called when a specific action(Add, Update, Delete) occurs on that resource
The informer periodically lists/watches for changed on the custom resource, and stores that info in its cache.
A
SharedInformer
is an informer where the underlying cache, is shared among controllers.The event handlers for this informer are registered. On
Add
orUpdate
, the key of the resource is enqueued to our workqueueI then start the main controller loop:
1
func work() {
2
for {
3
key, shutdown := queue.Get()
4
if shutdown {
5
stopCh <- struct{}{}
6
return
7
}
8
var strKey string
9
var ok bool
10
if strKey, ok = key.(string); !ok {
11
log.Printf("\n Invalid key format %v", key)
12
return
13
}
14
processItem(strKey)
15
}
16
}
This (very simple) function dequeues a key, and calls the
processItem
The
processItem
retrieves the resource object using the key, and calls thereconcile
function.
It also tells the queue, to forget about this key (and not retry)This
reconcile
function then deploys or updates the envoyproxy pods, services & configmap, based on this retrieved resource object.The final state after reconciling is updated in the
status
field of theenvoy
resource
Testing it out
Installing
1 | $ go get github.com/starizard/kube-envoy-controller |
2 | $ go build |
Start the controller:
1 | $ ./kube-envoy-controller |
In a separate shell:
The source for this project can be found on github
Future enhancements
- Sidecar Injection (inject an envoy sidecar in every pod)
- Implement XDS component
- Ship envoy access log & expose prometheus metrics