Add CRD, Controller for SkyWalking Event Exporter (#113)

* Add CRD and Controller for EventExporter and EventExporterConfig

* Add auto-generated config files for EventExporter and EventExporterConfig

* Merge EventExporterConfig into EventExporter

* Remove EventExporterConfig in PROJECT

* Remove unused type in eventexporter_types.go

* Pass linting

* Refactor event exporter CRD and controller

* Remove unused dependency

* Update EventExporterController rabc and configMap

* Add a simple test case to eventexporter e2e test

* Add eventexporter e2e test to GitHub workflows

* Remove unused UI component in event exporter e2e test

* Update event exporter controller: delete old configMap after creating the new one, generate fixed-length name for configMaps
diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml
index e647d9a..1ef554e 100644
--- a/.github/workflows/go.yml
+++ b/.github/workflows/go.yml
@@ -227,9 +227,24 @@
         uses: apache/skywalking-infra-e2e@996ed8902e941e2883fcf0ac5b3090364942d205
         with:
           e2e-file: test/e2e/oap-ui-agent-oapserverconfig-oapserverdynamicconfig/e2e.yaml
+  oapserver-eventexporter-e2e-tests:
+    name: e2e tests(oap+eventexporter)
+    runs-on: ubuntu-latest
+    steps:
+      - name: Install Go
+        uses: actions/setup-go@v3
+        with:
+          go-version: '1.22'
+        id: go
+      - name: Check out code into the Go module directory
+        uses: actions/checkout@v3
+      - name: Run E2E Test
+        uses: apache/skywalking-infra-e2e@996ed8902e941e2883fcf0ac5b3090364942d205
+        with:
+          e2e-file: test/e2e/oap-eventexporter/e2e.yaml
   checks:
     name: build
     runs-on: ubuntu-20.04
-    needs: [check, build, unit-tests, e2e-tests, swagent-e2e-tests, swagent-configmap-e2e-tests, internel-storage-e2e-tests, external-storage-e2e-tests, adapter-hpa-e2e-tests, e2e-tests-with-satellite, adapter-satellite-hpa-e2e-tests, oapserver-configuration-e2e-tests]
+    needs: [check, build, unit-tests, e2e-tests, swagent-e2e-tests, swagent-configmap-e2e-tests, internel-storage-e2e-tests, external-storage-e2e-tests, adapter-hpa-e2e-tests, e2e-tests-with-satellite, adapter-satellite-hpa-e2e-tests, oapserver-configuration-e2e-tests, oapserver-eventexporter-e2e-tests]
     steps:
       - run: echo 'success'
diff --git a/operator/PROJECT b/operator/PROJECT
index 73b6a37..e603a61 100644
--- a/operator/PROJECT
+++ b/operator/PROJECT
@@ -131,4 +131,17 @@
   kind: BanyanDB
   path: github.com/apache/skywalking-swck/operator/apis/operator/v1alpha1
   version: v1alpha1
+- api:
+    crdVersion: v1
+    namespaced: true
+  controller: true
+  domain: skywalking.apache.org
+  group: operator
+  kind: EventExporter
+  path: github.com/apache/skywalking-swck/operator/apis/operator/v1alpha1
+  version: v1alpha1
+  webhooks:
+    defaulting: true
+    validation: true
+    webhookVersion: v1
 version: "3"
diff --git a/operator/apis/operator/v1alpha1/eventexporter_types.go b/operator/apis/operator/v1alpha1/eventexporter_types.go
new file mode 100644
index 0000000..f548fd7
--- /dev/null
+++ b/operator/apis/operator/v1alpha1/eventexporter_types.go
@@ -0,0 +1,79 @@
+// Licensed to Apache Software Foundation (ASF) under one or more contributor
+// license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright
+// ownership. Apache Software Foundation (ASF) licenses this file to you under
+// the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package v1alpha1
+
+import (
+	appsv1 "k8s.io/api/apps/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+// EventExporterSpec defines the desired state of EventExporter
+type EventExporterSpec struct {
+	// Version of EventExporter.
+	// +kubebuilder:validation:Required
+	Version string `json:"version,omitempty"`
+	// Image is the event exporter Docker image to deploy.
+	Image string `json:"image,omitempty"`
+	// Replicas is the number of event exporter pods
+	// +kubebuilder:validation:Required
+	Replicas int32 `json:"replicas,omitempty"`
+	// Config of filters and exporters
+	// +kubebuilder:validation:Optional
+	Config string `json:"config,omitempty"`
+}
+
+// EventExporterStatus defines the observed state of EventExporter
+type EventExporterStatus struct {
+	// Total number of available pods targeted by this deployment.
+	// +kubebuilder:validation:Optional
+	AvailableReplicas int32 `json:"availableReplicas,omitempty"`
+	// Represents the latest available observations of the underlying deployment's current state.
+	// +kubebuilder:validation:Optional
+	Conditions []appsv1.DeploymentCondition `json:"conditions,omitempty"`
+	// Name of the configMap.
+	// +kubebuilder:validation:Optional
+	ConfigMapName string `json:"configMapName,omitempty"`
+}
+
+// +kubebuilder:object:root=true
+// +kubebuilder:subresource:status
+// +kubebuilder:printcolumn:name="Version",type="string",priority=1,JSONPath=".spec.version",description="The version"
+// +kubebuilder:printcolumn:name="Image",type="string",priority=1,JSONPath=".spec.image"
+// +kubebuilder:printcolumn:name="Instances",type="string",JSONPath=".spec.instances",description="The number of expected instances"
+
+// EventExporter is the Schema for the eventexporters API
+type EventExporter struct {
+	metav1.TypeMeta   `json:",inline"`
+	metav1.ObjectMeta `json:"metadata,omitempty"`
+
+	Spec   EventExporterSpec   `json:"spec,omitempty"`
+	Status EventExporterStatus `json:"status,omitempty"`
+}
+
+//+kubebuilder:object:root=true
+
+// EventExporterList contains a list of EventExporter
+type EventExporterList struct {
+	metav1.TypeMeta `json:",inline"`
+	metav1.ListMeta `json:"metadata,omitempty"`
+	Items           []EventExporter `json:"items"`
+}
+
+func init() {
+	SchemeBuilder.Register(&EventExporter{}, &EventExporterList{})
+}
diff --git a/operator/apis/operator/v1alpha1/eventexporter_webhook.go b/operator/apis/operator/v1alpha1/eventexporter_webhook.go
new file mode 100644
index 0000000..eac2e55
--- /dev/null
+++ b/operator/apis/operator/v1alpha1/eventexporter_webhook.go
@@ -0,0 +1,95 @@
+// Licensed to Apache Software Foundation (ASF) under one or more contributor
+// license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright
+// ownership. Apache Software Foundation (ASF) licenses this file to you under
+// the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package v1alpha1
+
+import (
+	"fmt"
+
+	"k8s.io/apimachinery/pkg/runtime"
+	ctrl "sigs.k8s.io/controller-runtime"
+	logf "sigs.k8s.io/controller-runtime/pkg/log"
+	"sigs.k8s.io/controller-runtime/pkg/webhook"
+	"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
+)
+
+const latestVersion = "latest"
+const image = "apache/skywalking-kubernetes-event-exporter"
+
+// log is for logging in this package.
+var eventexporterlog = logf.Log.WithName("eventexporter-resource")
+
+func (r *EventExporter) SetupWebhookWithManager(mgr ctrl.Manager) error {
+	return ctrl.NewWebhookManagedBy(mgr).
+		For(r).
+		Complete()
+}
+
+// nolint: lll
+//+kubebuilder:webhook:path=/mutate-operator-skywalking-apache-org-v1alpha1-eventexporter,mutating=true,failurePolicy=fail,sideEffects=None,groups=operator.skywalking.apache.org,resources=eventexporters,verbs=create;update,versions=v1alpha1,name=meventexporter.kb.io,admissionReviewVersions=v1
+
+var _ webhook.Defaulter = &EventExporter{}
+
+// Default implements webhook.Defaulter so a webhook will be registered for the type
+func (r *EventExporter) Default() {
+	eventexporterlog.Info("default", "name", r.Name)
+
+	if r.Spec.Version == "" {
+		r.Spec.Version = latestVersion
+	}
+
+	if r.Spec.Image == "" {
+		r.Spec.Image = fmt.Sprintf("%s:%s", image, r.Spec.Version)
+	}
+
+	if r.Spec.Replicas == 0 {
+		r.Spec.Replicas = 1
+	}
+}
+
+// nolint: lll
+// +kubebuilder:webhook:admissionReviewVersions=v1,sideEffects=None,path=/mutate-operator-skywalking-apache-org-v1alpha1-eventexporter,mutating=true,failurePolicy=fail,groups=operator.skywalking.apache.org,resources=eventexporters,verbs=create;update,versions=v1alpha1,name=meventexporter.kb.io
+
+var _ webhook.Validator = &EventExporter{}
+
+// ValidateCreate implements webhook.Validator so a webhook will be registered for the type
+func (r *EventExporter) ValidateCreate() (admission.Warnings, error) {
+	eventexporterlog.Info("validate create", "name", r.Name)
+
+	return nil, r.validate()
+}
+
+// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type
+func (r *EventExporter) ValidateUpdate(_ runtime.Object) (admission.Warnings, error) {
+	eventexporterlog.Info("validate update", "name", r.Name)
+
+	return nil, r.validate()
+}
+
+// ValidateDelete implements webhook.Validator so a webhook will be registered for the type
+func (r *EventExporter) ValidateDelete() (admission.Warnings, error) {
+	eventexporterlog.Info("validate delete", "name", r.Name)
+
+	return nil, nil
+}
+
+func (r *EventExporter) validate() error {
+	if r.Spec.Image == "" {
+		return fmt.Errorf("image is absent")
+	}
+	return nil
+}
diff --git a/operator/apis/operator/v1alpha1/zz_generated.deepcopy.go b/operator/apis/operator/v1alpha1/zz_generated.deepcopy.go
index 627c68a..340ef8b 100644
--- a/operator/apis/operator/v1alpha1/zz_generated.deepcopy.go
+++ b/operator/apis/operator/v1alpha1/zz_generated.deepcopy.go
@@ -1,5 +1,4 @@
 //go:build !ignore_autogenerated
-// +build !ignore_autogenerated
 
 // Licensed to Apache Software Foundation (ASF) under one or more contributor
 // license agreements. See the NOTICE file distributed with
@@ -156,6 +155,102 @@
 }
 
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *EventExporter) DeepCopyInto(out *EventExporter) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
+	out.Spec = in.Spec
+	in.Status.DeepCopyInto(&out.Status)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EventExporter.
+func (in *EventExporter) DeepCopy() *EventExporter {
+	if in == nil {
+		return nil
+	}
+	out := new(EventExporter)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *EventExporter) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *EventExporterList) DeepCopyInto(out *EventExporterList) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ListMeta.DeepCopyInto(&out.ListMeta)
+	if in.Items != nil {
+		in, out := &in.Items, &out.Items
+		*out = make([]EventExporter, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EventExporterList.
+func (in *EventExporterList) DeepCopy() *EventExporterList {
+	if in == nil {
+		return nil
+	}
+	out := new(EventExporterList)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *EventExporterList) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *EventExporterSpec) DeepCopyInto(out *EventExporterSpec) {
+	*out = *in
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EventExporterSpec.
+func (in *EventExporterSpec) DeepCopy() *EventExporterSpec {
+	if in == nil {
+		return nil
+	}
+	out := new(EventExporterSpec)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *EventExporterStatus) DeepCopyInto(out *EventExporterStatus) {
+	*out = *in
+	if in.Conditions != nil {
+		in, out := &in.Conditions, &out.Conditions
+		*out = make([]v1.DeploymentCondition, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EventExporterStatus.
+func (in *EventExporterStatus) DeepCopy() *EventExporterStatus {
+	if in == nil {
+		return nil
+	}
+	out := new(EventExporterStatus)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *Fetcher) DeepCopyInto(out *Fetcher) {
 	*out = *in
 	out.TypeMeta = in.TypeMeta
diff --git a/operator/config/crd/bases/operator.skywalking.apache.org_eventexporters.yaml b/operator/config/crd/bases/operator.skywalking.apache.org_eventexporters.yaml
new file mode 100644
index 0000000..568bb11
--- /dev/null
+++ b/operator/config/crd/bases/operator.skywalking.apache.org_eventexporters.yaml
@@ -0,0 +1,136 @@
+# Licensed to Apache Software Foundation (ASF) under one or more contributor
+# license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright
+# ownership. Apache Software Foundation (ASF) licenses this file to you under
+# the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+  annotations:
+    controller-gen.kubebuilder.io/version: v0.14.0
+  name: eventexporters.operator.skywalking.apache.org
+spec:
+  group: operator.skywalking.apache.org
+  names:
+    kind: EventExporter
+    listKind: EventExporterList
+    plural: eventexporters
+    singular: eventexporter
+  scope: Namespaced
+  versions:
+  - additionalPrinterColumns:
+    - description: The version
+      jsonPath: .spec.version
+      name: Version
+      priority: 1
+      type: string
+    - jsonPath: .spec.image
+      name: Image
+      priority: 1
+      type: string
+    - description: The number of expected instances
+      jsonPath: .spec.instances
+      name: Instances
+      type: string
+    name: v1alpha1
+    schema:
+      openAPIV3Schema:
+        description: EventExporter is the Schema for the eventexporters API
+        properties:
+          apiVersion:
+            description: |-
+              APIVersion defines the versioned schema of this representation of an object.
+              Servers should convert recognized schemas to the latest internal value, and
+              may reject unrecognized values.
+              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
+            type: string
+          kind:
+            description: |-
+              Kind is a string value representing the REST resource this object represents.
+              Servers may infer this from the endpoint the client submits requests to.
+              Cannot be updated.
+              In CamelCase.
+              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
+            type: string
+          metadata:
+            type: object
+          spec:
+            description: EventExporterSpec defines the desired state of EventExporter
+            properties:
+              config:
+                description: Config of filters and exporters
+                type: string
+              image:
+                description: Image is the event exporter Docker image to deploy.
+                type: string
+              replicas:
+                description: Replicas is the number of event exporter pods
+                format: int32
+                type: integer
+              version:
+                description: Version of EventExporter.
+                type: string
+            type: object
+          status:
+            description: EventExporterStatus defines the observed state of EventExporter
+            properties:
+              availableReplicas:
+                description: Total number of available pods targeted by this deployment.
+                format: int32
+                type: integer
+              conditions:
+                description: Represents the latest available observations of the underlying
+                  deployment's current state.
+                items:
+                  description: DeploymentCondition describes the state of a deployment
+                    at a certain point.
+                  properties:
+                    lastTransitionTime:
+                      description: Last time the condition transitioned from one status
+                        to another.
+                      format: date-time
+                      type: string
+                    lastUpdateTime:
+                      description: The last time this condition was updated.
+                      format: date-time
+                      type: string
+                    message:
+                      description: A human readable message indicating details about
+                        the transition.
+                      type: string
+                    reason:
+                      description: The reason for the condition's last transition.
+                      type: string
+                    status:
+                      description: Status of the condition, one of True, False, Unknown.
+                      type: string
+                    type:
+                      description: Type of deployment condition.
+                      type: string
+                  required:
+                  - status
+                  - type
+                  type: object
+                type: array
+              configMapName:
+                description: Name of the configMap.
+                type: string
+            type: object
+        type: object
+    served: true
+    storage: true
+    subresources:
+      status: {}
diff --git a/operator/config/crd/kustomization.yaml b/operator/config/crd/kustomization.yaml
index 468804a..109b8ec 100644
--- a/operator/config/crd/kustomization.yaml
+++ b/operator/config/crd/kustomization.yaml
@@ -29,6 +29,7 @@
 - bases/operator.skywalking.apache.org_oapserverconfigs.yaml
 - bases/operator.skywalking.apache.org_oapserverdynamicconfigs.yaml
 - bases/operator.skywalking.apache.org_banyandbs.yaml
+- bases/operator.skywalking.apache.org_eventexporters.yaml
 #+kubebuilder:scaffold:crdkustomizeresource
 
 patchesStrategicMerge:
@@ -44,6 +45,7 @@
 #- patches/webhook_in_oapserverdynamicconfigs.yaml
 #- patches/webhook_in_swagents.yaml
 #- patches/webhook_in_banyandbs.yaml
+#- patches/webhook_in_eventexporters.yaml
 #+kubebuilder:scaffold:crdkustomizewebhookpatch
 
 # [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix.
@@ -58,6 +60,7 @@
 #- patches/cainjection_in_oapserverdynamicconfigs.yaml
 #- patches/cainjection_in_swagents.yaml
 #- patches/cainjection_in_banyandbs.yaml
+#- patches/cainjection_in_eventexporters.yaml
 #+kubebuilder:scaffold:crdkustomizecainjectionpatch
 
 # the following config is for teaching kustomize how to do kustomization for CRDs.
diff --git a/operator/config/crd/patches/cainjection_in_operator_eventexporters.yaml b/operator/config/crd/patches/cainjection_in_operator_eventexporters.yaml
new file mode 100644
index 0000000..0519865
--- /dev/null
+++ b/operator/config/crd/patches/cainjection_in_operator_eventexporters.yaml
@@ -0,0 +1,24 @@
+# Licensed to Apache Software Foundation (ASF) under one or more contributor
+# license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright
+# ownership. Apache Software Foundation (ASF) licenses this file to you under
+# the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+# The following patch adds a directive for certmanager to inject CA into the CRD
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+  annotations:
+    cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME)
+  name: eventexporters.operator.skywalking.apache.org
diff --git a/operator/config/crd/patches/webhook_in_operator_eventexporters.yaml b/operator/config/crd/patches/webhook_in_operator_eventexporters.yaml
new file mode 100644
index 0000000..9751a89
--- /dev/null
+++ b/operator/config/crd/patches/webhook_in_operator_eventexporters.yaml
@@ -0,0 +1,33 @@
+# Licensed to Apache Software Foundation (ASF) under one or more contributor
+# license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright
+# ownership. Apache Software Foundation (ASF) licenses this file to you under
+# the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+# The following patch enables a conversion webhook for the CRD
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+  name: eventexporters.operator.skywalking.apache.org
+spec:
+  conversion:
+    strategy: Webhook
+    webhook:
+      clientConfig:
+        service:
+          namespace: system
+          name: webhook-service
+          path: /convert
+      conversionReviewVersions:
+      - v1
diff --git a/operator/config/rbac/operator_eventexporter_editor_role.yaml b/operator/config/rbac/operator_eventexporter_editor_role.yaml
new file mode 100644
index 0000000..e0f27b7
--- /dev/null
+++ b/operator/config/rbac/operator_eventexporter_editor_role.yaml
@@ -0,0 +1,48 @@
+# Licensed to Apache Software Foundation (ASF) under one or more contributor
+# license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright
+# ownership. Apache Software Foundation (ASF) licenses this file to you under
+# the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+# permissions for end users to edit eventexporters.
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+  labels:
+    app.kubernetes.io/name: clusterrole
+    app.kubernetes.io/instance: eventexporter-editor-role
+    app.kubernetes.io/component: rbac
+    app.kubernetes.io/created-by: skywalking-swck
+    app.kubernetes.io/part-of: skywalking-swck
+    app.kubernetes.io/managed-by: kustomize
+  name: eventexporter-editor-role
+rules:
+- apiGroups:
+  - operator.skywalking.apache.org
+  resources:
+  - eventexporters
+  verbs:
+  - create
+  - delete
+  - get
+  - list
+  - patch
+  - update
+  - watch
+- apiGroups:
+  - operator.skywalking.apache.org
+  resources:
+  - eventexporters/status
+  verbs:
+  - get
diff --git a/operator/config/rbac/operator_eventexporter_viewer_role.yaml b/operator/config/rbac/operator_eventexporter_viewer_role.yaml
new file mode 100644
index 0000000..e5202e1
--- /dev/null
+++ b/operator/config/rbac/operator_eventexporter_viewer_role.yaml
@@ -0,0 +1,44 @@
+# Licensed to Apache Software Foundation (ASF) under one or more contributor
+# license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright
+# ownership. Apache Software Foundation (ASF) licenses this file to you under
+# the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+# permissions for end users to view eventexporters.
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+  labels:
+    app.kubernetes.io/name: clusterrole
+    app.kubernetes.io/instance: eventexporter-viewer-role
+    app.kubernetes.io/component: rbac
+    app.kubernetes.io/created-by: skywalking-swck
+    app.kubernetes.io/part-of: skywalking-swck
+    app.kubernetes.io/managed-by: kustomize
+  name: eventexporter-viewer-role
+rules:
+- apiGroups:
+  - operator.skywalking.apache.org
+  resources:
+  - eventexporters
+  verbs:
+  - get
+  - list
+  - watch
+- apiGroups:
+  - operator.skywalking.apache.org
+  resources:
+  - eventexporters/status
+  verbs:
+  - get
diff --git a/operator/config/rbac/role.yaml b/operator/config/rbac/role.yaml
index 9e941d3..01d4d38 100644
--- a/operator/config/rbac/role.yaml
+++ b/operator/config/rbac/role.yaml
@@ -19,7 +19,6 @@
 apiVersion: rbac.authorization.k8s.io/v1
 kind: ClusterRole
 metadata:
-  creationTimestamp: null
   name: manager-role
 rules:
 - apiGroups:
@@ -182,6 +181,32 @@
 - apiGroups:
   - operator.skywalking.apache.org
   resources:
+  - eventexporters
+  verbs:
+  - create
+  - delete
+  - get
+  - list
+  - patch
+  - update
+  - watch
+- apiGroups:
+  - operator.skywalking.apache.org
+  resources:
+  - eventexporters/finalizers
+  verbs:
+  - update
+- apiGroups:
+  - operator.skywalking.apache.org
+  resources:
+  - eventexporters/status
+  verbs:
+  - get
+  - patch
+  - update
+- apiGroups:
+  - operator.skywalking.apache.org
+  resources:
   - fetchers
   verbs:
   - create
diff --git a/operator/config/samples/eventexporter.yaml b/operator/config/samples/eventexporter.yaml
new file mode 100644
index 0000000..edab9d0
--- /dev/null
+++ b/operator/config/samples/eventexporter.yaml
@@ -0,0 +1,58 @@
+# Licensed to Apache Software Foundation (ASF) under one or more contributor
+# license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright
+# ownership. Apache Software Foundation (ASF) licenses this file to you under
+# the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+apiVersion: operator.skywalking.apache.org/v1alpha1
+kind: EventExporter
+metadata:
+  name: eventexporter-sample
+spec:
+  replicas: 1
+  config: |
+    filters:
+      - reason: ""     
+        message: ""    
+        minCount: 1    
+        type: ""       
+        action: ""     
+        kind: "Pod|Service"       
+        namespace: "^default$"  
+        name: ""       
+        service: "[^\\s]{1,}"  
+        exporters:     
+          - skywalking
+    exporters:         
+      skywalking:      
+        template:      
+          source:
+            service: "{{ .Service.Name }}"
+            serviceInstance: "{{ .Pod.Name }}"
+            endpoint: ""
+          message: "{{ .Event.Message }}" 
+        address: "default-oap:11800"
+
+---
+apiVersion: operator.skywalking.apache.org/v1alpha1
+kind: OAPServer
+metadata:
+  name: default
+spec:
+  version: 9.5.0
+  instances: 1
+  image: apache/skywalking-oap-server:9.5.0
+  service:
+    template:
+      type: ClusterIP
\ No newline at end of file
diff --git a/operator/config/webhook/manifests.yaml b/operator/config/webhook/manifests.yaml
index 5c1811c..24970d7 100644
--- a/operator/config/webhook/manifests.yaml
+++ b/operator/config/webhook/manifests.yaml
@@ -19,7 +19,6 @@
 apiVersion: admissionregistration.k8s.io/v1
 kind: MutatingWebhookConfiguration
 metadata:
-  creationTimestamp: null
   name: mutating-webhook-configuration
 webhooks:
 - admissionReviewVersions:
@@ -48,6 +47,46 @@
     service:
       name: webhook-service
       namespace: system
+      path: /mutate-operator-skywalking-apache-org-v1alpha1-eventexporter
+  failurePolicy: Fail
+  name: meventexporter.kb.io
+  rules:
+  - apiGroups:
+    - operator.skywalking.apache.org
+    apiVersions:
+    - v1alpha1
+    operations:
+    - CREATE
+    - UPDATE
+    resources:
+    - eventexporters
+  sideEffects: None
+- admissionReviewVersions:
+  - v1
+  clientConfig:
+    service:
+      name: webhook-service
+      namespace: system
+      path: /mutate-operator-skywalking-apache-org-v1alpha1-eventexporter
+  failurePolicy: Fail
+  name: meventexporter.kb.io
+  rules:
+  - apiGroups:
+    - operator.skywalking.apache.org
+    apiVersions:
+    - v1alpha1
+    operations:
+    - CREATE
+    - UPDATE
+    resources:
+    - eventexporters
+  sideEffects: None
+- admissionReviewVersions:
+  - v1
+  clientConfig:
+    service:
+      name: webhook-service
+      namespace: system
       path: /mutate-operator-skywalking-apache-org-v1alpha1-fetcher
   failurePolicy: Fail
   name: mfetcher.kb.io
@@ -242,12 +281,10 @@
     resources:
     - pods
   sideEffects: None
-
 ---
 apiVersion: admissionregistration.k8s.io/v1
 kind: ValidatingWebhookConfiguration
 metadata:
-  creationTimestamp: null
   name: validating-webhook-configuration
 webhooks:
 - admissionReviewVersions:
diff --git a/operator/controllers/operator/eventexporter_controller.go b/operator/controllers/operator/eventexporter_controller.go
new file mode 100644
index 0000000..ba94285
--- /dev/null
+++ b/operator/controllers/operator/eventexporter_controller.go
@@ -0,0 +1,218 @@
+// Licensed to Apache Software Foundation (ASF) under one or more contributor
+// license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright
+// ownership. Apache Software Foundation (ASF) licenses this file to you under
+// the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package operator
+
+import (
+	"context"
+	"fmt"
+	"text/template"
+
+	"github.com/go-logr/logr"
+	l "github.com/sirupsen/logrus"
+	apps "k8s.io/api/apps/v1"
+	core "k8s.io/api/core/v1"
+	apiequal "k8s.io/apimachinery/pkg/api/equality"
+	apierrors "k8s.io/apimachinery/pkg/api/errors"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/client-go/tools/record"
+	"k8s.io/client-go/util/retry"
+	ctrl "sigs.k8s.io/controller-runtime"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	runtimelog "sigs.k8s.io/controller-runtime/pkg/log"
+
+	operatorv1alpha1 "github.com/apache/skywalking-swck/operator/apis/operator/v1alpha1"
+	"github.com/apache/skywalking-swck/operator/pkg/kubernetes"
+)
+
+var useImmutableConfigMap = true
+
+// EventExporterReconciler reconciles a EventExporter object
+type EventExporterReconciler struct {
+	client.Client
+	Scheme   *runtime.Scheme
+	FileRepo kubernetes.Repo
+	Recorder record.EventRecorder
+}
+
+// +kubebuilder:rbac:groups=operator.skywalking.apache.org,resources=eventexporters,verbs=get;list;watch;create;update;patch;delete
+// +kubebuilder:rbac:groups=operator.skywalking.apache.org,resources=eventexporters/status,verbs=get;update;patch
+// +kubebuilder:rbac:groups=operator.skywalking.apache.org,resources=eventexporters/finalizers,verbs=update
+// +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete
+// +kubebuilder:rbac:groups="",resources=configmaps,verbs=get;list;watch;create;update;patch;delete
+// +kubebuilder:rbac:groups="",resources=events,verbs=create;patch
+
+func (r *EventExporterReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
+	log := runtimelog.FromContext(ctx)
+	log.Info(fmt.Sprintf("===============eventexporter reconcile started (ns: %s, name: %s)===============", req.Namespace, req.Name))
+
+	eventExporter := operatorv1alpha1.EventExporter{}
+	if err := r.Client.Get(ctx, req.NamespacedName, &eventExporter); err != nil {
+		return ctrl.Result{}, client.IgnoreNotFound(err)
+	}
+
+	newConfigMapName := configMapName(&eventExporter)
+	if _, err := r.overlayData(ctx, log, &eventExporter, newConfigMapName); err != nil {
+		log.Error(err, "failed to delete eventexporter's configMap")
+		return ctrl.Result{}, err
+	}
+
+	ff, err := r.FileRepo.GetFilesRecursive("templates")
+	if err != nil {
+		log.Error(err, "failed to load resource templates")
+		return ctrl.Result{}, err
+	}
+
+	app := kubernetes.Application{
+		Client:   r.Client,
+		FileRepo: r.FileRepo,
+		CR:       &eventExporter,
+		GVK:      operatorv1alpha1.GroupVersion.WithKind("EventExporter"),
+		Recorder: r.Recorder,
+		TmplFunc: template.FuncMap{
+			"configMapName": func() string { return newConfigMapName },
+		},
+	}
+
+	if err := app.ApplyAll(ctx, ff, log); err != nil {
+		return ctrl.Result{}, err
+	}
+
+	if err := r.checkState(ctx, log, &eventExporter); err != nil {
+		l.Error(err, "failed to check sub resources state")
+		return ctrl.Result{}, err
+	}
+
+	return ctrl.Result{RequeueAfter: schedDuration}, nil
+
+}
+
+func (r *EventExporterReconciler) overlayData(ctx context.Context, log logr.Logger,
+	eventExporter *operatorv1alpha1.EventExporter, newConfigMapName string) (changed bool, err error) {
+
+	oldConfigMapName := eventExporter.Status.ConfigMapName
+	if oldConfigMapName == newConfigMapName {
+		log.Info("eventexporter configuration keeps the same as before")
+		return false, nil
+	}
+
+	newConfigMap := core.ConfigMap{
+		ObjectMeta: metav1.ObjectMeta{
+			Namespace: eventExporter.Namespace,
+			Name:      newConfigMapName,
+			OwnerReferences: []metav1.OwnerReference{
+				{
+					APIVersion: eventExporter.APIVersion,
+					Kind:       eventExporter.Kind,
+					Name:       eventExporter.Name,
+					UID:        eventExporter.UID,
+				},
+			},
+			Labels: map[string]string{
+				"app": "eventexporter",
+				"operator.skywalking.apache.org/eventexporter-name": eventExporter.Name,
+				"operator.skywalking.apache.org/application":        "eventexporter",
+				"operator.skywalking.apache.org/component":          "configmap",
+			},
+		},
+		Immutable: &useImmutableConfigMap,
+		Data:      map[string]string{"config.yaml": eventExporter.Spec.Config},
+	}
+
+	if err = r.Client.Create(ctx, &newConfigMap); err != nil {
+		log.Error(err, "failed to create eventexporter's configmap")
+		return true, err
+	}
+
+	oldConfigMap := core.ConfigMap{}
+	err = r.Client.Get(ctx, client.ObjectKey{Namespace: eventExporter.Namespace, Name: oldConfigMapName}, &oldConfigMap)
+	if err != nil && !apierrors.IsNotFound(err) {
+		log.Error(err, "failed to get the eventexporter's configmap")
+		return true, err
+	}
+
+	if !apierrors.IsNotFound(err) {
+		if err = r.Client.Delete(ctx, &oldConfigMap); err != nil {
+			log.Error(err, "failed to delete eventexporter's configmap")
+			return true, err
+		}
+	}
+
+	return true, nil
+}
+
+func (r *EventExporterReconciler) checkState(ctx context.Context, log logr.Logger, eventExporter *operatorv1alpha1.EventExporter) error {
+	overlay := operatorv1alpha1.EventExporterStatus{}
+	deployment := apps.Deployment{}
+	errCol := new(kubernetes.ErrorCollector)
+
+	if err := r.Client.Get(ctx, client.ObjectKey{Namespace: eventExporter.Namespace,
+		Name: eventExporter.Name + "-eventexporter"}, &deployment); err != nil && !apierrors.IsNotFound(err) {
+		errCol.Collect(fmt.Errorf("failed to get deployment: %w", err))
+	} else {
+		overlay.Conditions = deployment.Status.Conditions
+		overlay.AvailableReplicas = deployment.Status.AvailableReplicas
+		overlay.ConfigMapName = configMapName(eventExporter)
+	}
+
+	if apiequal.Semantic.DeepDerivative(overlay, eventExporter.Status) {
+		log.Info("Status keeps the same as before")
+		return errCol.Error()
+	}
+
+	if err := r.updateStatus(ctx, eventExporter, overlay, errCol); err != nil {
+		errCol.Collect(fmt.Errorf("failed to update status of EventExporter: %w", err))
+	}
+
+	log.Info("updated Status sub resource")
+
+	return errCol.Error()
+}
+
+func (r *EventExporterReconciler) updateStatus(ctx context.Context, eventExporter *operatorv1alpha1.EventExporter,
+	overlay operatorv1alpha1.EventExporterStatus, errCol *kubernetes.ErrorCollector) error {
+	// avoid resource conflict
+	return retry.RetryOnConflict(retry.DefaultBackoff, func() error {
+		if err := r.Client.Get(ctx, client.ObjectKey{Name: eventExporter.Name, Namespace: eventExporter.Namespace}, eventExporter); err != nil {
+			errCol.Collect(fmt.Errorf("failed to get EventExporter: %w", err))
+		}
+
+		eventExporter.Status = overlay
+		eventExporter.Kind = "EventExporter"
+		if err := kubernetes.ApplyOverlay(eventExporter, &operatorv1alpha1.EventExporter{Status: overlay}); err != nil {
+			errCol.Collect(fmt.Errorf("failed to apply overlay: %w", err))
+		}
+
+		if err := r.Status().Update(ctx, eventExporter); err != nil {
+			errCol.Collect(fmt.Errorf("failed to update status of EventExporter: %w", err))
+		}
+		return errCol.Error()
+	})
+}
+
+func configMapName(eventExporter *operatorv1alpha1.EventExporter) string {
+	md5 := MD5Hash(eventExporter.Name + eventExporter.Spec.Config)
+	return fmt.Sprintf("eventexporter-cm-%s", md5)
+}
+
+// SetupWithManager sets up the controller with the Manager.
+func (r *EventExporterReconciler) SetupWithManager(mgr ctrl.Manager) error {
+	return ctrl.NewControllerManagedBy(mgr).
+		For(&operatorv1alpha1.EventExporter{}).
+		Complete(r)
+}
diff --git a/operator/main.go b/operator/main.go
index 7ee7408..c048484 100644
--- a/operator/main.go
+++ b/operator/main.go
@@ -170,6 +170,15 @@
 		setupLog.Error(err, "unable to create controller", "controller", "BanyanDB")
 		os.Exit(1)
 	}
+	if err = (&operatorcontrollers.EventExporterReconciler{
+		Client:   mgr.GetClient(),
+		Scheme:   mgr.GetScheme(),
+		FileRepo: manifests.NewRepo("eventexporter"),
+		Recorder: mgr.GetEventRecorderFor("eventexporter-controller"),
+	}).SetupWithManager(mgr); err != nil {
+		setupLog.Error(err, "unable to create controller", "controller", "EventExporter")
+		os.Exit(1)
+	}
 	//+kubebuilder:scaffold:builder
 	if os.Getenv("ENABLE_WEBHOOKS") != "false" {
 		if err = (&operatorv1alpha1.OAPServer{}).SetupWebhookWithManager(mgr); err != nil {
@@ -212,6 +221,10 @@
 			setupLog.Error(err, "unable to create webhook", "webhook", "BanyanDB")
 			os.Exit(1)
 		}
+		if err = (&operatorv1alpha1.EventExporter{}).SetupWebhookWithManager(mgr); err != nil {
+			setupLog.Error(err, "unable to create webhook", "webhook", "EventExporter")
+			os.Exit(1)
+		}
 		// register a webhook to enable the java agent injector
 		setupLog.Info("registering /mutate-v1-pod webhook")
 		mgr.GetWebhookServer().Register("/mutate-v1-pod",
diff --git a/operator/pkg/operator/manifests/eventexporter/templates/cluster_role.yaml b/operator/pkg/operator/manifests/eventexporter/templates/cluster_role.yaml
new file mode 100644
index 0000000..1566e6b
--- /dev/null
+++ b/operator/pkg/operator/manifests/eventexporter/templates/cluster_role.yaml
@@ -0,0 +1,31 @@
+# Licensed to Apache Software Foundation (ASF) under one or more contributor
+# license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright
+# ownership. Apache Software Foundation (ASF) licenses this file to you under
+# the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+kind: ClusterRole
+apiVersion: rbac.authorization.k8s.io/v1
+metadata:
+  name: swck:eventexporter
+  labels:
+    operator.skywalking.apache.org/application: eventexporter
+    operator.skywalking.apache.org/component: rbac
+rules:
+  - apiGroups: [""]
+    resources: ["pods", "endpoints", "configmaps", "events", "services"]
+    verbs: ["get", "watch", "list"]
+  - apiGroups: ["extensions"]
+    resources: ["deployments", "replicasets"]
+    verbs: ["get", "watch", "list"]
diff --git a/operator/pkg/operator/manifests/eventexporter/templates/cluster_role_binding.yaml b/operator/pkg/operator/manifests/eventexporter/templates/cluster_role_binding.yaml
new file mode 100644
index 0000000..e174eb0
--- /dev/null
+++ b/operator/pkg/operator/manifests/eventexporter/templates/cluster_role_binding.yaml
@@ -0,0 +1,32 @@
+# Licensed to Apache Software Foundation (ASF) under one or more contributor
+# license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright
+# ownership. Apache Software Foundation (ASF) licenses this file to you under
+# the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+  name: swck:eventexporter
+  labels:
+    operator.skywalking.apache.org/application: eventexporter
+    operator.skywalking.apache.org/component: rbac
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: ClusterRole
+  name: swck:eventexporter
+subjects:
+  - kind: ServiceAccount
+    name: {{ .Name }}-eventexporter
+    namespace: {{ .Namespace }}
diff --git a/operator/pkg/operator/manifests/eventexporter/templates/deployment.yaml b/operator/pkg/operator/manifests/eventexporter/templates/deployment.yaml
new file mode 100644
index 0000000..3d3629e
--- /dev/null
+++ b/operator/pkg/operator/manifests/eventexporter/templates/deployment.yaml
@@ -0,0 +1,65 @@
+# Licensed to Apache Software Foundation (ASF) under one or more contributor
+# license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright
+# ownership. Apache Software Foundation (ASF) licenses this file to you under
+# the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: {{ .Name }}-eventexporter
+  namespace: {{ .Namespace }}
+  labels:
+    app: eventexporter
+    operator.skywalking.apache.org/eventexporter-name: {{ .Name }}
+    operator.skywalking.apache.org/application: eventexporter
+    operator.skywalking.apache.org/component: deployment
+spec:
+  replicas: {{ .Spec.Replicas }}
+  selector:
+    matchLabels:
+      operator.skywalking.apache.org/eventexporter-name: {{ .Name }}
+  template:
+    metadata:
+      labels:
+        operator.skywalking.apache.org/eventexporter-name: {{ .Name }}
+        operator.skywalking.apache.org/application: eventexporter
+        operator.skywalking.apache.org/component: pod
+    spec:
+      serviceAccountName: {{ .Name }}-eventexporter
+      affinity:
+        podAntiAffinity:
+          preferredDuringSchedulingIgnoredDuringExecution:
+            - weight: 1
+              podAffinityTerm:
+                topologyKey: kubernetes.io/hostname
+                labelSelector:
+                  matchLabels:
+                    app: eventexporter
+                    operator.skywalking.apache.org/eventexporter-name: {{ .Name }}
+      containers:
+        - name: eventexporter
+          image: {{ .Spec.Image }}
+          imagePullPolicy: IfNotPresent
+          args:
+            - start
+            - -v=debug
+            - -c=/data/config.yaml
+          volumeMounts:
+            - mountPath: /data
+              name: config
+      volumes:
+        - name: config
+          configMap:
+            name: {{ configMapName }}
diff --git a/operator/pkg/operator/manifests/eventexporter/templates/service_account.yaml b/operator/pkg/operator/manifests/eventexporter/templates/service_account.yaml
new file mode 100644
index 0000000..1cf5baa
--- /dev/null
+++ b/operator/pkg/operator/manifests/eventexporter/templates/service_account.yaml
@@ -0,0 +1,26 @@
+# Licensed to Apache Software Foundation (ASF) under one or more contributor
+# license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright
+# ownership. Apache Software Foundation (ASF) licenses this file to you under
+# the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  name: {{ .Name }}-eventexporter
+  namespace: {{ .Namespace }}
+  labels:
+    operator.skywalking.apache.org/fetcher-name: {{ .Name }}
+    operator.skywalking.apache.org/application: eventexporter
+    operator.skywalking.apache.org/component: rbac
diff --git a/operator/pkg/operator/manifests/repo.go b/operator/pkg/operator/manifests/repo.go
index 769697f..420cdf5 100644
--- a/operator/pkg/operator/manifests/repo.go
+++ b/operator/pkg/operator/manifests/repo.go
@@ -28,7 +28,7 @@
 
 var _ kubernetes.Repo = &AssetsRepo{}
 
-//go:embed fetcher injector oapserver satellite storage ui banyandb
+//go:embed fetcher injector oapserver satellite storage ui banyandb eventexporter
 var manifests embed.FS
 
 // AssetsRepo provides templates through assets
diff --git a/test/e2e/oap-eventexporter/e2e.yaml b/test/e2e/oap-eventexporter/e2e.yaml
new file mode 100644
index 0000000..9116bd9
--- /dev/null
+++ b/test/e2e/oap-eventexporter/e2e.yaml
@@ -0,0 +1,83 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+setup:
+  env: kind
+  file: ../kind.yaml
+  steps:
+    - name: prepare e2e.yaml
+      command: bash hack/prepare-e2e.sh
+    - name: install cert-manager
+      command: |
+        # kind k8s cluster is in $TMPDIR
+        export KUBECONFIG=$TMPDIR/e2e-k8s.config
+        kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.8.0/cert-manager.yaml
+      wait:
+        - namespace: cert-manager
+          resource: pod
+          for: condition=Ready
+    - name: install operator
+      command: |
+        export OPERATOR_IMG=controller
+        make -C operator docker-build   
+        kind load docker-image controller
+        make -C operator install
+        make -C operator deploy
+      wait:
+        - namespace: skywalking-swck-system
+          resource: pod
+          for: condition=Ready
+    - name: setup oapserver and eventexporter
+      command: |
+        kubectl create namespace skywalking-system
+        kubectl apply -f test/e2e/skywalking-components-with-eventexporter.yaml
+      wait:
+        - namespace: skywalking-system
+          resource: OAPServer/skywalking-system
+          for: condition=Available
+        - namespace: skywalking-system
+          resource: EventExporter/skywalking-system
+          for: condition=Available
+    - name: setup java agent demo
+      command: |
+        kubectl label namespace skywalking-system swck-injection=enabled
+        sed 's/oap-service/skywalking-system-oap.skywalking-system/' test/e2e/demo.yaml | kubectl create -f -
+      wait:
+        - namespace: skywalking-system
+          resource: deployment/demo
+          for: condition=Available
+
+  kind:
+    expose-ports:
+      - namespace: skywalking-system
+        resource: service/skywalking-system-oap
+        port: 12800
+  timeout: 20m
+
+cleanup:
+  # always never success failure
+  on: always
+
+verify:
+  # verify with retry strategy
+  retry:
+    # max retry count
+    count: 10
+    # the interval between two attempts, e.g. 10s, 1m.
+    interval: 10s
+  cases:
+    # test eventexporter
+    - query: 'swctl --display yaml --base-url=http://${service_skywalking_system_oap_host}:${service_skywalking_system_oap_12800}/graphql event ls | yq e ''.events | map({"service": .source.service, "name": .name})'''
+      expected: ../verify/eventexporter-event.yaml
\ No newline at end of file
diff --git a/test/e2e/skywalking-components-with-eventexporter.yaml b/test/e2e/skywalking-components-with-eventexporter.yaml
new file mode 100644
index 0000000..484c059
--- /dev/null
+++ b/test/e2e/skywalking-components-with-eventexporter.yaml
@@ -0,0 +1,62 @@
+# Licensed to Apache Software Foundation (ASF) under one or more contributor
+# license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright
+# ownership. Apache Software Foundation (ASF) licenses this file to you under
+# the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+apiVersion: operator.skywalking.apache.org/v1alpha1
+kind: OAPServer
+metadata:
+  name: skywalking-system
+  namespace: skywalking-system
+spec:
+  version: 9.5.0
+  instances: 1
+  image: apache/skywalking-oap-server:9.5.0
+  service:
+    template:
+      type: ClusterIP
+
+---
+apiVersion: operator.skywalking.apache.org/v1alpha1
+kind: EventExporter
+metadata:
+  name: skywalking-system
+  namespace: skywalking-system
+spec:
+  replicas: 1
+  config: |
+    filters:
+      - reason: ""     
+        message: ""    
+        minCount: 1    
+        type: ""       
+        action: ""     
+        kind: "Pod|Service"
+        namespace: "^skywalking-system$"  
+        name: ""       
+        service: "[^\\s]{1,}"  
+        exporters:     
+          - skywalking 
+    exporters:         
+      skywalking:      
+        template:      
+          source:
+            service: "{{ .Service.Name }}"
+            serviceInstance: "{{ .Pod.Name }}"
+            endpoint: ""
+          message: "{{ .Event.Message }}" 
+        address: "skywalking-system-oap.skywalking-system:11800"
+
+    
\ No newline at end of file
diff --git a/test/e2e/verify/eventexporter-event.yaml b/test/e2e/verify/eventexporter-event.yaml
new file mode 100644
index 0000000..015f1c8
--- /dev/null
+++ b/test/e2e/verify/eventexporter-event.yaml
@@ -0,0 +1,23 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+{{- contains . }}
+- service: demo
+  name: Pulled
+- service: demo
+  name: Started
+- service: demo
+  name: Created
+{{- end}}
\ No newline at end of file