Featured image for Golang operators.

You can use Kubernetes Operators to set up automatic resource management for services in your applications. In a previous article, I described this pattern and how to create an operator in the Go language. In this article, we will continue to explore the pattern, this time by creating a Kubernetes Operator in Go to keep a WordPress site up to date. I recommend reading or reviewing the earlier article before starting this one.

Example prerequisites

I use the following tools in this article's example:

In addition, set this environment variable in your terminal:

$ export GO111MODULE="on"

You can see the source code for this article on my GitHub repository.

Set up the environment

Before coding our automation, we need to set up the environment.

Enter the following command to generate boilerplate code that accomplishes much of the setup:

$ operator-sdk init --domain example.com --repo github.com/<git user name>/wordpress-operator

Creating the API and controller

Enter the following to set up the environment for WordPress:

$ operator-sdk create api --group wordpress --version v1 --kind Wordpress --resource --controller

The command creates the following files:

api/v1/wordpress_types.go
controllers/wordpress_controller.go

Defining the API

You can make changes to api/v1/wordpress_types.go to include the attributes that you would like in the WordpressSpec struct and WordpressStatus status.

For the sake of simplicity, we will set only one requirement. It places a root password for database access in the WordpressSpec struct:

apiVersion: wordpress.example.com/v1
kind: Wordpress
metadata:
  name: mysite
spec:
  sqlRootPassword: plaintextpassword

After you make the change in api/v1/wordpress_types.go, run the following commands to ensure that the changes are reflected in the. custom resource definitions (CRDs) in the config directory:

$ make generate
$ make manifests

Implement the controller

Now we can enter the Go code of the example to configure our controller. The WordPress controller struct is named WordpressReconciler in the current operator-sdk. In earlier versions, it was named ReconcileWordpress.

Watching resources

The boiler-plate code generated automatically creates a watch for the primary resource in the controllers/wordpress_controller.go file, in the SetupWithmanager function:

For(&v1.Wordpress{})

The preceding line is equivalent to the following code in previous versions of the operator-sdk:

Watch(&source.Kind{Type: &v1.Wordpress{}}, &handler.EnqueueRequestForObject{})

The additional resources we watch are the service, the deployment, and the persistent volume claim (PVC):

Owns(&appsv1.Deployment{}).
Owns(&corev1.Service{}).
Owns(&corev1.PersistentVolumeClaim{})

The preceding code is equivalent to the following code in previous versions of the operator-sdk:

Watch(&source.Kind{Type: &appsv1.Deployment{}}, &handler.EnqueueRequestForOwner{
      IsController: True,
      OwnerType: &v1.Wordpress{},
})
Watch(&source.Kind{Type: &corev1.Service{}}, &handler.EnqueueRequestForOwner{
    IsController: true,
    OwnerType: &v1.Wordpress{},
})
Watch(&source.Kind{Type: &corev1.PersistentVolumeClaim{}}, &handler.EnqueueRequestForOwner{
    IsController: true,
    OwnerType: &v1.Wordpress{},
})

To sum up, all the watches in the application can be written as:

For(&v1.Wordpress{}).
Owns(&appsv1.Deployment{}).
Owns(&corev1.Service{}).
Owns(&corev1.PersistentVolumeClaim{}).
Complete(r)

Note: To learn more about establishing watches in the operator-sdk, refer to Builder docs and Controller docs.

The reconcile loop

I introduced the reconcile logic in my previous article. The new version of the operator-sdk adds a few enhancements. It defines a few role-based access control (RBAC) rules by default:

//+kubebuilder:rbac:groups=wordpress.example.com,resources=wordpresses,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=wordpress.example.com,resources=wordpresses/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=wordpress.example.com,resources=wordpresses/finalizers,verbs=update

These are generated in the configuration directory when you run the make manifests command.

Logging

The current operator-sdk already defines a logger in the WordpressReconciler struct. As a result, you don’t need to explicitly define your own logger.

Adding the WordPress controller to the manager

The current operator-sdk adds the controller in main.go:

if err = (&controllers.WordpressReconciler{
            Client: mgr.GetClient(),
            Log: ctrl.Log.Withname(controllers”).Withname(“wordpress”),
            Scheme: mgr.GetScheme(),
          }).SetupWithManager(mgr); err!=nil{
                // log error message
                   os.Exit(1)
         }

