Building a Kubernetes Operator for Email Configuration and Sending .

Building a Kubernetes Operator for Email Configuration and Sending .

GitHub Source Code

https://github.com/Gatete-Bruno/email-operator/tree/main

Prerequisites

  • go version v1.20.0+

  • docker version 17.03+.

  • kubectl version v1.11.3+.

  • Access to a Kubernetes v1.11.3+ cluster.

Installing Docker on your environment

https://docs.docker.com/engine/install/

Installing kubectl on your environment

https://kubernetes.io/docs/tasks/tools/install-kubectl-linux/

Installing Go on your environment

https://go.dev/doc/install

Setting a Kubernetes Cluster

In my case I opted to use AWS EKS as it is easier to bootsrap

Requirments :

Make sure you have access to aws cli and you can use aws configure

Have your credentials setup

How to bootsrap your K8s cluster :

 eksctl create cluster --name mailersend-cluster --region us-east-1 --nodegroup-name standard-workers --node-type t3.medium --nodes 3

 aws eks update-kubeconfig --name mailersend-cluster

We already have our images pushed to docker hub we will need to deploy using that

To Deploy on the cluster

Install the CRDs into the cluster:

make install

Deploy the Manager to the cluster with the image specified byIMG:

make deploy IMG=bruno74t/email-operator:v13.0

How it will look like

bruno@Batman-2 email-operator % make deploy IMG=bruno74t/email-operator:v13.0                  
test -s /Users/bruno/Desktop/mailersend-k8s-operator/projects/email-operator/bin/controller-gen && /Users/bruno/Desktop/mailersend-k8s-operator/projects/email-operator/bin/controller-gen --version | grep -q v0.13.0 || \
        GOBIN=/Users/bruno/Desktop/mailersend-k8s-operator/projects/email-operator/bin go install sigs.k8s.io/controller-tools/cmd/controller-gen@v0.13.0
/Users/bruno/Desktop/mailersend-k8s-operator/projects/email-operator/bin/controller-gen rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases
cd config/manager && /Users/bruno/Desktop/mailersend-k8s-operator/projects/email-operator/bin/kustomize edit set image controller=bruno74t/email-operator:v13.0
/Users/bruno/Desktop/mailersend-k8s-operator/projects/email-operator/bin/kustomize build config/default | kubectl apply -f -
namespace/email-operator-system unchanged
customresourcedefinition.apiextensions.k8s.io/emails.email.batman.example.com unchanged
customresourcedefinition.apiextensions.k8s.io/emailsenderconfigs.email.batman.example.com unchanged
serviceaccount/email-operator-email-operator-controller-manager unchanged
role.rbac.authorization.k8s.io/email-operator-leader-election-role unchanged
clusterrole.rbac.authorization.k8s.io/email-operator-manager-role unchanged
clusterrole.rbac.authorization.k8s.io/email-operator-metrics-reader unchanged
clusterrole.rbac.authorization.k8s.io/email-operator-proxy-role unchanged
rolebinding.rbac.authorization.k8s.io/email-operator-leader-election-rolebinding unchanged
clusterrolebinding.rbac.authorization.k8s.io/email-operator-manager-rolebinding unchanged
clusterrolebinding.rbac.authorization.k8s.io/email-operator-proxy-rolebinding unchanged
service/email-operator-controller-manager-metrics-service unchanged
deployment.apps/email-operator-controller-manager unchanged
bruno@Batman-2 email-operator % kubectl get all -n email-operator-system                             
NAME                                                     READY   STATUS    RESTARTS   AGE
pod/email-operator-controller-manager-5dbccbb4cb-ghjf4   2/2     Running   0          24m

NAME                                                        TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE
service/controller-manager-metrics-service                  ClusterIP   10.100.116.206   <none>        8443/TCP   25m
service/email-operator-controller-manager-metrics-service   ClusterIP   10.100.27.240    <none>        8443/TCP   36m

NAME                                                READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/email-operator-controller-manager   1/1     1            1           36m

NAME                                                           DESIRED   CURRENT   READY   AGE
replicaset.apps/email-operator-controller-manager-5dbccbb4cb   1         1         1       24m
replicaset.apps/email-operator-controller-manager-bb9c7cb86    0         0         0       27m
replicaset.apps/email-operator-controller-manager-cfffdf4cf    0         0         0       36m

The most important requirement here will be to setup the mailgun-api-token on your cluster

kubectl create secret generic mailgun-api-token \
  --from-literal=api-token=<YOUR-MAILGUN-API-TOKEN> \
  -n email-operator-system

You should have something like this on your cluster :

bruno@Batman-2 email-operator % kubectl get secrets -n email-operator-system
NAME                TYPE     DATA   AGE
mailgun-api-token   Opaque   1      65m
bruno@Batman-2 email-operator %

Check if all everything is okay on the namespace (email-operator-system)

bruno@Batman-2 email-operator % kubectl get all -n email-operator-system                                                    
NAME                                                     READY   STATUS    RESTARTS   AGE
pod/email-operator-controller-manager-78c798457d-b9qtw   2/2     Running   0          38m

