Skip to content
This repository was archived by the owner on May 14, 2025. It is now read-only.

Commit 3293ede

Browse files
author
Amit Kumar Das
authored
chore(examples): add genericcontroller based helloworld controller (#118)
Signed-off-by: AmitKumarDas <amit.das@mayadata.io>
1 parent 5bb6711 commit 3293ede

File tree

14 files changed

+932
-0
lines changed

14 files changed

+932
-0
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/vendor/
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# --------------------------
2+
# Build d-operators binary
3+
# --------------------------
4+
FROM golang:1.13.5 as builder
5+
6+
WORKDIR /example.io/hello-world/
7+
8+
# copy go modules manifests
9+
COPY go.mod go.mod
10+
COPY go.sum go.sum
11+
12+
COPY Makefile Makefile
13+
14+
COPY vendor/ vendor/
15+
16+
# ensure vendoring is up-to-date by running make vendor
17+
# in your local setup
18+
#
19+
# we cache the vendored dependencies before building and
20+
# copying source so that we don't need to re-download when
21+
# source changes don't invalidate our downloaded layer
22+
RUN go mod download
23+
RUN go mod tidy
24+
RUN go mod vendor
25+
26+
# copy source file(s)
27+
COPY cmd/ cmd/
28+
29+
# build the binary
30+
RUN make
31+
32+
# ---------------------------
33+
# Use distroless as minimal base image to package the final binary
34+
# Refer https://github.com/GoogleContainerTools/distroless
35+
# ---------------------------
36+
FROM gcr.io/distroless/static:nonroot
37+
38+
WORKDIR /
39+
40+
COPY --from=builder /example.io/hello-world/hello-world .
41+
COPY config/config.yaml /etc/config/metac/
42+
43+
USER nonroot:nonroot
44+
45+
CMD ["/hello-world"]

examples/gctl/hello-world/Makefile

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
ALL_SRC = $(shell find . -name "*.go" | grep -v -e "vendor")
2+
3+
IMG_NAME ?= hello-world
4+
PACKAGE_VERSION ?= latest
5+
6+
all: bins
7+
8+
bins: vendor $(IMG_NAME)
9+
10+
$(IMG_NAME): $(ALL_SRC)
11+
@echo "+ Generating $(IMG_NAME) binary"
12+
@CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GO111MODULE=on \
13+
go build -o $@ ./cmd/main.go
14+
15+
$(ALL_SRC): ;
16+
17+
# go mod download modules to local cache
18+
# make vendored copy of dependencies
19+
# install other go binaries for code generation
20+
.PHONY: vendor
21+
vendor: go.mod go.sum
22+
@GO111MODULE=on go mod download
23+
@GO111MODULE=on go mod tidy
24+
@GO111MODULE=on go mod vendor
25+
26+
.PHONY: image
27+
image:
28+
docker build -t $(IMG_NAME):$(PACKAGE_VERSION) .
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
## Hello World K8s controller
2+
3+
- This is a K8s controller that imports metac as library
4+
- In other words, this uses inline hooks than webhooks
5+
- Controller's K8s dependencies is configured in a config file
6+
- Refer: config/config.yaml
7+
- Controller business logic is implemented in Go
8+
- Refer: cmd/main.go
9+
- K8s is completely abstracted from this logic
10+
- Logic is implemented in respective reconcile functions
11+
- A Pod gets created via sync inline hook
12+
- This Pod gets deleted via finalize inline hook
13+
- Docker image includes the binary as well as its config file
14+
- Refer: Dockerfile
15+
- Controller is deployed as a single StatefulSet
16+
- No need of separate metac binary since metac is used as a library
17+
- Refer: helloworld-operator.yaml
18+
19+
### Steps
20+
21+
```sh
22+
# workstation needs to have Docker
23+
# use kind to create a k8s cluster
24+
#
25+
# Refer: https://kind.sigs.k8s.io/docs/user/local-registry/
26+
sudo ./kind-with-registry.sh
27+
28+
# cat $HOME/.kube/config
29+
# connect to kind cluster
30+
sudo kubectl cluster-info --context kind-kind
31+
32+
# debugging info if required
33+
#
34+
# Kubernetes master is running at https://127.0.0.1:32774
35+
#
36+
# KubeDNS is running at:
37+
# https://127.0.0.1:32774/api/v1/namespaces/# kube-system/services/kube-dns:dns/proxy
38+
#
39+
# To further debug and diagnose cluster problems, use
40+
#'kubectl cluster-info dump'.
41+
```
42+
43+
```sh
44+
# workstation needs to have Docker
45+
make image
46+
47+
# tag the image to use the local registry
48+
sudo docker tag hello-world:latest localhost:5000/hello-world:latest
49+
50+
# push to local registry configured to be used by kind
51+
sudo docker push localhost:5000/hello-world:latest
52+
```
53+
54+
```sh
55+
# install namespace, rbac, crds & operator
56+
sudo kubectl apply -f helloworld-ns.yaml
57+
sudo kubectl apply -f helloworld-rbac-crd.yaml
58+
sudo kubectl apply -f helloworld-operator.yaml
59+
60+
# verify if above were installed properly
61+
sudo kubectl get ns
62+
sudo kubectl get crd
63+
sudo kubectl get sts -n hello-world
64+
sudo kubectl describe po -n hello-world
65+
sudo kubectl get po -n hello-world
66+
sudo kubectl logs -n hello-world hello-world-0
67+
```
68+
69+
### Test
70+
71+
```sh
72+
# create the helloworld custom resource
73+
sudo kubectl apply -f helloworld.yaml
74+
75+
# verify creation of Pod
76+
sudo kubectl get pods -n hello-world
77+
78+
# delete helloworld custom resource
79+
sudo kubectl delete -f helloworld.yaml
80+
81+
# verify deletion of Pod
82+
sudo kubectl get pods -n hello-world
83+
84+
# verify deletion of helloworld
85+
sudo kubectl get helloworlds -n hello-world
86+
```
87+
88+
### Cleanup
89+
90+
```sh
91+
sudo kubectl delete -f helloworld-operator.yaml
92+
sudo kubectl delete -f helloworld-rbac-crd.yaml
93+
sudo kubectl delete -f helloworld-ns.yaml
94+
sudo kind delete cluster
95+
```
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/golang/glog"
7+
"github.com/pkg/errors"
8+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
9+
"openebs.io/metac/controller/generic"
10+
"openebs.io/metac/start"
11+
)
12+
13+
// sync executes the reconciliation logic for the watched resource.
14+
// In this case resource under watch is a custom resource with kind
15+
// Helloworld.
16+
//
17+
// NOTE:
18+
// SyncHookRequest is the payload received as part of reconcile
19+
// request. Similarly, SyncHookResponse is the payload sent as a
20+
// response as part of reconcile request.
21+
//
22+
// NOTE:
23+
// SyncHookRequest has the resource that is under watch (here
24+
// Helloworld custom resource)
25+
//
26+
// NOTE:
27+
// Both SyncHookRequest & SyncHookResponse have the resources that
28+
// form the desired state w.r.t the watched resource. These desired
29+
// resources are termed as attachments by metac. SyncHookRequest has
30+
// these attachments filled up by metac based on what is observed
31+
// in the k8s cluster. SyncHookResponse's attachments needs to be
32+
// filled up with what is desired by this controller logic.
33+
func sync(
34+
request *generic.SyncHookRequest,
35+
response *generic.SyncHookResponse,
36+
) error {
37+
glog.Infof("Starting hello world sync")
38+
defer glog.Infof("Completed hello world sync")
39+
40+
// extract spec.who from Helloworld
41+
who, found, err := unstructured.NestedString(
42+
request.Watch.UnstructuredContent(),
43+
"spec",
44+
"who",
45+
)
46+
if err != nil {
47+
return err
48+
}
49+
if !found {
50+
return errors.Errorf("Can't sync: spec.who is not set")
51+
}
52+
// build the desired Pod
53+
desired := &unstructured.Unstructured{
54+
Object: map[string]interface{}{
55+
"kind": "Pod",
56+
"apiVersion": "v1",
57+
"metadata": map[string]interface{}{
58+
"name": request.Watch.GetName(),
59+
"namespace": request.Watch.GetNamespace(),
60+
"annotations": map[string]interface{}{
61+
// this annotation helps in associating the watch
62+
// with its corresponding attachment
63+
"helloworld/uid": string(request.Watch.GetUID()),
64+
},
65+
},
66+
"spec": map[string]interface{}{
67+
"restartPolicy": "OnFailure",
68+
"containers": []interface{}{
69+
map[string]interface{}{
70+
"name": "hello",
71+
"image": "busybox",
72+
"command": []interface{}{
73+
"echo",
74+
fmt.Sprintf("Hello, %s", who),
75+
},
76+
},
77+
},
78+
},
79+
},
80+
}
81+
// add this desired Pod instance to response
82+
// to let metac create this in the k8s cluster
83+
response.Attachments = append(
84+
response.Attachments,
85+
desired,
86+
)
87+
return nil
88+
}
89+
90+
// finalize will delete the attachment(s) created during
91+
// reconciliation of the watch
92+
//
93+
// NOTE:
94+
// Presence of finalize in the config automatically adds
95+
// finalizers against the watch
96+
//
97+
// NOTE:
98+
// Once attachments are deleted from the cluster,
99+
// 'response.Finalized' is set to true which in turn removes
100+
// this finalizers from the watch
101+
func finalize(
102+
request *generic.SyncHookRequest,
103+
response *generic.SyncHookResponse,
104+
) error {
105+
glog.Infof("Starting hello world finalize")
106+
defer glog.Infof("Completed hello world finalize")
107+
108+
if request.Attachments.IsEmpty() {
109+
response.Finalized = true
110+
}
111+
return nil
112+
}
113+
114+
func main() {
115+
generic.AddToInlineRegistry("sync/helloworld", sync)
116+
generic.AddToInlineRegistry("finalize/helloworld", finalize)
117+
start.Start()
118+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
apiVersion: metac.openebs.io/v1alpha1
2+
kind: GenericController
3+
metadata:
4+
name: hello-world-controller
5+
spec:
6+
watch:
7+
apiVersion: example.com/v1
8+
resource: helloworlds
9+
attachments:
10+
- apiVersion: v1
11+
resource: pods
12+
updateStrategy:
13+
method: Recreate
14+
advancedSelector:
15+
selectorTerms:
16+
- matchReferenceExpressions:
17+
# select Pod if its annotation
18+
#
19+
# matches HelloWorld _(i.e. watch)_ UID
20+
- key: metadata.annotations.helloworld/uid
21+
operator: EqualsWatchUID
22+
hooks:
23+
sync:
24+
inline:
25+
funcName: sync/helloworld
26+
finalize:
27+
inline:
28+
funcName: finalize/helloworld

examples/gctl/hello-world/go.mod

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
module example.io/hello-world
2+
3+
go 1.13
4+
5+
require (
6+
github.com/coreos/etcd v3.3.15+incompatible // indirect
7+
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b
8+
github.com/pkg/errors v0.9.1
9+
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1 // indirect
10+
k8s.io/apimachinery v0.17.3
11+
k8s.io/client-go v0.17.3 // indirect
12+
openebs.io/metac v0.2.1
13+
)
14+
15+
replace (
16+
k8s.io/apimachinery => k8s.io/apimachinery v0.17.3
17+
k8s.io/client-go => k8s.io/client-go v0.17.3
18+
openebs.io/metac => github.com/AmitKumarDas/metac v0.2.1
19+
)

0 commit comments

Comments
 (0)