Note that this code is equivalent to the following code in previous versions of the operator-sdk:

 if err := controllers.AddToManager(mgr); err != nil {
                log.Error(err, "")
                 os.Exit(1)
}

Run the Kubernetes Operator

You need a Kubernetes cluster for this part of the example. Enter the following command :

$ make install run

Open another terminal window and apply the custom resource for WordPress:

$ kubectl create -f config/samples/wordpress_v1_wordpress.yaml

Your config/samples/wordpress_v1_wordpress.yaml must look something like this:

apiVersion: wordpress.example.com/v1
kind: Wordpress
metadata:
  name: mysite
spec:
  sqlrootpassword: "abc"

Output from make install run should be similar to the following:

/home/pjiandan/go/src/wordpress-operator/bin/controller-gen "crd:trivialVersions=true,preserveUnknownFields=false" rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases
/home/pjiandan/go/src/wordpress-operator/bin/kustomize build config/crd | kubectl apply -f -
customresourcedefinition.apiextensions.k8s.io/wordpresses.wordpress.example.com created
/home/pjiandan/go/src/wordpress-operator/bin/controller-gen "crd:trivialVersions=true,preserveUnknownFields=false" rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases
/home/pjiandan/go/src/wordpress-operator/bin/controller-gen object:headerFile="hack/boilerplate.go.txt" paths="./..."
go fmt ./...
go vet ./...
go run ./main.go
2021-07-05T14:15:59.495+0530    INFO    controller-runtime.metrics      metrics server is starting to listen    {"addr": ":8080"}
2021-07-05T14:15:59.496+0530    INFO    setup   starting manager
2021-07-05T14:15:59.497+0530    INFO    controller-runtime.manager      starting metrics server {"path": "/metrics"}
2021-07-05T14:15:59.497+0530    INFO    controller-runtime.manager.controller.wordpress Starting EventSource    {"reconciler group": "wordpress.example.com", "reconciler kind": "Wordpress", "source": "kind source: /, Kind="}
2021-07-05T14:15:59.598+0530    INFO    controller-runtime.manager.controller.wordpress Starting EventSource    {"reconciler group": "wordpress.example.com", "reconciler kind": "Wordpress", "source": "kind source: /, Kind="}
2021-07-05T14:15:59.698+0530    INFO    controller-runtime.manager.controller.wordpress Starting EventSource    {"reconciler group": "wordpress.example.com", "reconciler kind": "Wordpress", "source": "kind source: /, Kind="}
2021-07-05T14:15:59.799+0530    INFO    controller-runtime.manager.controller.wordpress Starting EventSource    {"reconciler group": "wordpress.example.com", "reconciler kind": "Wordpress", "source": "kind source: /, Kind="}
2021-07-05T14:15:59.900+0530    INFO    controller-runtime.manager.controller.wordpress Starting Controller     {"reconciler group": "wordpress.example.com", "reconciler kind": "Wordpress"}
2021-07-05T14:15:59.900+0530    INFO    controller-runtime.manager.controller.wordpress Starting workers        {"reconciler group": "wordpress.example.com", "reconciler kind": "Wordpress", "worker count": 1}
2021-07-05T14:15:59.900+0530    INFO    controllers.Wordpress   Reconciling Wordpress
2021-07-05T14:15:59.900+0530    INFO    controllers.Wordpress   Creating a new PVC      {"PVC.Namespace": "default", "PVC.Name": "mysql-pv-claim"}
2021-07-05T14:15:59.947+0530    INFO    controllers.Wordpress   Creating a new Deployment  {"Deployment.Namespace": "default", "Deployment.Name": "wordpress-mysql"}
2021-07-05T14:15:59.969+0530    INFO    controllers.Wordpress   Creating a new Service  {"Service.Namespace": "default", "Service.Name": "wordpress-mysql"}
2021-07-05T14:15:59.977+0530    INFO    controllers.Wordpress   MySQL isn't running, waiting for 5s
2021-07-05T14:15:59.978+0530    INFO    controllers.Wordpress   Reconciling Wordpress
2021-07-05T14:15:59.978+0530    INFO    controllers.Wordpress   MySQL isn't running, waiting for 5s
2021-07-05T14:15:59.998+0530    INFO    controllers.Wordpress   Reconciling Wordpress
2021-07-05T14:15:59.998+0530    INFO    controllers.Wordpress   MySQL isn't running, waiting for 5s
2021-07-05T14:16:00.044+0530    INFO    controllers.Wordpress   Reconciling Wordpress
2021-07-05T14:16:00.044+0530    INFO    controllers.Wordpress   MySQL isn't running, waiting for 5s
2021-07-05T14:16:00.058+0530    INFO    controllers.Wordpress   Reconciling Wordpress
2021-07-05T14:16:00.059+0530    INFO    controllers.Wordpress   MySQL isn't running, waiting for 5s
2021-07-05T14:17:09.986+0530    INFO    controllers.Wordpress   Reconciling Wordpress
2021-07-05T14:17:09.987+0530    INFO    controllers.Wordpress   MySQL isn't running, waiting for 5s
2021-07-05T14:17:24.988+0530    INFO    controllers.Wordpress   Reconciling Wordpress
2021-07-05T14:17:24.988+0530    INFO    controllers.Wordpress   MySQL isn't running, waiting for 5s
2021-07-05T14:18:09.993+0530    INFO    controllers.Wordpress   Reconciling Wordpress
2021-07-05T14:18:09.993+0530    INFO    controllers.Wordpress   MySQL isn't running, waiting for 5s
2021-07-05T14:18:14.993+0530    INFO    controllers.Wordpress   Reconciling Wordpress
2021-07-05T14:18:14.994+0530    INFO    controllers.Wordpress   MySQL isn't running, waiting for 5s
2021-07-05T14:19:10.278+0530    INFO    controllers.Wordpress   Reconciling Wordpress
2021-07-05T14:19:10.278+0530    INFO    controllers.Wordpress   Creating a new PVC      {"PVC.Namespace": "default", "PVC.Name": "wp-pv-claim"}
2021-07-05T14:19:10.292+0530    INFO    controllers.Wordpress   Creating a new Deployment  {"Deployment.Namespace": "default", "Deployment.Name": "wordpress"}
2021-07-05T14:19:10.309+0530    INFO    controllers.Wordpress   Creating a new Service  {"Service.Namespace": "default", "Service.Name": "wordpress"}
2021-07-05T14:19:10.341+0530    INFO    controllers.Wordpress   Reconciling Wordpress
2021-07-05T14:19:10.355+0530    INFO    controllers.Wordpress   Reconciling Wordpress
2021-07-05T14:19:10.361+0530    INFO    controllers.Wordpress   Reconciling Wordpress
2021-07-05T14:19:10.371+0530    INFO    controllers.Wordpress   Reconciling Wordpress
2021-07-05T14:19:15.001+0530    INFO    controllers.Wordpress   Reconciling Wordpress

