Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
215 changes: 193 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,70 +10,241 @@

Operator SDK comes with a number of code generators that are designed to facilitate development lifecycle. It helps create the project scaffolding, preprocess custom resource API to generate Kubernetes related code, generate deployment scripts -- just everything that is necessary to build an operator.

Navigate to `$GOPATH/src/github.com/example.com/`.
Navigate to `$GOPATH/src/github.com/example-inc/`.

To start a project, we use the `new` generator to provide the foundation of a fresh operator project. Run the following command:

```
operator-sdk new play-operator
operator-sdk new memcached-operator cache.example.com/v1alpha1 Memcached
```

This will create the `play-operator` project with scaffolding with dependency code ready. It generates Kubernetes custom resource API of APIGroup `play.example.com` and Kind `PlayService` by default. APIGroups and Kinds can be overridden and added by flags.
This will generate a project repo `memcached-operator`, a custom resource with APIGroup `cache.example.com/v1apha1` and Kind `Memcached`, and an example operator that watches all deployments in the same namespace and logs deployment names.

Navigate to the project root folder:
Navigate to the project folder:

```
cd play-operator
cd memcached-operator
```

More details about the structure of the project can be found in [this doc][scaffold_doc].

## Up and running

At this point we are ready to build and deploy a functional operator. First build the binary and container:
At this step we actually have a functional operator already. To see it, first build the binary and container:

```
operator-sdk build $image
docker push $image
```

Kubernetes deployment manifests will be generated in `deploy/play-operator/operator.yaml`. Deploy play-operator:
Kubernetes deployment manifests will be generated in `deploy/memcached-operator.yaml`. The `$image` parameter value will be used and set in the manifest.

Deploy memcached-operator:

```
kubectl create -f deploy/play-operator/operator.yaml
kubectl create -f deploy/memcached-operator.yaml
```

The play-operator would be up and running:
The memcached-operator would be up and running:

```
# kubectl get deploy
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
play-operator 1 1 1 1 1m
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
memcached-operator 1 1 1 1 1m
```

A `PlayService` CR with the following spec can be created to demonstrate the use of operator:
Check memcached-operator pod’s log:

```
apiVersion: "play.coreos.com/v1"
kind: "PlayService"
metadata:
name: "example"
spec:
replica: 1
# kubectl get pod | grep memcached-operator | cut -d' ' -f1 | xargs kubectl logs
Found Deployment `memcached-operator`
```

Once the CR is created, a new pod `example-box` will be created:
Clean up resources:

```
# kubectl get pod
NAME READY STATUS RESTARTS AGE
example-box 2/2 Running 0 1m
kubectl delete -f deploy/memcached-operator.yaml
```

This is a basic test that verifies everything works correctly. Next we are going to write the business logic and do something more interesting.


## Customizing operator logic

An operator is used to extend the kube-API and codify application domain knowledge. Operator SDK is designed to provide non-Kubernetes developers an easy way to write the business logic.

In the following steps we are adding a custom resource `Memcached`, and customizing the operator logic that creating a new Memcached CR will create a Memcached Deployment and (optional) Service.

In `pkg/apis/cache/v1alpha1/types.go`, add to `MemcachedSpec` a new field `WithService`:

```Go
type MemcachedSpec struct {
WithService bool `json:"withService"`
}
```

Re-render the generated code for custom resource:

```
operator-sdk generate k8s
```

In `main.go`, change to watch Memcached CR:

```Go
func main() {
sdk.Watch(“memcacheds”, namespace, &api.Memcached{}, restcli)
sdk.Handle(stub.NewHandler())
sdk.Run(context.TODO())
}
```

In `pkg/stub/handler.go`, change `Handle()` logic:

```Go
import (
appsv1beta1 "k8s.io/api/apps/v1beta1"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

func (h *Handler) Handle(ctx Context, ev Event) []Action {
var actions []Action
switch obj := ev.Object.(type) {
case *Memcached:
ls := map[string]string{
"app": "memcached",
"name": obj.Name,
}

d := &appsv1beta1.Deployment{
TypeMeta: metav1.TypeMeta{
APIVersion: "apps/v1",
Kind: "Deployment",
},
ObjectMeta: metav1.ObjectMeta{
Name: obj.Name,
},
Spec: appsv1beta1.DeploymentSpec{
Selector: &metav1.LabelSelector{
MatchLabels: ls,
},
Template: v1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: ls,
},
Spec: v1.PodSpec{
Containers: []v1.Container{{
Image: "memcached:1.4.36-alpine",
Name: "memcached",
Command: []string{"memcached", "-m=64", "-o", "modern", "-v"},
Ports: []v1.ContainerPort{{
ContainerPort: 11211,
Name: "memcached",
}},
}},
},
},
},
}
actions = append(actions, Action{
Object: d,
Func: KubeApplyFunc,
})

if !obj.Spec.WithService {
break
}

svc := &v1.Service{
TypeMeta: metav1.TypeMeta{
APIVersion: "v1",
Kind: "Service",
},
ObjectMeta: metav1.ObjectMeta{
Name: obj.Name,
},
Spec: v1.ServiceSpec{
Selector: ls,
Ports: []v1.ServicePort{{
Port: 11211,
Name: "memcached",
}},
},
}
actions = append(actions, Action{
Object: svc,
Func: KubeApplyFunc,
})
}
return actions
}
```

Rebuild the container:

```
operator-sdk build $image
docker push $image
```

Deploy operator:
```
kubectl create -f deploy/memcached-operator.yaml
```

Create a `Memcached` CR with the following spec:

```
apiVersion: "cache.example.com/v1alpha1"
kind: "Memcached"
metadata:
name: "example"
spec:
withService: true
```

There will be a new Memcached Deployment:
```
# kubectl get deploy
example
```

There will be a new Memcached Service:
```
# kubectl get service
example
```

We can test the Memcached service by opening a telnet session and running commands via [Memcached protocols][mc_protocol]:

1. Open a telnet session in another container in order to talk to the service:
```
kubectl run -it --rm busybox --image=busybox --restart=Never -- telnet example 11211
```
2. In the telnet prompt, enter the following command to set a key:
```
set foo 0 0 5
bar
```
3. Enter the following command to get the key:
```
get foo
```
It should output:
```
VALUE foo 0 5
bar
```

Now we have successfully customized the event handling logic to deploy Memcached service for us.

Clean up resources:

```
kubectl delete memcached example
kubectl delete -f deploy/memcached-operator.yaml
```

[scaffold_doc]:./doc/project_layout.md
[mc_protocol]:https://github.com/memcached/memcached/blob/master/doc/protocol.txt