You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@skywalking.apache.org by wu...@apache.org on 2020/12/03 03:46:00 UTC

[skywalking-swck] branch master updated: Introduce templates into operator (#13)

This is an automated email from the ASF dual-hosted git repository.

wusheng pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/skywalking-swck.git


The following commit(s) were added to refs/heads/master by this push:
     new 0a628f8  Introduce templates into operator (#13)
0a628f8 is described below

commit 0a628f8fab137ed28db90ea135af3152237c3fb2
Author: Gao Hongtao <ha...@gmail.com>
AuthorDate: Thu Dec 3 11:45:52 2020 +0800

    Introduce templates into operator (#13)
    
    * Introduce templates
    
    Signed-off-by: Gao Hongtao <ha...@gmail.com>
    
    * Update docs
    
    Signed-off-by: Gao Hongtao <ha...@gmail.com>
---
 .gitignore                                         |   1 +
 CONTRIBUTING.md                                    |  12 +-
 Makefile                                           |  24 +-
 README.md                                          |   4 +-
 cmd/manager/manager.go                             |   8 +-
 config/operator/rbac/role.yaml                     |  14 +
 controllers/operator/oapserver_controller.go       | 277 +++-------
 {doc => docs}/custom-metrics-adapter.md            |   0
 {doc => docs}/design/proposal.md                   |   0
 {doc => docs}/operator.md                          |   8 +
 {doc => docs}/release.md                           |   0
 go.mod                                             |   3 +
 go.sum                                             |  19 +
 golangci.yml                                       |   2 +-
 hack/build-header.sh                               |  30 ++
 hack/run_update_templates.sh                       |  49 ++
 pkg/kubernetes/apply.go                            | 121 +++++
 pkg/kubernetes/kubernetes.go                       |  24 +
 .../oapserver/templates/cluster_role.yaml          |  61 +--
 .../oapserver/templates/cluster_role_binding.yaml  |  64 +--
 .../manifests/oapserver/templates/deployment.yaml  |  86 ++++
 .../manifests/oapserver/templates/service.yaml     |  72 +--
 .../oapserver/templates/service_account.yaml       |  59 +--
 pkg/operator/repo/assets.gen.go                    | 564 +++++++++++++++++++++
 .../kubernetes.go => operator/repo/repo.go}        |  35 +-
 25 files changed, 1097 insertions(+), 440 deletions(-)

diff --git a/.gitignore b/.gitignore
index f3064b8..b4df9fb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -27,3 +27,4 @@ apiserver.local*
 *~
 
 .DS_Store
+.env
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index e8eebd2..cc1802c 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -68,14 +68,14 @@ This section guides committers and PMC members to release SkyWalking Cloud on Ku
 ### Prerequisites
 - [x] [GNU Make](https://www.gnu.org/software/make/manual/make.html) is installed
 - [x] [GPG tool](https://gpgtools.org) is installed
-- [x] [Add your GPG key](doc/release.md#add-your-gpg-public-key)
+- [x] [Add your GPG key](docs/release.md#add-your-gpg-public-key)
 
 ### Release steps
 - Export the version that is to be released, `export VERSION=0.1.0 `
 - Tag the latest commit that is to be released with `git tag ${VERSION}` and push the tag with `git push https://github.com/apache/skywalking-swck ${VERSION}`
 - Verify licenses, build and sign distribution packages, simply run `make release`, distribution packages and checksums are generated
-- [Upload the packages to SVN repository](doc/release.md#upload-to-apache-svn) 
-- [Send internal announcement](doc/release.md#make-the-internal-announcements)
-- [Wait at least 48 hours for test responses](doc/release.md#wait-at-least-48-hours-for-test-responses)
-- [Call for vote](doc/release.md#call-a-vote-in-dev)
-- [Publish release](doc/release.md#publish-release)
+- [Upload the packages to SVN repository](docs/release.md#upload-to-apache-svn) 
+- [Send internal announcement](docs/release.md#make-the-internal-announcements)
+- [Wait at least 48 hours for test responses](docs/release.md#wait-at-least-48-hours-for-test-responses)
+- [Call for vote](docs/release.md#call-a-vote-in-dev)
+- [Publish release](docs/release.md#publish-release)
diff --git a/Makefile b/Makefile
index 186bf5f..9e1b446 100644
--- a/Makefile
+++ b/Makefile
@@ -27,6 +27,15 @@ else
 GOBIN=$(shell go env GOBIN)
 endif
 
+ARCH := $(shell uname)
+OSNAME := $(if $(findstring Darwin,$(ARCH)),darwin,linux)
+GOBINDATA_VERSION := v3.21.0
+
+# import local settings
+ifneq (,$(wildcard ./.env))
+    include .env
+    export
+endif
 
 all: operator adapter
 
@@ -84,6 +93,9 @@ $(GO_LICENSER):
 license: $(GO_LICENSER)
 	$(GO_LICENSER) -d -licensor='Apache Software Foundation (ASF)' -exclude=apis/operator/v1alpha1/zz_generated* .
 	go run github.com/apache/skywalking-swck/cmd/build license check config
+	go run github.com/apache/skywalking-swck/cmd/build license check pkg/operator/manifests
+
+.PHONY: license
 
 # Build the docker image
 operator-docker-build:
@@ -133,7 +145,7 @@ format: $(GOIMPORTS) ## Format all Go code
 	$(GOIMPORTS) -w -local github.com/apache/skywalking-swck .
 
 ## Check that the status is consistent with CI.
-check: generate operator-manifests license
+check: generate operator-manifests update-templates license
 	$(MAKE) format
 	mkdir -p /tmp/artifacts
 	git diff >/tmp/artifacts/check.diff 2>&1
@@ -155,6 +167,16 @@ lint: $(LINTER)
 
 .PHONY: lint
 
+GO_BINDATA := $(GOBIN)/go-bindata
+$(GO_BINDATA):
+	curl --location --output $(GO_BINDATA) https://github.com/kevinburke/go-bindata/releases/download/v3.21.0/go-bindata-$(OSNAME)-amd64 \
+		&& chmod +x $(GO_BINDATA)
+		
+update-templates: $(GO_BINDATA)
+	@echo updating charts
+	-hack/run_update_templates.sh
+	-hack/build-header.sh pkg/operator/repo/assets.gen.go
+
 release-operator: generate
 	CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GO111MODULE=on go build -a -o build/bin/manager-linux-amd64 cmd/manager/manager.go
 
diff --git a/README.md b/README.md
index 2687acb..501666a 100644
--- a/README.md
+++ b/README.md
@@ -26,7 +26,7 @@ SWCK is a platform for the SkyWalking user, provisions, upgrades, maintains SkyW
  kubectl apply -f release/operator/config
  ```
 
-For more details, please refer to [deploy operator](doc/operator.md)
+For more details, please refer to [deploy operator](docs/operator.md)
 
 ## Custom Metrics Adapter
   
@@ -37,7 +37,7 @@ For more details, please refer to [deploy operator](doc/operator.md)
  kubectl apply -f release/adapter/config
  ```
 
-For more details, please read [Custom metrics adapter](doc/custom-metrics-adapter.md)
+For more details, please read [Custom metrics adapter](docs/custom-metrics-adapter.md)
 
 # Contributing
 For developers who want to contribute to this project, see [Contribution Guide](CONTRIBUTING.md)
diff --git a/cmd/manager/manager.go b/cmd/manager/manager.go
index e94beab..18ae64e 100644
--- a/cmd/manager/manager.go
+++ b/cmd/manager/manager.go
@@ -29,6 +29,7 @@ import (
 
 	operatorv1alpha1 "github.com/apache/skywalking-swck/apis/operator/v1alpha1"
 	controllers "github.com/apache/skywalking-swck/controllers/operator"
+	"github.com/apache/skywalking-swck/pkg/operator/repo"
 	// +kubebuilder:scaffold:imports
 )
 
@@ -70,9 +71,10 @@ func main() {
 	}
 
 	if err = (&controllers.OAPServerReconciler{
-		Client: mgr.GetClient(),
-		Log:    ctrl.Log.WithName("controllers").WithName("OAPServer"),
-		Scheme: mgr.GetScheme(),
+		Client:   mgr.GetClient(),
+		Log:      ctrl.Log.WithName("controllers").WithName("OAPServer"),
+		Scheme:   mgr.GetScheme(),
+		FileRepo: repo.NewRepo("oapserver"),
 	}).SetupWithManager(mgr); err != nil {
 		setupLog.Error(err, "unable to create controller", "controller", "OAPServer")
 		os.Exit(1)
diff --git a/config/operator/rbac/role.yaml b/config/operator/rbac/role.yaml
index 9c7c975..12ce464 100644
--- a/config/operator/rbac/role.yaml
+++ b/config/operator/rbac/role.yaml
@@ -26,6 +26,7 @@ rules:
 - apiGroups:
   - ""
   resources:
+  - serviceaccounts
   - services
   verbs:
   - create
@@ -67,4 +68,17 @@ rules:
   - get
   - patch
   - update
+- apiGroups:
+  - rbac.authorization.k8s.io
+  resources:
+  - clusterrolebindings
+  - clusterroles
+  verbs:
+  - create
+  - delete
+  - get
+  - list
+  - patch
+  - update
+  - watch
 
diff --git a/controllers/operator/oapserver_controller.go b/controllers/operator/oapserver_controller.go
index d3e8329..ab91f6a 100644
--- a/controllers/operator/oapserver_controller.go
+++ b/controllers/operator/oapserver_controller.go
@@ -20,16 +20,15 @@ package controllers
 import (
 	"context"
 	"fmt"
+	"text/template"
 	"time"
 
 	"github.com/go-logr/logr"
 	apps "k8s.io/api/apps/v1"
 	core "k8s.io/api/core/v1"
+	rbac "k8s.io/api/rbac/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/apimachinery/pkg/util/intstr"
 	ctrl "sigs.k8s.io/controller-runtime"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 
@@ -45,14 +44,16 @@ var rushModeSchedDuration, _ = time.ParseDuration("5s")
 // OAPServerReconciler reconciles a OAPServer object
 type OAPServerReconciler struct {
 	client.Client
-	Log    logr.Logger
-	Scheme *runtime.Scheme
+	Log      logr.Logger
+	Scheme   *runtime.Scheme
+	FileRepo kubernetes.Repo
 }
 
 // +kubebuilder:rbac:groups=operator.skywalking.apache.org,resources=oapservers,verbs=get;list;watch;create;update;patch;delete
 // +kubebuilder:rbac:groups=operator.skywalking.apache.org,resources=oapservers/status,verbs=get;update;patch
 // +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete
-// +kubebuilder:rbac:groups="",resources=services,verbs=get;list;watch;create;update;patch;delete
+// +kubebuilder:rbac:groups="",resources=services;serviceaccounts,verbs=get;list;watch;create;update;patch;delete
+// +kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=clusterroles;clusterrolebindings,verbs=get;list;watch;create;update;patch;delete
 
 func (r *OAPServerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
 	log := r.Log.WithValues("oapserver", req.NamespacedName)
@@ -62,56 +63,84 @@ func (r *OAPServerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
 	if err := r.Client.Get(ctx, req.NamespacedName, &oapServer); err != nil {
 		return ctrl.Result{}, client.IgnoreNotFound(err)
 	}
-	serviceName := oapServer.Name
-	if err := r.service(ctx, log, serviceName, &oapServer); err != nil {
+	app := kubernetes.Application{
+		Log:      r.Log,
+		Client:   r.Client,
+		FileRepo: r.FileRepo,
+		CR:       &oapServer,
+		GVK:      operatorv1alpha1.GroupVersion.WithKind("OAPServer"),
+	}
+	if err := app.Apply(ctx, kubernetes.K8SObj{
+		Name:      "service_account",
+		Key:       client.ObjectKey{Namespace: oapServer.Namespace, Name: oapServer.Name + "-oap"},
+		Prototype: &core.ServiceAccount{},
+	}); err != nil {
 		return ctrl.Result{}, err
 	}
-
-	deploymentName := oapServer.Name
-	if err := r.deployment(ctx, log, deploymentName, &oapServer); err != nil {
+	if err := app.Apply(ctx, kubernetes.K8SObj{
+		Name:      "cluster_role",
+		Key:       client.ObjectKey{Name: "swck:oapserver"},
+		Prototype: &rbac.ClusterRole{},
+	}); err != nil {
 		return ctrl.Result{}, err
 	}
-
-	r.istio(ctx, log, serviceName, &oapServer)
-
-	return ctrl.Result{RequeueAfter: r.checkState(ctx, log, &oapServer, serviceName, deploymentName)}, nil
-}
-
-func (r *OAPServerReconciler) deployment(ctx context.Context, log logr.Logger, deploymentName string, oapServer *operatorv1alpha1.OAPServer) error {
-	currentDeploy := apps.Deployment{}
-	err := r.Client.Get(ctx, client.ObjectKey{Namespace: oapServer.Namespace, Name: deploymentName}, &currentDeploy)
-	if apierrors.IsNotFound(err) {
-		log.Info("could not find existing Deployment, creating one...")
-
-		currentDeploy = *buildDeployment(oapServer, deploymentName)
-		if err = r.Client.Create(ctx, &currentDeploy); err != nil {
-			return err
-		}
-
-		log.Info("created Deployment resource")
-		return nil
-	}
-	if err != nil {
-		return err
+	if err := app.Apply(ctx, kubernetes.K8SObj{
+		Name:      "cluster_role_binding",
+		Key:       client.ObjectKey{Name: "swck:oapserver"},
+		Prototype: &rbac.ClusterRoleBinding{},
+	}); err != nil {
+		return ctrl.Result{}, err
 	}
-
-	deployment := buildDeployment(oapServer, deploymentName)
-	if apiequal.Semantic.DeepDerivative(deployment.Spec, currentDeploy.Spec) {
-		log.Info("Deployment keeps the same as before")
-		return nil
+	if err := app.Apply(ctx, kubernetes.K8SObj{
+		Name:      "service",
+		Key:       client.ObjectKey{Namespace: oapServer.Namespace, Name: oapServer.Name},
+		Prototype: &core.Service{},
+		Extract: func(obj client.Object) interface{} {
+			return obj.(*core.Service).Spec
+		},
+	}); err != nil {
+		return ctrl.Result{}, err
 	}
-	if err := r.Client.Update(ctx, deployment); err != nil {
-		return err
+	if err := app.Apply(ctx, kubernetes.K8SObj{
+		Name:      "deployment",
+		Key:       client.ObjectKey{Namespace: oapServer.Namespace, Name: oapServer.Name},
+		Prototype: &apps.Deployment{},
+		TmplFunc: template.FuncMap{
+			"generateImage": func() string {
+				image := oapServer.Spec.Image
+				if image == "" {
+					v := oapServer.Spec.Version
+					vTmpl := "apache/skywalking-oap-server:%s-%s"
+					vES := "es6"
+					for _, e := range oapServer.Spec.Config {
+						if e.Name != "SW_STORAGE" {
+							continue
+						}
+						if e.Value == "elasticsearch7" {
+							vES = "es7"
+						}
+					}
+					image = fmt.Sprintf(vTmpl, v, vES)
+				}
+				return image
+			},
+		},
+		Extract: func(obj client.Object) interface{} {
+			return obj.(*apps.Deployment).Spec
+		},
+	}); err != nil {
+		return ctrl.Result{}, err
 	}
-	log.Info("updated Deployment resource")
-	return nil
+	r.istio(ctx, log, oapServer.Name, &oapServer)
+
+	return ctrl.Result{RequeueAfter: r.checkState(ctx, log, &oapServer, oapServer.Name)}, nil
 }
 
-func (r *OAPServerReconciler) checkState(ctx context.Context, log logr.Logger, oapServer *operatorv1alpha1.OAPServer, serviceName, deploymentName string) time.Duration {
+func (r *OAPServerReconciler) checkState(ctx context.Context, log logr.Logger, oapServer *operatorv1alpha1.OAPServer, name string) time.Duration {
 	overlay := operatorv1alpha1.OAPServerStatus{}
 	deployment := apps.Deployment{}
 	nextSchedule := schedDuration
-	if err := r.Client.Get(ctx, client.ObjectKey{Namespace: oapServer.Namespace, Name: deploymentName}, &deployment); err != nil {
+	if err := r.Client.Get(ctx, client.ObjectKey{Namespace: oapServer.Namespace, Name: name}, &deployment); err != nil {
 		nextSchedule = rushModeSchedDuration
 	} else {
 		overlay.Conditions = deployment.Status.Conditions
@@ -129,7 +158,7 @@ func (r *OAPServerReconciler) checkState(ctx context.Context, log logr.Logger, o
 		}
 	}
 	service := core.Service{}
-	if err := r.Client.Get(ctx, client.ObjectKey{Namespace: oapServer.Namespace, Name: serviceName}, &service); err != nil {
+	if err := r.Client.Get(ctx, client.ObjectKey{Namespace: oapServer.Namespace, Name: name}, &service); err != nil {
 		nextSchedule = rushModeSchedDuration
 	} else {
 		overlay.Address = fmt.Sprintf("%s.%s", service.Name, service.Namespace)
@@ -167,164 +196,6 @@ func (r *OAPServerReconciler) istio(ctx context.Context, log logr.Logger, servic
 	}
 }
 
-func buildDeployment(oapServer *operatorv1alpha1.OAPServer, deploymentName string) *apps.Deployment {
-	podSpec := &core.PodTemplateSpec{}
-
-	podSpec.ObjectMeta.Labels = labelSelector(oapServer)
-
-	pod := &podSpec.Spec
-	image := oapServer.Spec.Image
-	if image == "" {
-		v := oapServer.Spec.Version
-		vTmpl := "apache/skywalking-oap-server:%s-%s"
-		vES := "es6"
-		for _, e := range oapServer.Spec.Config {
-			if e.Name != "SW_STORAGE" {
-				continue
-			}
-			if e.Value == "elasticsearch7" {
-				vES = "es7"
-			}
-		}
-		image = fmt.Sprintf(vTmpl, v, vES)
-	}
-	probe := &core.Probe{
-		Handler: core.Handler{
-			Exec: &core.ExecAction{
-				Command: []string{"/skywalking/bin/swctl", "ch"},
-			},
-		},
-		InitialDelaySeconds: 40,
-		TimeoutSeconds:      10,
-		PeriodSeconds:       30,
-		SuccessThreshold:    1,
-		FailureThreshold:    3,
-	}
-	cc := oapServer.Spec.Config
-	if cc == nil {
-		cc = []core.EnvVar{}
-	}
-	cc = append(cc, core.EnvVar{Name: "SW_TELEMETRY", Value: "prometheus"})
-	cc = append(cc, core.EnvVar{Name: "SW_HEALTH_CHECKER", Value: "default"})
-	pod.Containers = []core.Container{
-		{
-			Name:  "oap",
-			Image: image,
-			Env:   cc,
-			Ports: []core.ContainerPort{
-				{
-					Name:          "grpc",
-					ContainerPort: 11800,
-					Protocol:      core.ProtocolTCP,
-				},
-				{
-					Name:          "graphql",
-					ContainerPort: 12800,
-					Protocol:      core.ProtocolTCP,
-				},
-				{
-					Name:          "http-monitoring",
-					ContainerPort: 1234,
-					Protocol:      core.ProtocolTCP,
-				},
-			},
-			LivenessProbe:  probe,
-			ReadinessProbe: probe,
-		},
-	}
-
-	deployment := apps.Deployment{
-		ObjectMeta: metav1.ObjectMeta{
-			Name:            deploymentName,
-			Namespace:       oapServer.Namespace,
-			OwnerReferences: []metav1.OwnerReference{*metav1.NewControllerRef(oapServer, operatorv1alpha1.GroupVersion.WithKind("OAPServer"))},
-		},
-		Spec: apps.DeploymentSpec{
-			Replicas: &oapServer.Spec.Instances,
-			Selector: &metav1.LabelSelector{
-				MatchLabels: labelSelector(oapServer),
-			},
-			MinReadySeconds: 5,
-			Template:        *podSpec,
-		},
-	}
-	return &deployment
-}
-
-func (r *OAPServerReconciler) service(ctx context.Context, log logr.Logger, serviceName string, oapServer *operatorv1alpha1.OAPServer) error {
-	currentService := core.Service{}
-	err := r.Client.Get(ctx, client.ObjectKey{Namespace: oapServer.Namespace, Name: serviceName}, &currentService)
-	if apierrors.IsNotFound(err) {
-		log.Info("could not find existing Service, creating one...")
-		currentService = buildService(serviceName, nil, oapServer)
-		if err = r.Client.Create(ctx, &currentService); err != nil {
-			return err
-		}
-		return nil
-	}
-	if err != nil {
-		return err
-	}
-
-	service := buildService(serviceName, currentService.DeepCopy(), oapServer)
-
-	if apiequal.Semantic.DeepDerivative(service.Spec, currentService.Spec) {
-		log.Info("Service keeps the same as before")
-		return nil
-	}
-	if err := r.Client.Update(ctx, &service); err != nil {
-		return err
-	}
-	log.Info("updated Service resource")
-	return nil
-}
-
-func buildService(serviceName string, base *core.Service, oapServer *operatorv1alpha1.OAPServer) core.Service {
-	s := core.Service{}
-	s.Name = serviceName
-	s.Namespace = oapServer.Namespace
-	s.ObjectMeta = metav1.ObjectMeta{
-		Name:            serviceName,
-		Namespace:       oapServer.Namespace,
-		OwnerReferences: []metav1.OwnerReference{*metav1.NewControllerRef(oapServer, operatorv1alpha1.GroupVersion.WithKind("OAPServer"))},
-	}
-	s.Spec = core.ServiceSpec{
-		Type:     core.ServiceTypeClusterIP,
-		Selector: labelSelector(oapServer),
-		Ports: []core.ServicePort{
-			{
-				Name:     "grpc",
-				Port:     11800,
-				Protocol: core.ProtocolTCP,
-				TargetPort: intstr.IntOrString{
-					Type:   intstr.String,
-					StrVal: "grpc",
-				},
-			},
-			{
-				Name:     "graphql",
-				Port:     12800,
-				Protocol: core.ProtocolTCP,
-				TargetPort: intstr.IntOrString{
-					Type:   intstr.String,
-					StrVal: "graphql",
-				},
-			},
-		},
-	}
-	if base == nil {
-		return s
-	}
-	if err := kubernetes.ApplyOverlay(base, &s); err != nil {
-		return s
-	}
-	return *base
-}
-
-func labelSelector(oapServer *operatorv1alpha1.OAPServer) map[string]string {
-	return map[string]string{fmt.Sprintf("%s/oap-server-name", operatorv1alpha1.GroupVersion.Group): oapServer.Name}
-}
-
 func (r *OAPServerReconciler) SetupWithManager(mgr ctrl.Manager) error {
 	return ctrl.NewControllerManagedBy(mgr).
 		For(&operatorv1alpha1.OAPServer{}).
diff --git a/doc/custom-metrics-adapter.md b/docs/custom-metrics-adapter.md
similarity index 100%
rename from doc/custom-metrics-adapter.md
rename to docs/custom-metrics-adapter.md
diff --git a/doc/design/proposal.md b/docs/design/proposal.md
similarity index 100%
rename from doc/design/proposal.md
rename to docs/design/proposal.md
diff --git a/doc/operator.md b/docs/operator.md
similarity index 78%
rename from doc/operator.md
rename to docs/operator.md
index 5f51d80..b57d4b7 100644
--- a/doc/operator.md
+++ b/docs/operator.md
@@ -47,3 +47,11 @@ kubectl --namespace skywalking-swck-system get pods
 # pull the logs
 kubectl --namespace skywalking-swck-system logs -f [name_of_the_controller_pod]
 ```
+
+
+## Custom manifests templates
+
+If you want to custom the manifests templates to generate dedicated Kubernetes resources,
+please edit YAMLs in `pkg/operator/manifests`.
+After saving your changes, issue `make update-templates` to transfer them to binary assets.
+The last step is to rebuild `operator` by `make operator-docker-build`.
diff --git a/doc/release.md b/docs/release.md
similarity index 100%
rename from doc/release.md
rename to docs/release.md
diff --git a/go.mod b/go.mod
index 4c74ec9..9877ff3 100644
--- a/go.mod
+++ b/go.mod
@@ -3,8 +3,10 @@ module github.com/apache/skywalking-swck
 go 1.14
 
 require (
+	github.com/Masterminds/sprig/v3 v3.1.0
 	github.com/apache/skywalking-cli v0.0.0-20201125155244-ffee47d2e83d
 	github.com/evanphx/json-patch v4.9.0+incompatible
+	github.com/ghodss/yaml v1.0.0
 	github.com/go-logr/logr v0.2.1
 	github.com/kubernetes-sigs/custom-metrics-apiserver v0.0.0-20201110135240-8c12d6d92362
 	github.com/machinebox/graphql v0.2.2
@@ -12,6 +14,7 @@ require (
 	github.com/spf13/cobra v1.0.0
 	github.com/urfave/cli v1.22.1
 	k8s.io/api v0.19.3
+	k8s.io/apiextensions-apiserver v0.19.3 // indirect
 	k8s.io/apimachinery v0.19.3
 	k8s.io/apiserver v0.19.3
 	k8s.io/client-go v0.19.3
diff --git a/go.sum b/go.sum
index 155f5fc..7502c13 100644
--- a/go.sum
+++ b/go.sum
@@ -30,6 +30,12 @@ github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
 github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
+github.com/Masterminds/goutils v1.1.0 h1:zukEsf/1JZwCMgHiK3GZftabmxiCw4apj3a28RPBiVg=
+github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
+github.com/Masterminds/semver/v3 v3.1.0 h1:Y2lUDsFKVRSYGojLJ1yLxSXdMmMYTYls0rCvoqmMUQk=
+github.com/Masterminds/semver/v3 v3.1.0/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
+github.com/Masterminds/sprig/v3 v3.1.0 h1:j7GpgZ7PdFqNsmncycTHsLmVPf5/3wJtlgW9TNDYD9Y=
+github.com/Masterminds/sprig/v3 v3.1.0/go.mod h1:ONGMf7UfYGAbMXCZmQLy8x3lCDIPrEZE/rU8pmrbihA=
 github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46 h1:lsxEuwrXEAokXB9qhlbKWPpo3KMLZQ5WB5WLQRW1uq0=
 github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
 github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
@@ -126,6 +132,7 @@ github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4
 github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
 github.com/gdamore/tcell v1.3.0/go.mod h1:Hjvr+Ofd+gLglo7RYKxxnzCBmev3BzsS67MebKS4zMM=
 github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
+github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
 github.com/gizak/termui/v3 v3.1.0/go.mod h1:bXQEBkJpzxUAKf0+xq9MSWAvWZlE7c+aidmyFlkYTrY=
 github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
@@ -285,8 +292,11 @@ github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2p
 github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
 github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
 github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
+github.com/huandu/xstrings v1.3.1 h1:4jgBlKK6tLKFvO8u5pmYjG91cqytmDCDvGh7ECVFfFs=
+github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
 github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
 github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
+github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
 github.com/imdario/mergo v0.3.10 h1:6q5mVkdH/vYmqngx7kZQTjJ5HRsx+ImorDIEQ+beJgc=
 github.com/imdario/mergo v0.3.10/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
 github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
@@ -346,6 +356,8 @@ github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182aff
 github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
 github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
 github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
+github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ=
+github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
 github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
 github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
 github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
@@ -355,6 +367,8 @@ github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0Qu
 github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
 github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
 github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY=
+github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
 github.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo=
 github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
@@ -454,6 +468,8 @@ github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B
 github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc=
 github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
 github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
+github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
+github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
 github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
 github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8=
 github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
@@ -533,6 +549,7 @@ golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8U
 golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -781,6 +798,8 @@ k8s.io/api v0.19.3 h1:GN6ntFnv44Vptj/b+OnMW7FmzkpDoIDLZRvKX3XH9aU=
 k8s.io/api v0.19.3/go.mod h1:VF+5FT1B74Pw3KxMdKyinLo+zynBaMBiAfGMuldcNDs=
 k8s.io/apiextensions-apiserver v0.19.2 h1:oG84UwiDsVDu7dlsGQs5GySmQHCzMhknfhFExJMz9tA=
 k8s.io/apiextensions-apiserver v0.19.2/go.mod h1:EYNjpqIAvNZe+svXVx9j4uBaVhTB4C94HkY3w058qcg=
+k8s.io/apiextensions-apiserver v0.19.3 h1:WZxBypSHW4SdXHbdPTS/Jy7L2la6Niggs8BuU5o+avo=
+k8s.io/apiextensions-apiserver v0.19.3/go.mod h1:igVEkrE9TzInc1tYE7qSqxaLg/rEAp6B5+k9Q7+IC8Q=
 k8s.io/apimachinery v0.19.2/go.mod h1:DnPGDnARWFvYa3pMHgSxtbZb7gpzzAZ1pTfaUNDVlmA=
 k8s.io/apimachinery v0.19.3 h1:bpIQXlKjB4cB/oNpnNnV+BybGPR7iP5oYpsOTEJ4hgc=
 k8s.io/apimachinery v0.19.3/go.mod h1:DnPGDnARWFvYa3pMHgSxtbZb7gpzzAZ1pTfaUNDVlmA=
diff --git a/golangci.yml b/golangci.yml
index 2f79cbb..2f43dd7 100644
--- a/golangci.yml
+++ b/golangci.yml
@@ -46,7 +46,7 @@ run:
   deadline: 10m
   skip-dirs:
     - config
-    - doc
+    - docs
 issues:
   exclude:
     # staticcheck
diff --git a/hack/build-header.sh b/hack/build-header.sh
new file mode 100755
index 0000000..07e2699
--- /dev/null
+++ b/hack/build-header.sh
@@ -0,0 +1,30 @@
+#!/usr/bin/env bash
+
+#
+# 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.
+
+set -ex
+
+SCRIPTDIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
+
+[[ -z "$1" ]] && { echo  "\nPlease call '$0 <file path>' to run this command!\n"; exit 1; }
+TARGET=$1
+OUT=$(mktemp) || { echo "Failed to create temp file"; exit 1; }
+
+cat "${SCRIPTDIR}/boilerplate.go.txt" > "${OUT}"
+echo "" >> "${OUT}"
+cat "${TARGET}" >> "${OUT}"
+mv "${OUT}" "${TARGET}"
diff --git a/hack/run_update_templates.sh b/hack/run_update_templates.sh
new file mode 100755
index 0000000..3f93135
--- /dev/null
+++ b/hack/run_update_templates.sh
@@ -0,0 +1,49 @@
+#!/usr/bin/env bash
+
+#
+# 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.
+#
+
+set -ue
+
+GOBINDATA="${LOCAL_GOBINDATA-go-bindata}"
+
+OUT_DIR=$(mktemp -d -t swck-templates.XXXXXXXXXX) || { echo "Failed to create temp file"; exit 1; }
+
+SCRIPTPATH="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
+ROOTDIR="${SCRIPTPATH}/.."
+
+OPERATOR_DIR="${ROOTDIR}"/pkg/operator
+INSTALLER_DIR="${OPERATOR_DIR}"/manifests
+GEN_PATH="${OPERATOR_DIR}"/repo/assets.gen.go
+
+rm -f "${GEN_PATH}"
+mkdir -p "${OUT_DIR}"
+
+for comp in $(ls ${INSTALLER_DIR});
+do
+    cp -Rf "${INSTALLER_DIR}"/"${comp}" "${OUT_DIR}"
+done
+
+set +u
+
+cd "${OUT_DIR}"
+set +e
+rm -f *.bak
+set -e
+"${GOBINDATA}" --nocompress --nometadata --pkg repo -o "${GEN_PATH}" ./...
+
+rm -Rf "${OUT_DIR}"
diff --git a/pkg/kubernetes/apply.go b/pkg/kubernetes/apply.go
new file mode 100644
index 0000000..ad73220
--- /dev/null
+++ b/pkg/kubernetes/apply.go
@@ -0,0 +1,121 @@
+// 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 kubernetes
+
+import (
+	"context"
+	"fmt"
+	"text/template"
+
+	"github.com/go-logr/logr"
+	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/schema"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+// Repo provides tools to access templates
+type Repo interface {
+	ReadFile(path string) ([]byte, error)
+}
+
+// Application contains the resource of one single component which is applied to api server
+type Application struct {
+	client.Client
+	CR       client.Object
+	Log      logr.Logger
+	FileRepo Repo
+	GVK      schema.GroupVersionKind
+}
+
+// K8SObj is a sub template of an Application
+type K8SObj struct {
+	Name      string
+	Key       client.ObjectKey
+	Prototype client.Object
+	TmplFunc  template.FuncMap
+	Extract   ExtractFunc
+}
+
+type ExtractFunc func(obj client.Object) interface{}
+
+// Apply a template represents a component to api server
+func (a *Application) Apply(ctx context.Context, input K8SObj) error {
+	log := a.Log.WithName(input.Name)
+	manifests, err := a.FileRepo.ReadFile(fmt.Sprintf("%s.yaml", input.Name))
+	if err != nil {
+		return err
+	}
+	overlay := input.Prototype.DeepCopyObject().(client.Object)
+	current := input.Prototype.DeepCopyObject().(client.Object)
+	err = a.Get(ctx, input.Key, current)
+	if apierrors.IsNotFound(err) {
+		log.Info("could not find existing resource, creating one...")
+
+		curr, errComp := compose(string(manifests), current, overlay, a.CR, a.GVK, input.TmplFunc)
+		if errComp != nil {
+			return errComp
+		}
+
+		if err = a.Create(ctx, curr); err != nil {
+			return err
+		}
+
+		log.Info("created resource")
+		return nil
+	}
+	if err != nil {
+		return err
+	}
+	if input.Extract == nil {
+		return nil
+	}
+
+	object, err := compose(string(manifests), current, overlay, a.CR, a.GVK, input.TmplFunc)
+	if err != nil {
+		return err
+	}
+
+	if apiequal.Semantic.DeepDerivative(input.Extract(object), input.Extract(current)) {
+		log.Info("resource keeps the same as before")
+		return nil
+	}
+	if err := a.Update(ctx, object); err != nil {
+		return err
+	}
+	log.Info("updated resource")
+	return nil
+}
+
+func compose(manifests string, base, overlay, cr client.Object,
+	gvk schema.GroupVersionKind, tmplFunc template.FuncMap) (client.Object, error) {
+	err := LoadTemplate(manifests, cr, tmplFunc, overlay)
+	if err != nil {
+		return nil, err
+	}
+
+	overlay.SetOwnerReferences([]metav1.OwnerReference{*metav1.NewControllerRef(cr, gvk)})
+	if base == nil {
+		return overlay, nil
+	}
+	if err := ApplyOverlay(base, overlay); err != nil {
+		return nil, err
+	}
+	return base, nil
+}
diff --git a/pkg/kubernetes/kubernetes.go b/pkg/kubernetes/kubernetes.go
index 72a8082..91a65cf 100644
--- a/pkg/kubernetes/kubernetes.go
+++ b/pkg/kubernetes/kubernetes.go
@@ -18,7 +18,12 @@
 package kubernetes
 
 import (
+	"bytes"
+	"text/template"
+
+	"github.com/Masterminds/sprig/v3"
 	jsonpatch "github.com/evanphx/json-patch"
+	"github.com/ghodss/yaml"
 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 	"k8s.io/apimachinery/pkg/runtime"
 )
@@ -39,3 +44,22 @@ func ApplyOverlay(current, overlay runtime.Object) error {
 	}
 	return runtime.DecodeInto(unstructured.UnstructuredJSONScheme, merged, current)
 }
+
+// LoadTemplate loads a YAML file to a component
+func LoadTemplate(manifest string, values interface{}, funcMap template.FuncMap, spec interface{}) error {
+	tmplBuilder := template.New("manifest").
+		Funcs(sprig.TxtFuncMap())
+	if funcMap != nil {
+		tmplBuilder = tmplBuilder.Funcs(funcMap)
+	}
+	tmpl, err := tmplBuilder.Parse(manifest)
+	if err != nil {
+		return err
+	}
+	buf := bytes.Buffer{}
+	err = tmpl.Execute(&buf, values)
+	if err != nil {
+		return err
+	}
+	return yaml.Unmarshal(buf.Bytes(), spec)
+}
diff --git a/config/operator/rbac/role.yaml b/pkg/operator/manifests/oapserver/templates/cluster_role.yaml
similarity index 60%
copy from config/operator/rbac/role.yaml
copy to pkg/operator/manifests/oapserver/templates/cluster_role.yaml
index 9c7c975..368682d 100644
--- a/config/operator/rbac/role.yaml
+++ b/pkg/operator/manifests/oapserver/templates/cluster_role.yaml
@@ -15,56 +15,17 @@
 # specific language governing permissions and limitations
 # under the License.
 
-
----
-apiVersion: rbac.authorization.k8s.io/v1
 kind: ClusterRole
+apiVersion: rbac.authorization.k8s.io/v1
 metadata:
-  creationTimestamp: null
-  name: manager-role
+  name: swck:oapserver
+  labels:
+    operator.skywalking.apache.org/application: oapserver
+    operator.skywalking.apache.org/component: rbac
 rules:
-- apiGroups:
-  - ""
-  resources:
-  - services
-  verbs:
-  - create
-  - delete
-  - get
-  - list
-  - patch
-  - update
-  - watch
-- apiGroups:
-  - apps
-  resources:
-  - deployments
-  verbs:
-  - create
-  - delete
-  - get
-  - list
-  - patch
-  - update
-  - watch
-- apiGroups:
-  - operator.skywalking.apache.org
-  resources:
-  - oapservers
-  verbs:
-  - create
-  - delete
-  - get
-  - list
-  - patch
-  - update
-  - watch
-- apiGroups:
-  - operator.skywalking.apache.org
-  resources:
-  - oapservers/status
-  verbs:
-  - get
-  - patch
-  - update
-
+  - apiGroups: [""]
+    resources: ["pods", "endpoints", "services"]
+    verbs: ["get", "watch", "list"]
+  - apiGroups: ["extensions"]
+    resources: ["deployments", "replicasets"]
+    verbs: ["get", "watch", "list"]
diff --git a/config/operator/rbac/role.yaml b/pkg/operator/manifests/oapserver/templates/cluster_role_binding.yaml
similarity index 58%
copy from config/operator/rbac/role.yaml
copy to pkg/operator/manifests/oapserver/templates/cluster_role_binding.yaml
index 9c7c975..4ee09c4 100644
--- a/config/operator/rbac/role.yaml
+++ b/pkg/operator/manifests/oapserver/templates/cluster_role_binding.yaml
@@ -15,56 +15,18 @@
 # specific language governing permissions and limitations
 # under the License.
 
-
----
 apiVersion: rbac.authorization.k8s.io/v1
-kind: ClusterRole
+kind: ClusterRoleBinding
 metadata:
-  creationTimestamp: null
-  name: manager-role
-rules:
-- apiGroups:
-  - ""
-  resources:
-  - services
-  verbs:
-  - create
-  - delete
-  - get
-  - list
-  - patch
-  - update
-  - watch
-- apiGroups:
-  - apps
-  resources:
-  - deployments
-  verbs:
-  - create
-  - delete
-  - get
-  - list
-  - patch
-  - update
-  - watch
-- apiGroups:
-  - operator.skywalking.apache.org
-  resources:
-  - oapservers
-  verbs:
-  - create
-  - delete
-  - get
-  - list
-  - patch
-  - update
-  - watch
-- apiGroups:
-  - operator.skywalking.apache.org
-  resources:
-  - oapservers/status
-  verbs:
-  - get
-  - patch
-  - update
-
+  name: swck:oapserver
+  labels:
+    operator.skywalking.apache.org/application: oapserver
+    operator.skywalking.apache.org/component: rbac
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: ClusterRole
+  name: swck:oapserver
+subjects:
+  - kind: ServiceAccount
+    name: {{ .Name }}-oap
+    namespace: {{ .Namespace }}
diff --git a/pkg/operator/manifests/oapserver/templates/deployment.yaml b/pkg/operator/manifests/oapserver/templates/deployment.yaml
new file mode 100644
index 0000000..7b806e1
--- /dev/null
+++ b/pkg/operator/manifests/oapserver/templates/deployment.yaml
@@ -0,0 +1,86 @@
+# 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 }}
+  namespace: {{ .Namespace }}
+  labels:
+    app: oap
+    operator.skywalking.apache.org/oap-server-name: {{ .Name }}
+    operator.skywalking.apache.org/application: oapserver
+    operator.skywalking.apache.org/component: deployment
+spec:
+  replicas: {{ .Spec.Instances }}
+  minReadySeconds: 5
+  selector:
+    matchLabels:
+      app: oap
+      operator.skywalking.apache.org/oap-server-name: {{ .Name }}
+  template:
+    metadata:
+      labels:
+        app: oap
+        operator.skywalking.apache.org/oap-server-name: {{ .Name }}
+        operator.skywalking.apache.org/application: oapserver
+        operator.skywalking.apache.org/component: deployment
+    spec:
+      serviceAccountName: {{ .Name }}-oap
+      containers:
+      - name: oap
+        image: {{ generateImage }}
+        imagePullPolicy: IfNotPresent
+        ports:
+        - containerPort: 11800
+          name: grpc
+        - containerPort: 12800
+          name: rest
+        - containerPort: 1234
+          name: http-monitoring
+        livenessProbe:
+          initialDelaySeconds: 10
+          timeoutSeconds: 10
+          periodSeconds: 30
+          failureThreshold: 10
+          successThreshold: 1
+          exec:
+            command:
+              - /skywalking/bin/swctl
+              - ch
+        readinessProbe:
+          initialDelaySeconds: 10
+          timeoutSeconds: 10
+          periodSeconds: 30
+          failureThreshold: 10
+          successThreshold: 1
+          exec:
+            command:
+              - /skywalking/bin/swctl
+              - ch
+        env:
+        - name: JAVA_OPTS
+          value: -Xmx2048M
+        - name: SW_TELEMETRY
+          value: prometheus
+        - name: SW_HEALTH_CHECKER
+          value: default
+        {{range .Spec.Config}}
+        - name: {{ .Name }}
+          value: {{ .Value }}
+        {{end}}
+
diff --git a/config/operator/rbac/role.yaml b/pkg/operator/manifests/oapserver/templates/service.yaml
similarity index 55%
copy from config/operator/rbac/role.yaml
copy to pkg/operator/manifests/oapserver/templates/service.yaml
index 9c7c975..e97a9de 100644
--- a/config/operator/rbac/role.yaml
+++ b/pkg/operator/manifests/oapserver/templates/service.yaml
@@ -15,56 +15,26 @@
 # specific language governing permissions and limitations
 # under the License.
 
-
----
-apiVersion: rbac.authorization.k8s.io/v1
-kind: ClusterRole
+apiVersion: v1
+kind: Service
 metadata:
-  creationTimestamp: null
-  name: manager-role
-rules:
-- apiGroups:
-  - ""
-  resources:
-  - services
-  verbs:
-  - create
-  - delete
-  - get
-  - list
-  - patch
-  - update
-  - watch
-- apiGroups:
-  - apps
-  resources:
-  - deployments
-  verbs:
-  - create
-  - delete
-  - get
-  - list
-  - patch
-  - update
-  - watch
-- apiGroups:
-  - operator.skywalking.apache.org
-  resources:
-  - oapservers
-  verbs:
-  - create
-  - delete
-  - get
-  - list
-  - patch
-  - update
-  - watch
-- apiGroups:
-  - operator.skywalking.apache.org
-  resources:
-  - oapservers/status
-  verbs:
-  - get
-  - patch
-  - update
+  name: {{ .Name }}
+  namespace: {{ .Namespace }}
+  labels:
+    app: oap
+    operator.skywalking.apache.org/oap-server-name: {{ .Name }}
+    operator.skywalking.apache.org/application: oapserver
+    operator.skywalking.apache.org/component: service
+spec:
+  type: ClusterIP
+  ports:
+  - port: 12800
+    name: rest
+  - port: 11800
+    name: grpc
+  - port: 1234
+    name: http-monitoring
+  selector:
+    app: oap
+    operator.skywalking.apache.org/oap-server-name: {{ .Name }}
 
diff --git a/config/operator/rbac/role.yaml b/pkg/operator/manifests/oapserver/templates/service_account.yaml
similarity index 55%
copy from config/operator/rbac/role.yaml
copy to pkg/operator/manifests/oapserver/templates/service_account.yaml
index 9c7c975..c870638 100644
--- a/config/operator/rbac/role.yaml
+++ b/pkg/operator/manifests/oapserver/templates/service_account.yaml
@@ -15,56 +15,13 @@
 # specific language governing permissions and limitations
 # under the License.
 
-
----
-apiVersion: rbac.authorization.k8s.io/v1
-kind: ClusterRole
+apiVersion: v1
+kind: ServiceAccount
 metadata:
-  creationTimestamp: null
-  name: manager-role
-rules:
-- apiGroups:
-  - ""
-  resources:
-  - services
-  verbs:
-  - create
-  - delete
-  - get
-  - list
-  - patch
-  - update
-  - watch
-- apiGroups:
-  - apps
-  resources:
-  - deployments
-  verbs:
-  - create
-  - delete
-  - get
-  - list
-  - patch
-  - update
-  - watch
-- apiGroups:
-  - operator.skywalking.apache.org
-  resources:
-  - oapservers
-  verbs:
-  - create
-  - delete
-  - get
-  - list
-  - patch
-  - update
-  - watch
-- apiGroups:
-  - operator.skywalking.apache.org
-  resources:
-  - oapservers/status
-  verbs:
-  - get
-  - patch
-  - update
+  name: {{ .Name }}-oap
+  namespace: {{ .Namespace }}
+  labels:
+    operator.skywalking.apache.org/oap-server-name: {{ .Name }}
+    operator.skywalking.apache.org/application: oapserver
+    operator.skywalking.apache.org/component: rbac
 
diff --git a/pkg/operator/repo/assets.gen.go b/pkg/operator/repo/assets.gen.go
new file mode 100644
index 0000000..4bdd0b9
--- /dev/null
+++ b/pkg/operator/repo/assets.gen.go
@@ -0,0 +1,564 @@
+// 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.
+// Code generated by go-bindata. DO NOT EDIT.
+// sources:
+// oapserver/templates/cluster_role.yaml (1.241kB)
+// oapserver/templates/cluster_role_binding.yaml (1.207kB)
+// oapserver/templates/deployment.yaml (2.693kB)
+// oapserver/templates/service.yaml (1.32kB)
+// oapserver/templates/service_account.yaml (1.09kB)
+
+package repo
+
+import (
+	"crypto/sha256"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"strings"
+	"time"
+)
+
+type asset struct {
+	bytes  []byte
+	info   os.FileInfo
+	digest [sha256.Size]byte
+}
+
+type bindataFileInfo struct {
+	name    string
+	size    int64
+	mode    os.FileMode
+	modTime time.Time
+}
+
+func (fi bindataFileInfo) Name() string {
+	return fi.name
+}
+func (fi bindataFileInfo) Size() int64 {
+	return fi.size
+}
+func (fi bindataFileInfo) Mode() os.FileMode {
+	return fi.mode
+}
+func (fi bindataFileInfo) ModTime() time.Time {
+	return fi.modTime
+}
+func (fi bindataFileInfo) IsDir() bool {
+	return false
+}
+func (fi bindataFileInfo) Sys() interface{} {
+	return nil
+}
+
+var _oapserverTemplatesCluster_roleYaml = []byte(`# 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:oapserver
+  labels:
+    operator.skywalking.apache.org/application: oapserver
+    operator.skywalking.apache.org/component: rbac
+rules:
+  - apiGroups: [""]
+    resources: ["pods", "endpoints", "services"]
+    verbs: ["get", "watch", "list"]
+  - apiGroups: ["extensions"]
+    resources: ["deployments", "replicasets"]
+    verbs: ["get", "watch", "list"]
+`)
+
+func oapserverTemplatesCluster_roleYamlBytes() ([]byte, error) {
+	return _oapserverTemplatesCluster_roleYaml, nil
+}
+
+func oapserverTemplatesCluster_roleYaml() (*asset, error) {
+	bytes, err := oapserverTemplatesCluster_roleYamlBytes()
+	if err != nil {
+		return nil, err
+	}
+
+	info := bindataFileInfo{name: "oapserver/templates/cluster_role.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)}
+	a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xb5, 0xde, 0x59, 0x47, 0x2c, 0x69, 0xe9, 0xcd, 0x7e, 0xa7, 0x4, 0xf7, 0xf2, 0xa0, 0x1a, 0x1b, 0xb6, 0xa3, 0x42, 0xb3, 0x20, 0x49, 0x9b, 0x2f, 0x81, 0x87, 0xa0, 0x95, 0x73, 0x49, 0x6a, 0xdb}}
+	return a, nil
+}
+
+var _oapserverTemplatesCluster_role_bindingYaml = []byte(`# 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:oapserver
+  labels:
+    operator.skywalking.apache.org/application: oapserver
+    operator.skywalking.apache.org/component: rbac
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: ClusterRole
+  name: swck:oapserver
+subjects:
+  - kind: ServiceAccount
+    name: {{ .Name }}-oap
+    namespace: {{ .Namespace }}
+`)
+
+func oapserverTemplatesCluster_role_bindingYamlBytes() ([]byte, error) {
+	return _oapserverTemplatesCluster_role_bindingYaml, nil
+}
+
+func oapserverTemplatesCluster_role_bindingYaml() (*asset, error) {
+	bytes, err := oapserverTemplatesCluster_role_bindingYamlBytes()
+	if err != nil {
+		return nil, err
+	}
+
+	info := bindataFileInfo{name: "oapserver/templates/cluster_role_binding.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)}
+	a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xf8, 0x15, 0xc5, 0xe0, 0xe7, 0x4e, 0xeb, 0xed, 0x61, 0xda, 0xad, 0x6c, 0x43, 0xf, 0x80, 0x5, 0x4c, 0x47, 0xbc, 0x20, 0xe7, 0xc6, 0xdf, 0x63, 0xcf, 0x9b, 0x54, 0x56, 0x1f, 0x4, 0xbf, 0x1c}}
+	return a, nil
+}
+
+var _oapserverTemplatesDeploymentYaml = []byte(`# 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 }}
+  namespace: {{ .Namespace }}
+  labels:
+    app: oap
+    operator.skywalking.apache.org/oap-server-name: {{ .Name }}
+    operator.skywalking.apache.org/application: oapserver
+    operator.skywalking.apache.org/component: deployment
+spec:
+  replicas: {{ .Spec.Instances }}
+  minReadySeconds: 5
+  selector:
+    matchLabels:
+      app: oap
+      operator.skywalking.apache.org/oap-server-name: {{ .Name }}
+  template:
+    metadata:
+      labels:
+        app: oap
+        operator.skywalking.apache.org/oap-server-name: {{ .Name }}
+        operator.skywalking.apache.org/application: oapserver
+        operator.skywalking.apache.org/component: deployment
+    spec:
+      serviceAccountName: {{ .Name }}-oap
+      containers:
+      - name: oap
+        image: {{ generateImage }}
+        imagePullPolicy: IfNotPresent
+        ports:
+        - containerPort: 11800
+          name: grpc
+        - containerPort: 12800
+          name: rest
+        - containerPort: 1234
+          name: http-monitoring
+        livenessProbe:
+          initialDelaySeconds: 10
+          timeoutSeconds: 10
+          periodSeconds: 30
+          failureThreshold: 10
+          successThreshold: 1
+          exec:
+            command:
+              - /skywalking/bin/swctl
+              - ch
+        readinessProbe:
+          initialDelaySeconds: 10
+          timeoutSeconds: 10
+          periodSeconds: 30
+          failureThreshold: 10
+          successThreshold: 1
+          exec:
+            command:
+              - /skywalking/bin/swctl
+              - ch
+        env:
+        - name: JAVA_OPTS
+          value: -Xmx2048M
+        - name: SW_TELEMETRY
+          value: prometheus
+        - name: SW_HEALTH_CHECKER
+          value: default
+        {{range .Spec.Config}}
+        - name: {{ .Name }}
+          value: {{ .Value }}
+        {{end}}
+
+`)
+
+func oapserverTemplatesDeploymentYamlBytes() ([]byte, error) {
+	return _oapserverTemplatesDeploymentYaml, nil
+}
+
+func oapserverTemplatesDeploymentYaml() (*asset, error) {
+	bytes, err := oapserverTemplatesDeploymentYamlBytes()
+	if err != nil {
+		return nil, err
+	}
+
+	info := bindataFileInfo{name: "oapserver/templates/deployment.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)}
+	a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x84, 0xc6, 0x37, 0x3e, 0x28, 0x85, 0xf9, 0x34, 0xb6, 0x9f, 0xae, 0xfa, 0x6c, 0x34, 0x80, 0xa8, 0x23, 0xf1, 0x51, 0xfb, 0x6d, 0x8, 0x6e, 0x9f, 0x74, 0xb5, 0x8b, 0xb3, 0xb, 0xf7, 0x66, 0xeb}}
+	return a, nil
+}
+
+var _oapserverTemplatesServiceYaml = []byte(`# 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: Service
+metadata:
+  name: {{ .Name }}
+  namespace: {{ .Namespace }}
+  labels:
+    app: oap
+    operator.skywalking.apache.org/oap-server-name: {{ .Name }}
+    operator.skywalking.apache.org/application: oapserver
+    operator.skywalking.apache.org/component: service
+spec:
+  type: ClusterIP
+  ports:
+  - port: 12800
+    name: rest
+  - port: 11800
+    name: grpc
+  - port: 1234
+    name: http-monitoring
+  selector:
+    app: oap
+    operator.skywalking.apache.org/oap-server-name: {{ .Name }}
+
+`)
+
+func oapserverTemplatesServiceYamlBytes() ([]byte, error) {
+	return _oapserverTemplatesServiceYaml, nil
+}
+
+func oapserverTemplatesServiceYaml() (*asset, error) {
+	bytes, err := oapserverTemplatesServiceYamlBytes()
+	if err != nil {
+		return nil, err
+	}
+
+	info := bindataFileInfo{name: "oapserver/templates/service.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)}
+	a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x26, 0x9a, 0x7e, 0x23, 0x6c, 0xc6, 0x36, 0x17, 0xd5, 0x9e, 0xb2, 0x94, 0x47, 0xb6, 0xc, 0xaf, 0x83, 0x5e, 0x97, 0x44, 0x5e, 0x94, 0x33, 0xb1, 0xba, 0x72, 0xdd, 0xab, 0x20, 0xbc, 0xdf, 0x59}}
+	return a, nil
+}
+
+var _oapserverTemplatesService_accountYaml = []byte(`# 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 }}-oap
+  namespace: {{ .Namespace }}
+  labels:
+    operator.skywalking.apache.org/oap-server-name: {{ .Name }}
+    operator.skywalking.apache.org/application: oapserver
+    operator.skywalking.apache.org/component: rbac
+
+`)
+
+func oapserverTemplatesService_accountYamlBytes() ([]byte, error) {
+	return _oapserverTemplatesService_accountYaml, nil
+}
+
+func oapserverTemplatesService_accountYaml() (*asset, error) {
+	bytes, err := oapserverTemplatesService_accountYamlBytes()
+	if err != nil {
+		return nil, err
+	}
+
+	info := bindataFileInfo{name: "oapserver/templates/service_account.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)}
+	a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x35, 0x3b, 0x36, 0x6, 0xcc, 0x9, 0x84, 0x7d, 0x62, 0x48, 0x31, 0xcf, 0x17, 0xec, 0xd1, 0xdd, 0x83, 0x0, 0x86, 0x6a, 0x50, 0xd5, 0xb6, 0x4a, 0x23, 0xdc, 0xea, 0xd, 0x80, 0x87, 0x2, 0x24}}
+	return a, nil
+}
+
+// Asset loads and returns the asset for the given name.
+// It returns an error if the asset could not be found or
+// could not be loaded.
+func Asset(name string) ([]byte, error) {
+	canonicalName := strings.Replace(name, "\\", "/", -1)
+	if f, ok := _bindata[canonicalName]; ok {
+		a, err := f()
+		if err != nil {
+			return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err)
+		}
+		return a.bytes, nil
+	}
+	return nil, fmt.Errorf("Asset %s not found", name)
+}
+
+// AssetString returns the asset contents as a string (instead of a []byte).
+func AssetString(name string) (string, error) {
+	data, err := Asset(name)
+	return string(data), err
+}
+
+// MustAsset is like Asset but panics when Asset would return an error.
+// It simplifies safe initialization of global variables.
+func MustAsset(name string) []byte {
+	a, err := Asset(name)
+	if err != nil {
+		panic("asset: Asset(" + name + "): " + err.Error())
+	}
+
+	return a
+}
+
+// MustAssetString is like AssetString but panics when Asset would return an
+// error. It simplifies safe initialization of global variables.
+func MustAssetString(name string) string {
+	return string(MustAsset(name))
+}
+
+// AssetInfo loads and returns the asset info for the given name.
+// It returns an error if the asset could not be found or
+// could not be loaded.
+func AssetInfo(name string) (os.FileInfo, error) {
+	canonicalName := strings.Replace(name, "\\", "/", -1)
+	if f, ok := _bindata[canonicalName]; ok {
+		a, err := f()
+		if err != nil {
+			return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err)
+		}
+		return a.info, nil
+	}
+	return nil, fmt.Errorf("AssetInfo %s not found", name)
+}
+
+// AssetDigest returns the digest of the file with the given name. It returns an
+// error if the asset could not be found or the digest could not be loaded.
+func AssetDigest(name string) ([sha256.Size]byte, error) {
+	canonicalName := strings.Replace(name, "\\", "/", -1)
+	if f, ok := _bindata[canonicalName]; ok {
+		a, err := f()
+		if err != nil {
+			return [sha256.Size]byte{}, fmt.Errorf("AssetDigest %s can't read by error: %v", name, err)
+		}
+		return a.digest, nil
+	}
+	return [sha256.Size]byte{}, fmt.Errorf("AssetDigest %s not found", name)
+}
+
+// Digests returns a map of all known files and their checksums.
+func Digests() (map[string][sha256.Size]byte, error) {
+	mp := make(map[string][sha256.Size]byte, len(_bindata))
+	for name := range _bindata {
+		a, err := _bindata[name]()
+		if err != nil {
+			return nil, err
+		}
+		mp[name] = a.digest
+	}
+	return mp, nil
+}
+
+// AssetNames returns the names of the assets.
+func AssetNames() []string {
+	names := make([]string, 0, len(_bindata))
+	for name := range _bindata {
+		names = append(names, name)
+	}
+	return names
+}
+
+// _bindata is a table, holding each asset generator, mapped to its name.
+var _bindata = map[string]func() (*asset, error){
+	"oapserver/templates/cluster_role.yaml":         oapserverTemplatesCluster_roleYaml,
+	"oapserver/templates/cluster_role_binding.yaml": oapserverTemplatesCluster_role_bindingYaml,
+	"oapserver/templates/deployment.yaml":           oapserverTemplatesDeploymentYaml,
+	"oapserver/templates/service.yaml":              oapserverTemplatesServiceYaml,
+	"oapserver/templates/service_account.yaml":      oapserverTemplatesService_accountYaml,
+}
+
+// AssetDebug is true if the assets were built with the debug flag enabled.
+const AssetDebug = false
+
+// AssetDir returns the file names below a certain
+// directory embedded in the file by go-bindata.
+// For example if you run go-bindata on data/... and data contains the
+// following hierarchy:
+//     data/
+//       foo.txt
+//       img/
+//         a.png
+//         b.png
+// then AssetDir("data") would return []string{"foo.txt", "img"},
+// AssetDir("data/img") would return []string{"a.png", "b.png"},
+// AssetDir("foo.txt") and AssetDir("notexist") would return an error, and
+// AssetDir("") will return []string{"data"}.
+func AssetDir(name string) ([]string, error) {
+	node := _bintree
+	if len(name) != 0 {
+		canonicalName := strings.Replace(name, "\\", "/", -1)
+		pathList := strings.Split(canonicalName, "/")
+		for _, p := range pathList {
+			node = node.Children[p]
+			if node == nil {
+				return nil, fmt.Errorf("Asset %s not found", name)
+			}
+		}
+	}
+	if node.Func != nil {
+		return nil, fmt.Errorf("Asset %s not found", name)
+	}
+	rv := make([]string, 0, len(node.Children))
+	for childName := range node.Children {
+		rv = append(rv, childName)
+	}
+	return rv, nil
+}
+
+type bintree struct {
+	Func     func() (*asset, error)
+	Children map[string]*bintree
+}
+
+var _bintree = &bintree{nil, map[string]*bintree{
+	"oapserver": &bintree{nil, map[string]*bintree{
+		"templates": &bintree{nil, map[string]*bintree{
+			"cluster_role.yaml":         &bintree{oapserverTemplatesCluster_roleYaml, map[string]*bintree{}},
+			"cluster_role_binding.yaml": &bintree{oapserverTemplatesCluster_role_bindingYaml, map[string]*bintree{}},
+			"deployment.yaml":           &bintree{oapserverTemplatesDeploymentYaml, map[string]*bintree{}},
+			"service.yaml":              &bintree{oapserverTemplatesServiceYaml, map[string]*bintree{}},
+			"service_account.yaml":      &bintree{oapserverTemplatesService_accountYaml, map[string]*bintree{}},
+		}},
+	}},
+}}
+
+// RestoreAsset restores an asset under the given directory.
+func RestoreAsset(dir, name string) error {
+	data, err := Asset(name)
+	if err != nil {
+		return err
+	}
+	info, err := AssetInfo(name)
+	if err != nil {
+		return err
+	}
+	err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755))
+	if err != nil {
+		return err
+	}
+	err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode())
+	if err != nil {
+		return err
+	}
+	return os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime())
+}
+
+// RestoreAssets restores an asset under the given directory recursively.
+func RestoreAssets(dir, name string) error {
+	children, err := AssetDir(name)
+	// File
+	if err != nil {
+		return RestoreAsset(dir, name)
+	}
+	// Dir
+	for _, child := range children {
+		err = RestoreAssets(dir, filepath.Join(name, child))
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func _filePath(dir, name string) string {
+	canonicalName := strings.Replace(name, "\\", "/", -1)
+	return filepath.Join(append([]string{dir}, strings.Split(canonicalName, "/")...)...)
+}
diff --git a/pkg/kubernetes/kubernetes.go b/pkg/operator/repo/repo.go
similarity index 54%
copy from pkg/kubernetes/kubernetes.go
copy to pkg/operator/repo/repo.go
index 72a8082..ffba7d5 100644
--- a/pkg/kubernetes/kubernetes.go
+++ b/pkg/operator/repo/repo.go
@@ -15,27 +15,20 @@
 // specific language governing permissions and limitations
 // under the License.
 
-package kubernetes
+package repo
 
-import (
-	jsonpatch "github.com/evanphx/json-patch"
-	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
-	"k8s.io/apimachinery/pkg/runtime"
-)
+import "fmt"
 
-// ApplyOverlay applies an overlay using JSON patch strategy over the current Object in place.
-func ApplyOverlay(current, overlay runtime.Object) error {
-	cj, err := runtime.Encode(unstructured.UnstructuredJSONScheme, current)
-	if err != nil {
-		return err
-	}
-	uj, err := runtime.Encode(unstructured.UnstructuredJSONScheme, overlay)
-	if err != nil {
-		return err
-	}
-	merged, err := jsonpatch.MergePatch(cj, uj)
-	if err != nil {
-		return err
-	}
-	return runtime.DecodeInto(unstructured.UnstructuredJSONScheme, merged, current)
+// AssetsRepo provides templates through assets
+type AssetsRepo struct {
+	Root string
+}
+
+func NewRepo(component string) *AssetsRepo {
+	return &AssetsRepo{Root: fmt.Sprintf("%s/templates", component)}
+}
+
+// ReadFile reads the content of compiled in files at path and returns a buffer with the data.
+func (v *AssetsRepo) ReadFile(path string) ([]byte, error) {
+	return Asset(fmt.Sprintf("%s/%s", v.Root, path))
 }