From the second terminal window, check for the resources created as follows:

[pjiandan@localhost wordpress-operator]$ kubectl get all
NAME                                   READY   STATUS    RESTARTS   AGE
pod/wordpress-7446b985d9-vc7w2         1/1     Running   0          128m
pod/wordpress-mysql-5cd8987844-4p6jp   1/1     Running   0          128m

NAME                      TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
service/kubernetes        ClusterIP   10.96.0.1       <none>        443/TCP        3h58m
service/wordpress         NodePort    10.110.12.200   <none>        80:32324/TCP   128m
service/wordpress-mysql   ClusterIP   None            <none>        3306/TCP       128m

NAME                              READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/wordpress         1/1     1            1           128m
deployment.apps/wordpress-mysql   1/1     1            1           128m

NAME                                         DESIRED   CURRENT   READY   AGE
replicaset.apps/wordpress-7446b985d9         1         1         1       128m
replicaset.apps/wordpress-mysql-5cd8987844   1         1         1       128m

Next, run the following command to return the IP address for the WordPress service:

[pjiandan@localhost wordpress-operator]$ minikube service wordpress --url
http://192.168.99.143:32324

Verify that WordPress is running

Open the URL in your browser to verify that WordPress is running, as shown in Figure 1.

A running instance of WordPress shows that the Operator was successful.
Figure 1: A running instance of WordPress.
Creator

Conclusion

Visit my GitHub repository to see all of the code used in this example.

Last updated: September 19, 2023