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:
- Golang v1.15+
- The operator-sdk command-line interface, version 1 or higher
- minikube
- kubectl
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.
Conclusion
Visit my GitHub repository to see all of the code used in this example.
Last updated: September 19, 2023