Working with Kubernetes CRD model as protobuf

Anh Duong Viet
2 min readDec 2, 2022

--

Recently, Kyber’s API Gateway (aka Pilot) has had many upgraded and added features, in addition, the number of services that interact with Pilot is also gradually increasing, which makes us need to remind to converting our model definition to protobuf. This helps us to reduce the model definition many times, as well as the communication between the services that interact with the Pilot also become simpler.

When switching to protobuf to define CRDs. go-client cannot unmarshal the CRD because go-client uses enconding/json to marshal/unmarshal the CRD. After a few hours of searching, I found out that encoding/json can perform a marshal/unmarshal redirect if the struct implements the 2 functions MarshalJSON and UnmarshalJSON. That means, we can completely redirect marshal/unmarshal a protobuf message to a library that supports marshal protobuf messages.

package v1alpha1

import (
bytes "bytes"
jsonpb "github.com/golang/protobuf/jsonpb"
)

type Bar struct { } // Your CRD

func (this *Bar) MarshalJSON() ([]byte, error) {
str, err := BarMarshaler.MarshalToString(this)
return []byte(str), err
}

func (this *Bar) UnmarshalJSON(b []byte) error {
return BarUnmarshaler.Unmarshal(bytes.NewReader(b), this)
}

var (
BarMarshaler = &jsonpb.Marshaler{}
BarUnmarshaler = &jsonpb.Unmarshaler{AllowUnknownFields: true}
)

But doing this manually with dozens of structs would be a pain in the ass. Continuing to search, I discovered that Istio has also developed tools to do this. Thank you to Istio’s contributors, they saved me a lot of time.

To be able to generate code, you need to prepare the following tools:

  • buf (https://buf.build/)
  • $ go install istio.io/tools/cmd/protoc-gen-golang-deepcopy
  • $ go install istio.io/tools/cmd/protoc-gen-golang-jsonshim
# prepare buf.yaml
cat > buf.yaml << EOF
version: v1
lint:
use:
- BASIC
except:
- FIELD_LOWER_SNAKE_CASE
- PACKAGE_DIRECTORY_MATCH
allow_comment_ignores: true
EOF

# prepare buf.gen.yaml
cat > buf.yaml << EOF
version: v1
plugins:
- name: go
out: .
opt: paths=source_relative
- name: go-grpc
out: .
opt: paths=source_relative
- name: golang-deepcopy
out: .
opt: paths=source_relative
- name: golang-jsonshim
out: .
opt: paths=source_relative
EOF

# prepare a protobuf
cat > ./api/foo/v1alpha1/bar.proto << EOF
syntax = "proto3";

package foo.v1alpha1;

option go_package = "github.com/vietanhduong/xcontroller/api/foo/v1alpha1";

message Bar {
int32 replicas = 1;
string image = 2;
string container_name = 3;
map<string,string> annotations = 4;
}
EOF

# generate code

buf generate --path api

That’s all you need to do. I also made a repository which is a full controller, and base on the concept of kubernetes/sample-controller, but more details.

Source: https://github.com/vietanhduong/xcontroller

Hope this article will help you!

--

--

Anh Duong Viet

I’m a Software / DevOps engineer. My main is focus on maintain and maintain and ensure the stability of the infrastructure.