NAME                                                        TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE
service/controller-manager-metrics-service                  ClusterIP   10.100.116.206   <none>        8443/TCP   64m
service/email-operator-controller-manager-metrics-service   ClusterIP   10.100.27.240    <none>        8443/TCP   75m

NAME                                                READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/email-operator-controller-manager   1/1     1            1           75m

NAME                                                           DESIRED   CURRENT   READY   AGE
replicaset.apps/email-operator-controller-manager-5dbccbb4cb   0         0         0       64m
replicaset.apps/email-operator-controller-manager-78c798457d   1         1         1       38m
replicaset.apps/email-operator-controller-manager-bb9c7cb86    0         0         0       67m
replicaset.apps/email-operator-controller-manager-cfffdf4cf    0         0         0       75m
bruno@Batman-2 email-operator %

Once everything is in place check to see if the operator has been deployed successfully

bruno@Batman-2 email-operator % kubectl logs deployment.apps/email-operator-controller-manager -n email-operator-system           
2024-06-13T07:50:13Z    INFO    setup   starting manager
2024-06-13T07:50:13Z    INFO    starting server {"kind": "health probe", "addr": "[::]:8081"}
2024-06-13T07:50:13Z    INFO    controller-runtime.metrics      Starting metrics server
2024-06-13T07:50:13Z    INFO    controller-runtime.metrics      Serving metrics server  {"bindAddress": "127.0.0.1:8080", "secure": false}
I0613 07:50:13.304846       1 leaderelection.go:250] attempting to acquire leader lease email-operator-system/650681eb.batman.example.com...
I0613 07:50:38.613997       1 leaderelection.go:260] successfully acquired lease email-operator-system/650681eb.batman.example.com
2024-06-13T07:50:38Z    INFO    Starting EventSource    {"controller": "emailsenderconfig", "controllerGroup": "email.batman.example.com", "controllerKind": "EmailSenderConfig", "source": "kind source: *v1alpha1.EmailSenderConfig"}
2024-06-13T07:50:38Z    INFO    Starting Controller     {"controller": "emailsenderconfig", "controllerGroup": "email.batman.example.com", "controllerKind": "EmailSenderConfig"}
2024-06-13T07:50:38Z    INFO    Starting EventSource    {"controller": "email", "controllerGroup": "email.batman.example.com", "controllerKind": "Email", "source": "kind source: *v1alpha1.Email"}
2024-06-13T07:50:38Z    INFO    Starting Controller     {"controller": "email", "controllerGroup": "email.batman.example.com", "controllerKind": "Email"}
2024-06-13T07:50:38Z    DEBUG   events  email-operator-controller-manager-78c798457d-b9qtw_e9c498bf-980b-4f47-bf90-29a9ca1dc227 became leader   {"type": "Normal", "object": {"kind":"Lease","namespace":"email-operator-system","name":"650681eb.batman.example.com","uid":"4cc830f6-fe99-443e-8556-da98381160b9","apiVersion":"coordination.k8s.io/v1","resourceVersion":"10838"}, "reason": "LeaderElection"}
2024-06-13T07:50:38Z    INFO    Starting workers        {"controller": "emailsenderconfig", "controllerGroup": "email.batman.example.com", "controllerKind": "EmailSenderConfig", "worker count": 1}
2024-06-13T07:50:38Z    INFO    Starting workers        {"controller": "email", "controllerGroup": "email.batman.example.com", "controllerKind": "Email", "worker count": 1}
2024-06-13T07:52:57Z    INFO    KubeAPIWarningLogger    metadata.finalizers: "emailSenderConfig.finalizers.batman.example.com": prefer a domain-qualified finalizer name to avoid accidental conflicts with other finalizer writers
bruno@Batman-2 email-operator %

Final Step is to Deploy the email testing yaml files


bruno@Batman-2 email-operator % tree config/samples                                                                               
config/samples
├── email_v1alpha1_email.yaml
├── email_v1alpha1_emailsenderconfig.yaml
└── kustomization.yaml

1 directory, 3 files
bruno@Batman-2 email-operator % cat config/samples/email_v1alpha1_email.yaml 
apiVersion: email.batman.example.com/v1alpha1
kind: Email
metadata:
  labels:
    app.kubernetes.io/name: email
    app.kubernetes.io/instance: email-sample
    app.kubernetes.io/part-of: email-operator
    app.kubernetes.io/managed-by: kustomize
    app.kubernetes.io/created-by: email-operator
  name: email-sample
  namespace: email-operator-system
spec:
  senderConfigRef: emailsenderconfig-sample  # Ensure this is correct
  recipientEmail: "catobrunoisrael@gmail.com"
  subject: "Test Email"
  body: "This is the first test email using k8s operator."
bruno@Batman-2 email-operator % cat config/samples/email_v1alpha1_emailsenderconfig.yaml 
apiVersion: email.batman.example.com/v1alpha1
kind: EmailSenderConfig
metadata:
  labels:
    app.kubernetes.io/name: emailsenderconfig
    app.kubernetes.io/instance: emailsenderconfig-sample
    app.kubernetes.io/part-of: email-operator
    app.kubernetes.io/managed-by: kustomize
    app.kubernetes.io/created-by: email-operator
  name: emailsenderconfig-sample  # Corrected name
  namespace: email-operator-system
