Skip to main content
Redhat Developers  Logo
  • AI

    Get started with AI

    • Red Hat AI
      Accelerate the development and deployment of enterprise AI solutions.
    • AI learning hub
      Explore learning materials and tools, organized by task.
    • AI interactive demos
      Click through scenarios with Red Hat AI, including training LLMs and more.
    • AI/ML learning paths
      Expand your OpenShift AI knowledge using these learning resources.
    • AI quickstarts
      Focused AI use cases designed for fast deployment on Red Hat AI platforms.
    • No-cost AI training
      Foundational Red Hat AI training.

    Featured resources

    • OpenShift AI learning
    • Open source AI for developers
    • AI product application development
    • Open source-powered AI/ML for hybrid cloud
    • AI and Node.js cheat sheet

    Red Hat AI Factory with NVIDIA

    • Red Hat AI Factory with NVIDIA is a co-engineered, enterprise-grade AI solution for building, deploying, and managing AI at scale across hybrid cloud environments.
    • Explore the solution
  • Learn

    Self-guided

    • Documentation
      Find answers, get step-by-step guidance, and learn how to use Red Hat products.
    • Learning paths
      Explore curated walkthroughs for common development tasks.
    • Guided learning
      Receive custom learning paths powered by our AI assistant.
    • See all learning

    Hands-on

    • Developer Sandbox
      Spin up Red Hat's products and technologies without setup or configuration.
    • Interactive labs
      Learn by doing in these hands-on, browser-based experiences.
    • Interactive demos
      Click through product features in these guided tours.

    Browse by topic

    • AI/ML
    • Automation
    • Java
    • Kubernetes
    • Linux
    • See all topics

    Training & certifications

    • Courses and exams
    • Certifications
    • Skills assessments
    • Red Hat Academy
    • Learning subscription
    • Explore training
  • Build

    Get started

    • Red Hat build of Podman Desktop
      A downloadable, local development hub to experiment with our products and builds.
    • Developer Sandbox
      Spin up Red Hat's products and technologies without setup or configuration.

    Download products

    • Access product downloads to start building and testing right away.
    • Red Hat Enterprise Linux
    • Red Hat AI
    • Red Hat OpenShift
    • Red Hat Ansible Automation Platform
    • See all products

    Featured

    • Red Hat build of OpenJDK
    • Red Hat JBoss Enterprise Application Platform
    • Red Hat OpenShift Dev Spaces
    • Red Hat Developer Toolset

    References

    • E-books
    • Documentation
    • Cheat sheets
    • Architecture center
  • Community

    Get involved

    • Events
    • Live AI events
    • Red Hat Summit
    • Red Hat Accelerators
    • Community discussions

    Follow along

    • Articles & blogs
    • Developer newsletter
    • Videos
    • Github

    Get help

    • Customer service
    • Customer support
    • Regional contacts
    • Find a partner

    Join the Red Hat Developer program

    • Download Red Hat products and project builds, access support documentation, learning content, and more.
    • Explore the benefits

Managing stateful applications with Kubernetes Operators in Golang

August 4, 2021
Priyanka Jiandani
Related topics:
Event-drivenGoKubernetesOperators
Related products:
Red Hat OpenShift

    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.

    A running instance of WordPress shows that the Operator was successful.
    Figure 1: A running instance of WordPress.
    Created by ,

    Conclusion

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

    Last updated: September 19, 2023

    Related Posts

    • Create a Kubernetes Operator in Golang to automatically manage a simple, stateful application

    • 5 tips for developing Kubernetes Operators with the new Operator SDK

    • Deploy and bind enterprise-grade microservices with Kubernetes Operators

    • Deploying Kubernetes Operators with Operator Lifecycle Manager bundles

    Recent Posts

    • Trusted software factory: Building trust in the agentic AI era

    • Build a zero trust AI pipeline with OpenShift and RHEL CVMs

    • Red Hat Hardened Images: Top 5 benefits for software developers

    • How EvalHub manages two-layer Kubernetes control planes

    • Tekton joins the CNCF as an incubating project

    Red Hat Developers logo LinkedIn YouTube Twitter Facebook

    Platforms

    • Red Hat AI
    • Red Hat Enterprise Linux
    • Red Hat OpenShift
    • Red Hat Ansible Automation Platform
    • See all products

    Build

    • Developer Sandbox
    • Developer tools
    • Interactive tutorials
    • API catalog

    Quicklinks

    • Learning resources
    • E-books
    • Cheat sheets
    • Blog
    • Events
    • Newsletter

    Communicate

    • About us
    • Contact sales
    • Find a partner
    • Report a website issue
    • Site status dashboard
    • Report a security problem

    RED HAT DEVELOPER

    Build here. Go anywhere.

    We serve the builders. The problem solvers who create careers with code.

    Join us if you’re a developer, software engineer, web designer, front-end designer, UX designer, computer scientist, architect, tester, product manager, project manager or team lead.

    Sign me up

    Red Hat legal and privacy links

    • About Red Hat
    • Jobs
    • Events
    • Locations
    • Contact Red Hat
    • Red Hat Blog
    • Inclusion at Red Hat
    • Cool Stuff Store
    • Red Hat Summit
    © 2026 Red Hat

    Red Hat legal and privacy links

    • Privacy statement
    • Terms of use
    • All policies and guidelines
    • Digital accessibility

    Chat Support

    Please log in with your Red Hat account to access chat support.