The getting started guide for the kubernetes operator framework has a few issues that prevent a smooth intial experience with the SDK. This doc represents a successful effort to follow the getting started guide while incorporating fixes from pending pull requests.
!! GOPATH must be set
!! Please make sure you have a an account on Quay.io for the next step,
or substitute your preferred container registry. On the registry,
create a new public image repository named “memcached-operator”.
!! You must have a github account
- Prepare Environment
- Install the Operator SDK
- Create a Memcached Operator
- Build and Run the Memcached Operator
- Test the Deploy
- Manage the Operator with OLM
$ export GOPATH=__CHANGEME__
$ export QUAY_USERNAME=__CHANGEME__
$ export GITHUB_USERNAME=__CHANGEME__
$ go get github.com/operator-framework/operator-sdk
$ dep ensure
$ go install github.com/operator-framework/operator-sdk/commands/operator-sdk
$ cd $GOPATH/src/github.com/$GITHUB_USERNAME/
$ operator-sdk new memcached-operator --api-version=cache.example.com/v1alpha1 --kind=Memcached
$ cd memcached-operator
$ vim cmd/memcached-operator/main.go
Replace the main() func with this:
func main() {
sdk.Watch("cache.example.com/v1alpha1", "Memcached", "default", 5)
sdk.Handle(stub.NewHandler())
sdk.Run(context.TODO())
}
$ vim pkg/apis/cache/v1alpha1/types.go
Replace the MemcachedSpec and MemcachedStatus structs with this:
type MemcachedSpec struct {
// Size is the size of the memcached deployment
Size int32 `json:"size"`
}
type MemcachedStatus struct {
// Nodes are the names of the memcached pods
Nodes []string `json:"nodes"`
}
$ operator-sdk generate k8s
$ vim pkg/stub/handler.go
Replace the entire handler.go file with this:
package stub
import (
"fmt"
"reflect"
v1alpha1 "github.com/$GITHUB_USERNAME/memcached-operator/pkg/apis/cache/v1alpha1"
"github.com/operator-framework/operator-sdk/pkg/sdk/action"
"github.com/operator-framework/operator-sdk/pkg/sdk/handler"
"github.com/operator-framework/operator-sdk/pkg/sdk/query"
"github.com/operator-framework/operator-sdk/pkg/sdk/types"
appsv1 "k8s.io/api/apps/v1"
"k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
)
func NewHandler() handler.Handler {
return &Handler{}
}
type Handler struct {
}
func (h *Handler) Handle(ctx types.Context, event types.Event) error {
switch o := event.Object.(type) {
case *v1alpha1.Memcached:
memcached := o
// Ignore the delete event since the garbage collector will clean up all secondary resources for the CR
// All secondary resources must have the CR set as their OwnerReference for this to be the case
if event.Deleted {
return nil
}
// Create the deployment if it doesn't exist
dep := deploymentForMemcached(memcached)
err := action.Create(dep)
if err != nil && !apierrors.IsAlreadyExists(err) {
return fmt.Errorf("failed to create deployment: %v", err)
}
// Ensure the deployment size is the same as the spec
err = query.Get(dep)
if err != nil {
return fmt.Errorf("failed to get deployment: %v", err)
}
size := memcached.Spec.Size
if *dep.Spec.Replicas != size {
dep.Spec.Replicas = &size
err = action.Update(dep)
if err != nil {
return fmt.Errorf("failed to update deployment: %v", err)
}
}
// Update the Memcached status with the pod names
podList := podList()
labelSelector := labels.SelectorFromSet(labelsForMemcached(memcached.Name)).String()
listOps := &metav1.ListOptions{LabelSelector: labelSelector}
err = query.List(memcached.Namespace, podList, query.WithListOptions(listOps))
if err != nil {
return fmt.Errorf("failed to list pods: %v", err)
}
podNames := getPodNames(podList.Items)
if !reflect.DeepEqual(podNames, memcached.Status.Nodes) {
memcached.Status.Nodes = podNames
err := action.Update(memcached)
if err != nil {
return fmt.Errorf("failed to update memcached status: %v", err)
}
}
}
return nil
}
// deploymentForMemcached returns a memcached Deployment object
func deploymentForMemcached(m *v1alpha1.Memcached) *appsv1.Deployment {
ls := labelsForMemcached(m.Name)
replicas := m.Spec.Size
dep := &appsv1.Deployment{
TypeMeta: metav1.TypeMeta{
APIVersion: "apps/v1",
Kind: "Deployment",
},
ObjectMeta: metav1.ObjectMeta{
Name: m.Name,
Namespace: m.Namespace,
},
Spec: appsv1.DeploymentSpec{
Replicas: &replicas,
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",
}},
}},
},
},
},
}
addOwnerRefToObject(dep, asOwner(m))
return dep
}
// labelsForMemcached returns the labels for selecting the resources
// belonging to the given memcached CR name.
func labelsForMemcached(name string) map[string]string {
return map[string]string{"app": "memcached", "memcached_cr": name}
}
// addOwnerRefToObject appends the desired OwnerReference to the object
func addOwnerRefToObject(obj metav1.Object, ownerRef metav1.OwnerReference) {
obj.SetOwnerReferences(append(obj.GetOwnerReferences(), ownerRef))
}
// asOwner returns an OwnerReference set as the memcached CR
func asOwner(m *v1alpha1.Memcached) metav1.OwnerReference {
trueVar := true
return metav1.OwnerReference{
APIVersion: m.APIVersion,
Kind: m.Kind,
Name: m.Name,
UID: m.UID,
Controller: &trueVar,
}
}
// podList returns a v1.PodList object
func podList() *v1.PodList {
return &v1.PodList{
TypeMeta: metav1.TypeMeta{
Kind: "Pod",
APIVersion: "v1",
},
}
}
// getPodNames returns the pod names of the array of pods passed in
func getPodNames(pods []v1.Pod) []string {
var podNames []string
for _, pod := range pods {
podNames = append(podNames, pod.Name)
}
return podNames
}
$ operator-sdk build quay.io/$QUAY_USERNAME/memcached-operator:v0.0.1
Login to Quay (recommend generating a CLI password on www.quay.io)
$ docker login quay.io
$ docker push quay.io/$QUAY_USERNAME/memcached-operator:v0.0.1
$ kubectl create -f deploy/rbac.yaml
$ kubectl create -f deploy/crd.yaml
$ kubectl create -f deploy/operator.yaml
$ kubectl get pods
$ kubectl delete -f deploy/operator.yaml
$ kubectl delete -f deploy/rbac.yaml