spec:
  apiTokenSecretRef: mailgun-api-token
  senderEmail: kato@example.com
  mailgunConfig:
    domain: "sandbox949259d91ad141fa8fb2518c835ccf49.mailgun.org"
    apiKeySecretRef: mailgun-api-token

bruno@Batman-2 email-operator %

Once you have applied the manifest files

kubectl delete -f config/samples/email_v1alpha1_email.yaml
kubectl delete -f config/samples/email_v1alpha1_emailsenderconfig.yaml

kubectl apply -f config/samples/email_v1alpha1_emailsenderconfig.yaml
kubectl apply -f config/samples/email_v1alpha1_email.yaml

Monitor Operator Logs

Monitor the logs of your Kubernetes operator to check for any messages related

bruno@Batman-2 email-operator % kubectl logs deployment.apps/email-operator-controller-manager -n email-operator-system -c manager
2024-06-13T07:50:13Z    INFO    setup   starting manager
2024-06-13T07:50:13Z    INFO    starting server {"kind": "health probe", "addr": "[::]:8081"}
2024-06-13T07:50:13Z    INFO    controller-runtime.metrics      Starting metrics server
2024-06-13T07:50:13Z    INFO    controller-runtime.metrics      Serving metrics server  {"bindAddress": "127.0.0.1:8080", "secure": false}
I0613 07:50:13.304846       1 leaderelection.go:250] attempting to acquire leader lease email-operator-system/650681eb.batman.example.com...
I0613 07:50:38.613997       1 leaderelection.go:260] successfully acquired lease email-operator-system/650681eb.batman.example.com
2024-06-13T07:50:38Z    INFO    Starting EventSource    {"controller": "emailsenderconfig", "controllerGroup": "email.batman.example.com", "controllerKind": "EmailSenderConfig", "source": "kind source: *v1alpha1.EmailSenderConfig"}
2024-06-13T07:50:38Z    INFO    Starting Controller     {"controller": "emailsenderconfig", "controllerGroup": "email.batman.example.com", "controllerKind": "EmailSenderConfig"}
2024-06-13T07:50:38Z    INFO    Starting EventSource    {"controller": "email", "controllerGroup": "email.batman.example.com", "controllerKind": "Email", "source": "kind source: *v1alpha1.Email"}
2024-06-13T07:50:38Z    INFO    Starting Controller     {"controller": "email", "controllerGroup": "email.batman.example.com", "controllerKind": "Email"}
2024-06-13T07:50:38Z    DEBUG   events  email-operator-controller-manager-78c798457d-b9qtw_e9c498bf-980b-4f47-bf90-29a9ca1dc227 became leader   {"type": "Normal", "object": {"kind":"Lease","namespace":"email-operator-system","name":"650681eb.batman.example.com","uid":"4cc830f6-fe99-443e-8556-da98381160b9","apiVersion":"coordination.k8s.io/v1","resourceVersion":"10838"}, "reason": "LeaderElection"}
2024-06-13T07:50:38Z    INFO    Starting workers        {"controller": "emailsenderconfig", "controllerGroup": "email.batman.example.com", "controllerKind": "EmailSenderConfig", "worker count": 1}
2024-06-13T07:50:38Z    INFO    Starting workers        {"controller": "email", "controllerGroup": "email.batman.example.com", "controllerKind": "Email", "worker count": 1}
2024-06-13T07:52:57Z    INFO    KubeAPIWarningLogger    metadata.finalizers: "emailSenderConfig.finalizers.batman.example.com": prefer a domain-qualified finalizer name to avoid accidental conflicts with other finalizer writers
bruno@Batman-2 email-operator %

you should be able to check the status of the emails sent

bruno@Batman-2 email-operator % kubectl describe email email-sample -n email-operator-system                               
Name:         email-sample
Namespace:    email-operator-system
Labels:       app.kubernetes.io/created-by=email-operator
              app.kubernetes.io/instance=email-sample
              app.kubernetes.io/managed-by=kustomize
              app.kubernetes.io/name=email
              app.kubernetes.io/part-of=email-operator
Annotations:  <none>
API Version:  email.batman.example.com/v1alpha1
Kind:         Email
Metadata:
  Creation Timestamp:  2024-06-13T07:56:40Z
  Generation:          1
  Resource Version:    12048
  UID:                 7327636e-c232-423e-a08b-d2a4c2f7cfc5
Spec:
  Body:               This is the first test email using k8s operator.
  Recipient Email:    catobrunoisrael@gmail.com
  Sender Config Ref:  emailsenderconfig-sample
  Subject:            Test Email
Status:
  Delivery Status:  Failed
  Error:            Unknown error
Events:             <none>
bruno@Batman-2 email-operator %

My email Delivery Status is failing without showing any events or anything ..I am still trying to find out why !