Repository: vanus-labs/vanus
Pull request #459 (2023-02-24 07:22:39)

feat: separate etcd
### What problem does this PR solve?
separate etcd

Issue Number: close #xxx

### Problem Summary

### What is changed and how does it work?

### Check List

<!-- At least one of them must be included. -->

#### Tests

- [ ] Unit test
- [ ] Integration test
- [ ] Manual test (add detailed scripts or steps below)
- [ ] No code


codecov[bot]: # [Codecov](https://codecov.io/gh/vanus-labs/vanus/pull/459?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vanus-labs) Report
> Merging [#459](https://codecov.io/gh/vanus-labs/vanus/pull/459?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vanus-labs) (8d4e33b) into [main](https://codecov.io/gh/vanus-labs/vanus/commit/2a78cb5c98e21ede2d084739bfadd4151ac48054?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vanus-labs) (2a78cb5) will **decrease** coverage by `0.53%`.
> The diff coverage is `29.53%`.

<details><summary>Additional details and impacted files</summary>


[![Impacted file tree graph](https://codecov.io/gh/vanus-labs/vanus/pull/459/graphs/tree.svg?width=650&height=150&src=pr&token=RSXSIMEY4V&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vanus-labs)](https://codecov.io/gh/vanus-labs/vanus/pull/459?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vanus-labs)

```diff
@@            Coverage Diff             @@
##             main     #459      +/-   ##
==========================================
- Coverage   57.71%   57.18%   -0.53%     
==========================================
  Files         180      184       +4     
  Lines       14614    15007     +393     
==========================================
+ Hits         8434     8582     +148     
- Misses       5552     5794     +242     
- Partials      628      631       +3     
```


| [Impacted Files](https://codecov.io/gh/vanus-labs/vanus/pull/459?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vanus-labs) | Coverage Δ | |
|---|---|---|
| [...ernal/controller/trigger/validation/subscripton.go](https://codecov.io/gh/vanus-labs/vanus/pull/459?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vanus-labs#diff-aW50ZXJuYWwvY29udHJvbGxlci90cmlnZ2VyL3ZhbGlkYXRpb24vc3Vic2NyaXB0b24uZ28=) | `69.29% <ø> (+0.85%)` | :arrow_up: |
| [internal/gateway/proxy/deadletter.go](https://codecov.io/gh/vanus-labs/vanus/pull/459?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vanus-labs#diff-aW50ZXJuYWwvZ2F0ZXdheS9wcm94eS9kZWFkbGV0dGVyLmdv) | `0.00% <0.00%> (ø)` | |
| [internal/gateway/proxy/direct.go](https://codecov.io/gh/vanus-labs/vanus/pull/459?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vanus-labs#diff-aW50ZXJuYWwvZ2F0ZXdheS9wcm94eS9kaXJlY3QuZ28=) | `73.33% <0.00%> (-5.24%)` | :arrow_down: |
| [.../primitive/interceptor/memberinterceptor/member.go](https://codecov.io/gh/vanus-labs/vanus/pull/459?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vanus-labs#diff-aW50ZXJuYWwvcHJpbWl0aXZlL2ludGVyY2VwdG9yL21lbWJlcmludGVyY2VwdG9yL21lbWJlci5nbw==) | `0.00% <0.00%> (ø)` | |
| [internal/store/segment/server.go](https://codecov.io/gh/vanus-labs/vanus/pull/459?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vanus-labs#diff-aW50ZXJuYWwvc3RvcmUvc2VnbWVudC9zZXJ2ZXIuZ28=) | `14.10% <0.00%> (-0.03%)` | :arrow_down: |
| [internal/trigger/client/gcloud\_functions.go](https://codecov.io/gh/vanus-labs/vanus/pull/459?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vanus-labs#diff-aW50ZXJuYWwvdHJpZ2dlci9jbGllbnQvZ2Nsb3VkX2Z1bmN0aW9ucy5nbw==) | `0.00% <0.00%> (ø)` | |
| [internal/trigger/client/grpc.go](https://codecov.io/gh/vanus-labs/vanus/pull/459?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vanus-labs#diff-aW50ZXJuYWwvdHJpZ2dlci9jbGllbnQvZ3JwYy5nbw==) | `0.00% <0.00%> (ø)` | |
| [internal/trigger/client/http.go](https://codecov.io/gh/vanus-labs/vanus/pull/459?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vanus-labs#diff-aW50ZXJuYWwvdHJpZ2dlci9jbGllbnQvaHR0cC5nbw==) | `0.00% <0.00%> (ø)` | |
| [internal/trigger/client/interface.go](https://codecov.io/gh/vanus-labs/vanus/pull/459?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vanus-labs#diff-aW50ZXJuYWwvdHJpZ2dlci9jbGllbnQvaW50ZXJmYWNlLmdv) | `0.00% <0.00%> (-19.24%)` | :arrow_down: |
| [internal/trigger/client/lambda.go](https://codecov.io/gh/vanus-labs/vanus/pull/459?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vanus-labs#diff-aW50ZXJuYWwvdHJpZ2dlci9jbGllbnQvbGFtYmRhLmdv) | `0.00% <0.00%> (ø)` | |
| ... and [29 more](https://codecov.io/gh/vanus-labs/vanus/pull/459?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vanus-labs) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/vanus-labs/vanus/pull/459?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vanus-labs).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vanus-labs)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/vanus-labs/vanus/pull/459?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vanus-labs). Last update [e08097a...8d4e33b](https://codecov.io/gh/vanus-labs/vanus/pull/459?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vanus-labs). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=vanus-labs).

</details>

Patch:
diff --git a/build/all-in-one/config/controller.yaml b/build/all-in-one/config/controller.yaml
index 95fccca..0160cb5 100644
--- a/build/all-in-one/config/controller.yaml
+++ b/build/all-in-one/config/controller.yaml
@@ -11,17 +11,8 @@ topology:
 replicas: 1
 metadata:
   key_prefix: "/standalone"
-embed_etcd:
-  # relative path to ${data_dir} above
-  data_dir: "etcd"
-  listen_client_addr: 127.0.0.1:2379
-  listen_peer_addr: 127.0.0.1:2380
-  advertise_client_addr: 127.0.0.1:2379
-  advertise_peer_addr: 127.0.0.1:2380
-  heartbeat-interval: 600000
-  election-timeout: 600000
-  clusters:
-    - standalone=http://127.0.0.1:2380
+leader_election:
+  lease_duration: 15
 secret_encryption_salt: "encryption_salt"
 observability:
   metrics:
diff --git a/build/all-in-one/config/timer.yaml b/build/all-in-one/config/timer.yaml
index 622b7d0..8a46b70 100644
--- a/build/all-in-one/config/timer.yaml
+++ b/build/all-in-one/config/timer.yaml
@@ -5,7 +5,7 @@ etcd:
 replicas: 1
 metadata:
   key_prefix: "/standalone"
-leaderelection:
+leader_election:
   lease_duration: 15
 timingwheel:
   tick: 1
diff --git a/cmd/controller/main.go b/cmd/controller/main.go
index 32ae520..bf092ce 100644
--- a/cmd/controller/main.go
+++ b/cmd/controller/main.go
@@ -24,9 +24,9 @@ import (
 	"sync"
 
 	recovery "github.com/grpc-ecosystem/go-grpc-middleware/recovery"
-	embedetcd "github.com/linkall-labs/embed-etcd"
 	"github.com/linkall-labs/vanus/internal/controller"
 	"github.com/linkall-labs/vanus/internal/controller/eventbus"
+	"github.com/linkall-labs/vanus/internal/controller/member"
 	"github.com/linkall-labs/vanus/internal/controller/snowflake"
 	"github.com/linkall-labs/vanus/internal/controller/trigger"
 	"github.com/linkall-labs/vanus/internal/primitive/interceptor/errinterceptor"
@@ -68,16 +68,16 @@ func main() {
 
 	ctx := signal.SetupSignalContext()
 	_ = observability.Initialize(ctx, cfg.Observability, metrics.GetControllerMetrics)
-	etcd := embedetcd.New(cfg.Topology)
-	if err = etcd.Init(ctx, cfg.GetEtcdConfig()); err != nil {
-		log.Error(ctx, "failed to init etcd", map[string]interface{}{
+	mem := member.New(cfg.Topology)
+	if err = mem.Init(ctx, cfg.GetMemberConfig()); err != nil {
+		log.Error(ctx, "failed to init member", map[string]interface{}{
 			log.KeyError: err,
 		})
 		os.Exit(-1)
 	}
 
 	// TODO wait server ready
-	snowflakeCtrl := snowflake.NewSnowflakeController(cfg.GetSnowflakeConfig(), etcd)
+	snowflakeCtrl := snowflake.NewSnowflakeController(cfg.GetSnowflakeConfig(), mem)
 	if err = snowflakeCtrl.Start(ctx); err != nil {
 		log.Error(ctx, "start Snowflake Controller failed", map[string]interface{}{
 			log.KeyError: err,
@@ -85,7 +85,7 @@ func main() {
 		os.Exit(-1)
 	}
 
-	segmentCtrl := eventbus.NewController(cfg.GetEventbusCtrlConfig(), etcd)
+	segmentCtrl := eventbus.NewController(cfg.GetEventbusCtrlConfig(), mem)
 	if err = segmentCtrl.Start(ctx); err != nil {
 		log.Error(ctx, "start EventbusService Controller failed", map[string]interface{}{
 			log.KeyError: err,
@@ -94,7 +94,7 @@ func main() {
 	}
 
 	//trigger controller
-	triggerCtrlStv := trigger.NewController(cfg.GetTriggerConfig(), etcd)
+	triggerCtrlStv := trigger.NewController(cfg.GetTriggerConfig(), mem)
 	if err = triggerCtrlStv.Start(); err != nil {
 		log.Error(ctx, "start trigger controller fail", map[string]interface{}{
 			log.KeyError: err,
@@ -102,9 +102,9 @@ func main() {
 		os.Exit(-1)
 	}
 
-	etcdStopCh, err := etcd.Start(ctx)
+	memStopCh, err := mem.Start(ctx)
 	if err != nil {
-		log.Error(ctx, "failed to start etcd", map[string]interface{}{
+		log.Error(ctx, "failed to start member", map[string]interface{}{
 			log.KeyError: err,
 		})
 		os.Exit(-2)
@@ -124,13 +124,13 @@ func main() {
 		grpc.ChainStreamInterceptor(
 			errinterceptor.StreamServerInterceptor(),
 			recovery.StreamServerInterceptor(recoveryOpt),
-			memberinterceptor.StreamServerInterceptor(etcd),
+			memberinterceptor.StreamServerInterceptor(mem),
 			otelgrpc.StreamServerInterceptor(),
 		),
 		grpc.ChainUnaryInterceptor(
 			errinterceptor.UnaryServerInterceptor(),
 			recovery.UnaryServerInterceptor(recoveryOpt),
-			memberinterceptor.UnaryServerInterceptor(etcd),
+			memberinterceptor.UnaryServerInterceptor(mem),
 			otelgrpc.UnaryServerInterceptor(),
 		),
 	)
@@ -164,7 +164,7 @@ func main() {
 		snowflakeCtrl.Stop()
 		triggerCtrlStv.Stop(ctx)
 		segmentCtrl.Stop()
-		etcd.Stop(ctx)
+		mem.Stop(ctx)
 		grpcServer.GracefulStop()
 	}
 
@@ -177,8 +177,8 @@ func main() {
 	}
 
 	select {
-	case <-etcdStopCh:
-		log.Info(ctx, "received etcd ready to stop, preparing exit", nil)
+	case <-memStopCh:
+		log.Info(ctx, "received member ready to stop, preparing exit", nil)
 	case <-ctx.Done():
 		log.Info(ctx, "received system signal, preparing exit", nil)
 	case <-segmentCtrl.StopNotify():
diff --git a/config/cluster/controller/node-1.yaml.example b/config/cluster/controller/node-1.yaml.example
index afb1a9f..51eab17 100644
--- a/config/cluster/controller/node-1.yaml.example
+++ b/config/cluster/controller/node-1.yaml.example
@@ -13,16 +13,5 @@ topology:
   test-3: 127.0.0.1:4048
 metadata:
   key_prefix: "/vanus"
-embed_etcd:
-  # relative path to ${data_dir} above
-  data_dir: "etcd/node1"
-  listen_client_addr: 127.0.0.1:2379
-  listen_peer_addr: 127.0.0.1:2380
-  advertise_client_addr: 127.0.0.1:2379
-  advertise_peer_addr: 127.0.0.1:2380
-  heartbeat-interval: 600000
-  election-timeout: 600000
-  clusters:
-    - test-1=http://127.0.0.1:2380
-    - test-2=http://127.0.0.1:3380
-    - test-3=http://127.0.0.1:4380
\ No newline at end of file
+leader_election:
+  lease_duration: 15
\ No newline at end of file
diff --git a/config/cluster/controller/node-2.yaml.example b/config/cluster/controller/node-2.yaml.example
index e84ea96..4abc230 100644
--- a/config/cluster/controller/node-2.yaml.example
+++ b/config/cluster/controller/node-2.yaml.example
@@ -13,16 +13,5 @@ data_dir: "/tmp/vanus/data2/cluster"
 replicas: 1
 metadata:
   key_prefix: "/vanus"
-embed_etcd:
-  # relative path to ${data_dir} above
-  data_dir: "etcd/node2"
-  listen_client_addr: 127.0.0.1:3379
-  listen_peer_addr: 127.0.0.1:3380
-  advertise_client_addr: 127.0.0.1:3379
-  advertise_peer_addr: 127.0.0.1:3380
-  heartbeat-interval: 600000
-  election-timeout: 600000
-  clusters:
-    - test-1=http://127.0.0.1:2380
-    - test-2=http://127.0.0.1:3380
-    - test-3=http://127.0.0.1:4380
\ No newline at end of file
+leader_election:
+  lease_duration: 15
\ No newline at end of file
diff --git a/config/cluster/controller/node-3.yaml.example b/config/cluster/controller/node-3.yaml.example
index 408dd03..9301a29 100644
--- a/config/cluster/controller/node-3.yaml.example
+++ b/config/cluster/controller/node-3.yaml.example
@@ -12,16 +12,5 @@ topology:
 data_dir: "/tmp/vanus/data3/cluster"
 metadata:
   key_prefix: "/vanus"
-embed_etcd:
-  # relative path to ${data_dir} above
-  data_dir: "etcd/node3"
-  listen_client_addr: 127.0.0.1:4379
-  listen_peer_addr: 127.0.0.1:4380
-  advertise_client_addr: 127.0.0.1:4379
-  advertise_peer_addr: 127.0.0.1:4380
-  heartbeat-interval: 600000
-  election-timeout: 600000
-  clusters:
-    - test-1=http://127.0.0.1:2380
-    - test-2=http://127.0.0.1:3380
-    - test-3=http://127.0.0.1:4380
\ No newline at end of file
+leader_election:
+  lease_duration: 15
\ No newline at end of file
diff --git a/config/controller.yaml.example b/config/controller.yaml.example
index e55b3a7..4fab17b 100644
--- a/config/controller.yaml.example
+++ b/config/controller.yaml.example
@@ -11,17 +11,8 @@ topology:
 replicas: 1
 metadata:
   key_prefix: "/vanus"
-embed_etcd:
-  # relative path to ${data_dir} above
-  data_dir: "etcd"
-  listen_client_addr: 127.0.0.1:2379
-  listen_peer_addr: 127.0.0.1:2380
-  advertise_client_addr: 127.0.0.1:2379
-  advertise_peer_addr: 127.0.0.1:2380
-  heartbeat-interval: 600000
-  election-timeout: 600000
-  clusters:
-    - test-1=http://127.0.0.1:2380
+leader_election:
+  lease_duration: 15
 secret_encryption_salt: "encryption_salt"
 observability:
   metrics:
diff --git a/config/timer.yaml.example b/config/timer.yaml.example
index 405ac62..67a0a33 100644
--- a/config/timer.yaml.example
+++ b/config/timer.yaml.example
@@ -6,7 +6,7 @@ etcd:
   # - "127.0.0.1:4379"
 metadata:
   key_prefix: "/vanus"
-leaderelection:
+leader_election:
   lease_duration: 15
 timingwheel:
   tick: 1
diff --git a/deploy/aws-eks/aws-eks.yml b/deploy/aws-eks/aws-eks.yml
index 73ef271..d71533b 100644
--- a/deploy/aws-eks/aws-eks.yml
+++ b/deploy/aws-eks/aws-eks.yml
@@ -24,29 +24,20 @@ data:
     ip: ${POD_IP}
     port: 2048
     etcd:
-      - vanus-controller-0.vanus-controller:2379
-      - vanus-controller-1.vanus-controller:2379
-      - vanus-controller-2.vanus-controller:2379
+      - vanus-etcd-0.vanus-etcd:2379
+      - vanus-etcd-1.vanus-etcd:2379
+      - vanus-etcd-2.vanus-etcd:2379
     data_dir: /data
     segment_capacity: 268435456
     replicas: 3
     metadata:
       key_prefix: /vanus
+    leader_election:
+      lease_duration: 15
     topology:
       vanus-controller-0: vanus-controller-0.vanus-controller.vanus.svc:2048
       vanus-controller-1: vanus-controller-1.vanus-controller.vanus.svc:2048
       vanus-controller-2: vanus-controller-2.vanus-controller.vanus.svc:2048
-    embed_etcd:
-      # relative path to ${data_dir} above
-      data_dir: etcd/data
-      listen_client_addr: 0.0.0.0:2379
-      listen_peer_addr: 0.0.0.0:2380
-      advertise_client_addr: ${POD_NAME}.vanus-controller:2379
-      advertise_peer_addr: ${POD_NAME}.vanus-controller:2380
-      clusters:
-        - vanus-controller-0=http://vanus-controller-0.vanus-controller:2380
-        - vanus-controller-1=http://vanus-controller-1.vanus-controller:2380
-        - vanus-controller-2=http://vanus-controller-2.vanus-controller:2380
     observability:
       metrics:
         enable: true
@@ -123,12 +114,12 @@ data:
     name: "timer"
     ip: ${POD_IP}
     etcd:
-      - vanus-controller-0.vanus-controller:2379
-      - vanus-controller-1.vanus-controller:2379
-      - vanus-controller-2.vanus-controller:2379
+      - vanus-etcd-0.vanus-etcd:2379
+      - vanus-etcd-1.vanus-etcd:2379
+      - vanus-etcd-2.vanus-etcd:2379
     metadata:
       key_prefix: "/vanus"
-    leaderelection:
+    leader_election:
       lease_duration: 15
     timingwheel:
       tick: 1
@@ -173,6 +164,36 @@ metadata:
 ---
 apiVersion: v1
 kind: Service
+metadata:
+  labels:
+    app: vanus-etcd
+  name: vanus-etcd
+  namespace: vanus
+spec:
+  clusterIP: None
+  clusterIPs:
+  - None
+  internalTrafficPolicy: Cluster
+  ipFamilies:
+  - IPv4
+  ipFamilyPolicy: SingleStack
+  ports:
+  - name: client
+    port: 2379
+    protocol: TCP
+    targetPort: client
+  - name: peer
+    port: 2380
+    protocol: TCP
+    targetPort: peer
+  publishNotReadyAddresses: true
+  selector:
+    app: vanus-etcd
+  sessionAffinity: None
+  type: ClusterIP
+---
+apiVersion: v1
+kind: Service
 metadata:
   name: vanus-controller
   namespace: vanus
@@ -420,6 +441,143 @@ spec:
 ---
 apiVersion: apps/v1
 kind: StatefulSet
+metadata:
+  labels:
+    app: vanus-etcd
+  name: vanus-etcd
+  namespace: vanus
+spec:
+  podManagementPolicy: Parallel
+  replicas: 3
+  revisionHistoryLimit: 10
+  selector:
+    matchLabels:
+      app: vanus-etcd
+  serviceName: vanus-etcd
+  template:
+    metadata:
+      labels:
+        app: vanus-etcd
+    spec:
+      containers:
+      - env:
+        - name: BITNAMI_DEBUG
+          value: "false"
+        - name: MY_POD_IP
+          valueFrom:
+            fieldRef:
+              apiVersion: v1
+              fieldPath: status.podIP
+        - name: MY_POD_NAME
+          valueFrom:
+            fieldRef:
+              apiVersion: v1
+              fieldPath: metadata.name
+        - name: MY_STS_NAME
+          value: vanus-etcd
+        - name: ETCDCTL_API
+          value: "3"
+        - name: ETCD_ON_K8S
+          value: "yes"
+        - name: ETCD_START_FROM_SNAPSHOT
+          value: "no"
+        - name: ETCD_INIT_SNAPSHOT_FILENAME
+          value: "snapshotdb"
+        - name: ETCD_DISASTER_RECOVERY
+          value: "no"
+        - name: ETCD_NAME
+          value: $(MY_POD_NAME)
+        - name: ETCD_DATA_DIR
+          value: /bitnami/etcd/data
+        - name: ETCD_LOG_LEVEL
+          value: info
+        - name: ALLOW_NONE_AUTHENTICATION
+          value: "yes"
+        - name: ETCD_ADVERTISE_CLIENT_URLS
+          value: http://$(MY_POD_NAME).vanus-etcd.vanus.svc.cluster.local:2379,http://vanus-etcd.vanus.svc.cluster.local:2379
+        - name: ETCD_LISTEN_CLIENT_URLS
+          value: http://0.0.0.0:2379
+        - name: ETCD_INITIAL_ADVERTISE_PEER_URLS
+          value: http://$(MY_POD_NAME).vanus-etcd.vanus.svc.cluster.local:2380
+        - name: ETCD_LISTEN_PEER_URLS
+          value: http://0.0.0.0:2380
+        - name: ETCD_INITIAL_CLUSTER_STATE
+          value: new
+        - name: ETCD_INITIAL_CLUSTER
+          value: vanus-etcd-0=http://vanus-etcd-0.vanus-etcd.vanus.svc.cluster.local:2380,vanus-etcd-1=http://vanus-etcd-1.vanus-etcd.vanus.svc.cluster.local:2380,vanus-etcd-2=http://vanus-etcd-2.vanus-etcd.vanus.svc.cluster.local:2380
+        - name: ETCD_CLUSTER_DOMAIN
+          value: vanus-etcd.vanus.svc.cluster.local
+        image: docker.io/bitnami/etcd:3.5.7-debian-11-r9
+        imagePullPolicy: IfNotPresent
+        lifecycle:
+          preStop:
+            exec:
+              command:
+              - /opt/bitnami/scripts/etcd/prestop.sh
+        livenessProbe:
+          exec:
+            command:
+            - /opt/bitnami/scripts/etcd/healthcheck.sh
+          failureThreshold: 5
+          initialDelaySeconds: 60
+          periodSeconds: 30
+          successThreshold: 1
+          timeoutSeconds: 5
+        name: etcd
+        ports:
+        - containerPort: 2379
+          name: client
+          protocol: TCP
+        - containerPort: 2380
+          name: peer
+          protocol: TCP
+        readinessProbe:
+          exec:
+            command:
+            - /opt/bitnami/scripts/etcd/healthcheck.sh
+          failureThreshold: 5
+          initialDelaySeconds: 60
+          periodSeconds: 10
+          successThreshold: 1
+          timeoutSeconds: 5
+        resources: {}
+        securityContext:
+          allowPrivilegeEscalation: false
+          runAsNonRoot: true
+          runAsUser: 1001
+        terminationMessagePath: /dev/termination-log
+        terminationMessagePolicy: File
+        volumeMounts:
+        - name: data
+          mountPath: /bitnami/etcd
+      dnsPolicy: ClusterFirst
+      restartPolicy: Always
+      schedulerName: default-scheduler
+      securityContext:
+        fsGroup: 1001
+      serviceAccount: default
+      serviceAccountName: default
+      terminationGracePeriodSeconds: 30
+  updateStrategy:
+    type: RollingUpdate
+  volumeClaimTemplates:
+  - apiVersion: v1
+    kind: PersistentVolumeClaim
+    metadata:
+      creationTimestamp: null
+      labels:
+        app: vanus-etcd
+      name: data
+    spec:
+      accessModes:
+      - ReadWriteOnce
+      resources:
+        requests:
+          storage: 10Gi
+      volumeMode: Filesystem
+---
+apiVersion: apps/v1
+kind: StatefulSet
 metadata:
   labels:
     app: vanus-controller
diff --git a/deploy/aws-eks/kustomization.yaml b/deploy/aws-eks/kustomization.yaml
index 53cf444..9b973c9 100644
--- a/deploy/aws-eks/kustomization.yaml
+++ b/deploy/aws-eks/kustomization.yaml
@@ -3,6 +3,7 @@ kind: Kustomization
 resources:
   - yaml/ns.yaml
   - yaml/sc.yaml
+  - yaml/etcd.yaml
   - yaml/controller.yaml
   - yaml/trigger.yaml
   - yaml/gateway.yaml
diff --git a/deploy/aws-eks/yaml/controller.yaml b/deploy/aws-eks/yaml/controller.yaml
index f4b5103..093bd7a 100644
--- a/deploy/aws-eks/yaml/controller.yaml
+++ b/deploy/aws-eks/yaml/controller.yaml
@@ -10,29 +10,20 @@ data:
     ip: ${POD_IP}
     port: 2048
     etcd:
-      - vanus-controller-0.vanus-controller:2379
-      - vanus-controller-1.vanus-controller:2379
-      - vanus-controller-2.vanus-controller:2379
+      - vanus-etcd-0.vanus-etcd:2379
+      - vanus-etcd-1.vanus-etcd:2379
+      - vanus-etcd-2.vanus-etcd:2379
     data_dir: /data
     segment_capacity: 268435456
     replicas: 3
     metadata:
       key_prefix: /vanus
+    leader_election:
+      lease_duration: 15
     topology:
       vanus-controller-0: vanus-controller-0.vanus-controller.vanus.svc:2048
       vanus-controller-1: vanus-controller-1.vanus-controller.vanus.svc:2048
       vanus-controller-2: vanus-controller-2.vanus-controller.vanus.svc:2048
-    embed_etcd:
-      # relative path to ${data_dir} above
-      data_dir: etcd/data
-      listen_client_addr: 0.0.0.0:2379
-      listen_peer_addr: 0.0.0.0:2380
-      advertise_client_addr: ${POD_NAME}.vanus-controller:2379
-      advertise_peer_addr: ${POD_NAME}.vanus-controller:2380
-      clusters:
-        - vanus-controller-0=http://vanus-controller-0.vanus-controller:2380
-        - vanus-controller-1=http://vanus-controller-1.vanus-controller:2380
-        - vanus-controller-2=http://vanus-controller-2.vanus-controller:2380
     observability:
       metrics:
         enable: true
diff --git a/deploy/aws-eks/yaml/etcd.yaml b/deploy/aws-eks/yaml/etcd.yaml
new file mode 100644
index 0000000..35f6a41
--- /dev/null
+++ b/deploy/aws-eks/yaml/etcd.yaml
@@ -0,0 +1,166 @@
+apiVersion: v1
+kind: Service
+metadata:
+  labels:
+    app: vanus-etcd
+  name: vanus-etcd
+  namespace: vanus
+spec:
+  clusterIP: None
+  clusterIPs:
+  - None
+  internalTrafficPolicy: Cluster
+  ipFamilies:
+  - IPv4
+  ipFamilyPolicy: SingleStack
+  ports:
+  - name: client
+    port: 2379
+    protocol: TCP
+    targetPort: client
+  - name: peer
+    port: 2380
+    protocol: TCP
+    targetPort: peer
+  publishNotReadyAddresses: true
+  selector:
+    app: vanus-etcd
+  sessionAffinity: None
+  type: ClusterIP
+---
+apiVersion: apps/v1
+kind: StatefulSet
+metadata:
+  labels:
+    app: vanus-etcd
+  name: vanus-etcd
+  namespace: vanus
+spec:
+  podManagementPolicy: Parallel
+  replicas: 3
+  revisionHistoryLimit: 10
+  selector:
+    matchLabels:
+      app: vanus-etcd
+  serviceName: vanus-etcd
+  template:
+    metadata:
+      labels:
+        app: vanus-etcd
+    spec:
+      containers:
+      - env:
+        - name: BITNAMI_DEBUG
+          value: "false"
+        - name: MY_POD_IP
+          valueFrom:
+            fieldRef:
+              apiVersion: v1
+              fieldPath: status.podIP
+        - name: MY_POD_NAME
+          valueFrom:
+            fieldRef:
+              apiVersion: v1
+              fieldPath: metadata.name
+        - name: MY_STS_NAME
+          value: vanus-etcd
+        - name: ETCDCTL_API
+          value: "3"
+        - name: ETCD_ON_K8S
+          value: "yes"
+        - name: ETCD_START_FROM_SNAPSHOT
+          value: "no"
+        - name: ETCD_INIT_SNAPSHOT_FILENAME
+          value: "snapshotdb"
+        - name: ETCD_DISASTER_RECOVERY
+          value: "no"
+        - name: ETCD_NAME
+          value: $(MY_POD_NAME)
+        - name: ETCD_DATA_DIR
+          value: /bitnami/etcd/data
+        - name: ETCD_LOG_LEVEL
+          value: info
+        - name: ALLOW_NONE_AUTHENTICATION
+          value: "yes"
+        - name: ETCD_ADVERTISE_CLIENT_URLS
+          value: http://$(MY_POD_NAME).vanus-etcd.vanus.svc.cluster.local:2379,http://vanus-etcd.vanus.svc.cluster.local:2379
+        - name: ETCD_LISTEN_CLIENT_URLS
+          value: http://0.0.0.0:2379
+        - name: ETCD_INITIAL_ADVERTISE_PEER_URLS
+          value: http://$(MY_POD_NAME).vanus-etcd.vanus.svc.cluster.local:2380
+        - name: ETCD_LISTEN_PEER_URLS
+          value: http://0.0.0.0:2380
+        - name: ETCD_INITIAL_CLUSTER_STATE
+          value: new
+        - name: ETCD_INITIAL_CLUSTER
+          value: vanus-etcd-0=http://vanus-etcd-0.vanus-etcd.vanus.svc.cluster.local:2380,vanus-etcd-1=http://vanus-etcd-1.vanus-etcd.vanus.svc.cluster.local:2380,vanus-etcd-2=http://vanus-etcd-2.vanus-etcd.vanus.svc.cluster.local:2380
+        - name: ETCD_CLUSTER_DOMAIN
+          value: vanus-etcd.vanus.svc.cluster.local
+        image: docker.io/bitnami/etcd:3.5.7-debian-11-r9
+        imagePullPolicy: IfNotPresent
+        lifecycle:
+          preStop:
+            exec:
+              command:
+              - /opt/bitnami/scripts/etcd/prestop.sh
+        livenessProbe:
+          exec:
+            command:
+            - /opt/bitnami/scripts/etcd/healthcheck.sh
+          failureThreshold: 5
+          initialDelaySeconds: 60
+          periodSeconds: 30
+          successThreshold: 1
+          timeoutSeconds: 5
+        name: etcd
+        ports:
+        - containerPort: 2379
+          name: client
+          protocol: TCP
+        - containerPort: 2380
+          name: peer
+          protocol: TCP
+        readinessProbe:
+          exec:
+            command:
+            - /opt/bitnami/scripts/etcd/healthcheck.sh
+          failureThreshold: 5
+          initialDelaySeconds: 60
+          periodSeconds: 10
+          successThreshold: 1
+          timeoutSeconds: 5
+        resources: {}
+        securityContext:
+          allowPrivilegeEscalation: false
+          runAsNonRoot: true
+          runAsUser: 1001
+        terminationMessagePath: /dev/termination-log
+        terminationMessagePolicy: File
+        volumeMounts:
+        - name: data
+          mountPath: /bitnami/etcd
+      dnsPolicy: ClusterFirst
+      restartPolicy: Always
+      schedulerName: default-scheduler
+      securityContext:
+        fsGroup: 1001
+      serviceAccount: default
+      serviceAccountName: default
+      terminationGracePeriodSeconds: 30
+  updateStrategy:
+    type: RollingUpdate
+  volumeClaimTemplates:
+  - apiVersion: v1
+    kind: PersistentVolumeClaim
+    metadata:
+      creationTimestamp: null
+      labels:
+        app: vanus-etcd
+      name: data
+    spec:
+      accessModes:
+      - ReadWriteOnce
+      resources:
+        requests:
+          storage: 10Gi
+      volumeMode: Filesystem
\ No newline at end of file
diff --git a/deploy/aws-eks/yaml/timer.yaml b/deploy/aws-eks/yaml/timer.yaml
index 0a20bfa..a2e8f31 100644
--- a/deploy/aws-eks/yaml/timer.yaml
+++ b/deploy/aws-eks/yaml/timer.yaml
@@ -4,12 +4,12 @@ data:
     name: "timer"
     ip: ${POD_IP}
     etcd:
-      - vanus-controller-0.vanus-controller:2379
-      - vanus-controller-1.vanus-controller:2379
-      - vanus-controller-2.vanus-controller:2379
+      - vanus-etcd-0.vanus-etcd:2379
+      - vanus-etcd-1.vanus-etcd:2379
+      - vanus-etcd-2.vanus-etcd:2379
     metadata:
       key_prefix: "/vanus"
-    leaderelection:
+    leader_election:
       lease_duration: 15
     timingwheel:
       tick: 1
diff --git a/deploy/bare-metal/vanus.yml b/deploy/bare-metal/vanus.yml
index b969953..a9857e5 100644
--- a/deploy/bare-metal/vanus.yml
+++ b/deploy/bare-metal/vanus.yml
@@ -11,29 +11,20 @@ data:
     ip: ${POD_IP}
     port: 2048
     etcd:
-      - vanus-controller-0.vanus-controller:2379
-      - vanus-controller-1.vanus-controller:2379
-      - vanus-controller-2.vanus-controller:2379
+      - vanus-etcd-0.vanus-etcd:2379
+      - vanus-etcd-1.vanus-etcd:2379
+      - vanus-etcd-2.vanus-etcd:2379
     data_dir: /data
     segment_capacity: 268435456
     replicas: 3
     metadata:
       key_prefix: /vanus
+    leader_election:
+      lease_duration: 15
     topology:
       vanus-controller-0: vanus-controller-0.vanus-controller.vanus.svc:2048
       vanus-controller-1: vanus-controller-1.vanus-controller.vanus.svc:2048
       vanus-controller-2: vanus-controller-2.vanus-controller.vanus.svc:2048
-    embed_etcd:
-      # relative path to ${data_dir} above
-      data_dir: etcd/data
-      listen_client_addr: 0.0.0.0:2379
-      listen_peer_addr: 0.0.0.0:2380
-      advertise_client_addr: ${POD_NAME}.vanus-controller:2379
-      advertise_peer_addr: ${POD_NAME}.vanus-controller:2380
-      clusters:
-        - vanus-controller-0=http://vanus-controller-0.vanus-controller:2380
-        - vanus-controller-1=http://vanus-controller-1.vanus-controller:2380
-        - vanus-controller-2=http://vanus-controller-2.vanus-controller:2380
 kind: ConfigMap
 metadata:
   name: config-controller
@@ -96,12 +87,12 @@ data:
     name: "timer"
     ip: ${POD_IP}
     etcd:
-      - vanus-controller-0.vanus-controller:2379
-      - vanus-controller-1.vanus-controller:2379
-      - vanus-controller-2.vanus-controller:2379
+      - vanus-etcd-0.vanus-etcd:2379
+      - vanus-etcd-1.vanus-etcd:2379
+      - vanus-etcd-2.vanus-etcd:2379
     metadata:
       key_prefix: "/vanus"
-    leaderelection:
+    leader_election:
       lease_duration: 15
     timingwheel:
       tick: 1
diff --git a/deploy/docker-compose/config/timer.yaml b/deploy/docker-compose/config/timer.yaml
index 769b914..7d4270e 100644
--- a/deploy/docker-compose/config/timer.yaml
+++ b/deploy/docker-compose/config/timer.yaml
@@ -5,7 +5,7 @@ etcd:
 replicas: 1
 metadata:
   key_prefix: "/standalone"
-leaderelection:
+leader_election:
   lease_duration: 15
 timingwheel:
   tick: 1
diff --git a/deploy/on-premises/controller.yaml b/deploy/on-premises/controller.yaml
index 03ecc10..5856d14 100644
--- a/deploy/on-premises/controller.yaml
+++ b/deploy/on-premises/controller.yaml
@@ -10,28 +10,19 @@ data:
     ip: ${POD_IP}
     port: 2048
     etcd:
-      - vanus-controller-0.vanus-controller:2379
-      - vanus-controller-1.vanus-controller:2379
-      - vanus-controller-2.vanus-controller:2379
+      - vanus-etcd-0.vanus-etcd:2379
+      - vanus-etcd-1.vanus-etcd:2379
+      - vanus-etcd-2.vanus-etcd:2379
     data_dir: /data
     replicas: 3
     metadata:
       key_prefix: /vanus
+    leader_election:
+      lease_duration: 15
     topology:
       vanus-controller-0: vanus-controller-0.vanus-controller.vanus.svc:2048
       vanus-controller-1: vanus-controller-1.vanus-controller.vanus.svc:2048
       vanus-controller-2: vanus-controller-2.vanus-controller.vanus.svc:2048
-    embed_etcd:
-      # relative path to ${data_dir} above
-      data_dir: etcd/data
-      listen_client_addr: 0.0.0.0:2379
-      listen_peer_addr: 0.0.0.0:2380
-      advertise_client_addr: ${POD_NAME}.vanus-controller:2379
-      advertise_peer_addr: ${POD_NAME}.vanus-controller:2380
-      clusters:
-        - vanus-controller-0=http://vanus-controller-0.vanus-controller:2380
-        - vanus-controller-1=http://vanus-controller-1.vanus-controller:2380
-        - vanus-controller-2=http://vanus-controller-2.vanus-controller:2380
 ---
 apiVersion: v1
 kind: Service
@@ -59,17 +50,6 @@ spec:
       app: vanus-controller
   replicas: 3
   serviceName: vanus-controller
-  volumeClaimTemplates:
-    - metadata:
-        name: data
-        labels:
-          app: vanus-controller
-      spec:
-        accessModes:
-          - ReadWriteOnce
-        resources:
-          requests:
-            storage: 1Gi
   template:
     metadata:
       labels:
@@ -86,10 +66,6 @@ spec:
           ports:
             - name: grpc
               containerPort: 2048
-            - name: etcd-client
-              containerPort: 2379
-            - name: etcd-peer
-              containerPort: 2380
             - name: metrics
               containerPort: 2112
           env:
@@ -106,8 +82,6 @@ spec:
           volumeMounts:
             - name: config-controller
               mountPath: /vanus/config
-            - name: data
-              mountPath: /data
       volumes:
         - name: config-controller
           configMap:
diff --git a/deploy/on-premises/etcd.yaml b/deploy/on-premises/etcd.yaml
new file mode 100644
index 0000000..35f6a41
--- /dev/null
+++ b/deploy/on-premises/etcd.yaml
@@ -0,0 +1,166 @@
+apiVersion: v1
+kind: Service
+metadata:
+  labels:
+    app: vanus-etcd
+  name: vanus-etcd
+  namespace: vanus
+spec:
+  clusterIP: None
+  clusterIPs:
+  - None
+  internalTrafficPolicy: Cluster
+  ipFamilies:
+  - IPv4
+  ipFamilyPolicy: SingleStack
+  ports:
+  - name: client
+    port: 2379
+    protocol: TCP
+    targetPort: client
+  - name: peer
+    port: 2380
+    protocol: TCP
+    targetPort: peer
+  publishNotReadyAddresses: true
+  selector:
+    app: vanus-etcd
+  sessionAffinity: None
+  type: ClusterIP
+---
+apiVersion: apps/v1
+kind: StatefulSet
+metadata:
+  labels:
+    app: vanus-etcd
+  name: vanus-etcd
+  namespace: vanus
+spec:
+  podManagementPolicy: Parallel
+  replicas: 3
+  revisionHistoryLimit: 10
+  selector:
+    matchLabels:
+      app: vanus-etcd
+  serviceName: vanus-etcd
+  template:
+    metadata:
+      labels:
+        app: vanus-etcd
+    spec:
+      containers:
+      - env:
+        - name: BITNAMI_DEBUG
+          value: "false"
+        - name: MY_POD_IP
+          valueFrom:
+            fieldRef:
+              apiVersion: v1
+              fieldPath: status.podIP
+        - name: MY_POD_NAME
+          valueFrom:
+            fieldRef:
+              apiVersion: v1
+              fieldPath: metadata.name
+        - name: MY_STS_NAME
+          value: vanus-etcd
+        - name: ETCDCTL_API
+          value: "3"
+        - name: ETCD_ON_K8S
+          value: "yes"
+        - name: ETCD_START_FROM_SNAPSHOT
+          value: "no"
+        - name: ETCD_INIT_SNAPSHOT_FILENAME
+          value: "snapshotdb"
+        - name: ETCD_DISASTER_RECOVERY
+          value: "no"
+        - name: ETCD_NAME
+          value: $(MY_POD_NAME)
+        - name: ETCD_DATA_DIR
+          value: /bitnami/etcd/data
+        - name: ETCD_LOG_LEVEL
+          value: info
+        - name: ALLOW_NONE_AUTHENTICATION
+          value: "yes"
+        - name: ETCD_ADVERTISE_CLIENT_URLS
+          value: http://$(MY_POD_NAME).vanus-etcd.vanus.svc.cluster.local:2379,http://vanus-etcd.vanus.svc.cluster.local:2379
+        - name: ETCD_LISTEN_CLIENT_URLS
+          value: http://0.0.0.0:2379
+        - name: ETCD_INITIAL_ADVERTISE_PEER_URLS
+          value: http://$(MY_POD_NAME).vanus-etcd.vanus.svc.cluster.local:2380
+        - name: ETCD_LISTEN_PEER_URLS
+          value: http://0.0.0.0:2380
+        - name: ETCD_INITIAL_CLUSTER_STATE
+          value: new
+        - name: ETCD_INITIAL_CLUSTER
+          value: vanus-etcd-0=http://vanus-etcd-0.vanus-etcd.vanus.svc.cluster.local:2380,vanus-etcd-1=http://vanus-etcd-1.vanus-etcd.vanus.svc.cluster.local:2380,vanus-etcd-2=http://vanus-etcd-2.vanus-etcd.vanus.svc.cluster.local:2380
+        - name: ETCD_CLUSTER_DOMAIN
+          value: vanus-etcd.vanus.svc.cluster.local
+        image: docker.io/bitnami/etcd:3.5.7-debian-11-r9
+        imagePullPolicy: IfNotPresent
+        lifecycle:
+          preStop:
+            exec:
+              command:
+              - /opt/bitnami/scripts/etcd/prestop.sh
+        livenessProbe:
+          exec:
+            command:
+            - /opt/bitnami/scripts/etcd/healthcheck.sh
+          failureThreshold: 5
+          initialDelaySeconds: 60
+          periodSeconds: 30
+          successThreshold: 1
+          timeoutSeconds: 5
+        name: etcd
+        ports:
+        - containerPort: 2379
+          name: client
+          protocol: TCP
+        - containerPort: 2380
+          name: peer
+          protocol: TCP
+        readinessProbe:
+          exec:
+            command:
+            - /opt/bitnami/scripts/etcd/healthcheck.sh
+          failureThreshold: 5
+          initialDelaySeconds: 60
+          periodSeconds: 10
+          successThreshold: 1
+          timeoutSeconds: 5
+        resources: {}
+        securityContext:
+          allowPrivilegeEscalation: false
+          runAsNonRoot: true
+          runAsUser: 1001
+        terminationMessagePath: /dev/termination-log
+        terminationMessagePolicy: File
+        volumeMounts:
+        - name: data
+          mountPath: /bitnami/etcd
+      dnsPolicy: ClusterFirst
+      restartPolicy: Always
+      schedulerName: default-scheduler
+      securityContext:
+        fsGroup: 1001
+      serviceAccount: default
+      serviceAccountName: default
+      terminationGracePeriodSeconds: 30
+  updateStrategy:
+    type: RollingUpdate
+  volumeClaimTemplates:
+  - apiVersion: v1
+    kind: PersistentVolumeClaim
+    metadata:
+      creationTimestamp: null
+      labels:
+        app: vanus-etcd
+      name: data
+    spec:
+      accessModes:
+      - ReadWriteOnce
+      resources:
+        requests:
+          storage: 10Gi
+      volumeMode: Filesystem
\ No newline at end of file
diff --git a/deploy/on-premises/kustomization.yaml b/deploy/on-premises/kustomization.yaml
index 8684569..fd249a1 100644
--- a/deploy/on-premises/kustomization.yaml
+++ b/deploy/on-premises/kustomization.yaml
@@ -2,6 +2,7 @@ apiVersion: kustomize.config.k8s.io/v1beta1
 kind: Kustomization
 resources:
   - namespace.yaml
+  - etcd.yaml
   - controller.yaml
   - trigger.yaml
   - gateway.yaml
diff --git a/deploy/on-premises/timer.yaml b/deploy/on-premises/timer.yaml
index 409bba8..2ba642c 100644
--- a/deploy/on-premises/timer.yaml
+++ b/deploy/on-premises/timer.yaml
@@ -8,12 +8,12 @@ data:
     name: "timer"
     ip: ${POD_IP}
     etcd:
-      - vanus-controller-0.vanus-controller:2379
-      - vanus-controller-1.vanus-controller:2379
-      - vanus-controller-2.vanus-controller:2379
+      - vanus-etcd-0.vanus-etcd:2379
+      - vanus-etcd-1.vanus-etcd:2379
+      - vanus-etcd-2.vanus-etcd:2379
     metadata:
       key_prefix: "/vanus"
-    leaderelection:
+    leader_election:
       lease_duration: 15
     timingwheel:
       tick: 1
diff --git a/deploy/poc/kustomization.yaml b/deploy/poc/kustomization.yaml
index 2726a92..f7a7ac9 100644
--- a/deploy/poc/kustomization.yaml
+++ b/deploy/poc/kustomization.yaml
@@ -2,6 +2,7 @@ apiVersion: kustomize.config.k8s.io/v1beta1
 kind: Kustomization
 resources:
   - yaml/ns.yaml
+  - yaml/etcd.yaml
   - yaml/controller.yaml
   - yaml/trigger.yaml
   - yaml/gateway.yaml
diff --git a/deploy/poc/poc.yaml b/deploy/poc/poc.yaml
index f7a70d4..469a2dc 100644
--- a/deploy/poc/poc.yaml
+++ b/deploy/poc/poc.yaml
@@ -11,29 +11,20 @@ data:
     ip: ${POD_IP}
     port: 2048
     etcd:
-      - vanus-controller-0.vanus-controller:2379
-      - vanus-controller-1.vanus-controller:2379
-      - vanus-controller-2.vanus-controller:2379
+      - vanus-etcd-0.vanus-etcd:2379
+      - vanus-etcd-1.vanus-etcd:2379
+      - vanus-etcd-2.vanus-etcd:2379
     data_dir: /data
     segment_capacity: 268435456
     replicas: 3
     metadata:
       key_prefix: /vanus
+    leader_election:
+      lease_duration: 15
     topology:
       vanus-controller-0: vanus-controller-0.vanus-controller.vanus.svc:2048
       vanus-controller-1: vanus-controller-1.vanus-controller.vanus.svc:2048
       vanus-controller-2: vanus-controller-2.vanus-controller.vanus.svc:2048
-    embed_etcd:
-      # relative path to ${data_dir} above
-      data_dir: etcd/data
-      listen_client_addr: 0.0.0.0:2379
-      listen_peer_addr: 0.0.0.0:2380
-      advertise_client_addr: ${POD_NAME}.vanus-controller:2379
-      advertise_peer_addr: ${POD_NAME}.vanus-controller:2380
-      clusters:
-        - vanus-controller-0=http://vanus-controller-0.vanus-controller:2380
-        - vanus-controller-1=http://vanus-controller-1.vanus-controller:2380
-        - vanus-controller-2=http://vanus-controller-2.vanus-controller:2380
     observability:
       metrics:
         enable: true
@@ -110,12 +101,12 @@ data:
     name: "timer"
     ip: ${POD_IP}
     etcd:
-      - vanus-controller-0.vanus-controller:2379
-      - vanus-controller-1.vanus-controller:2379
-      - vanus-controller-2.vanus-controller:2379
+      - vanus-etcd-0.vanus-etcd:2379
+      - vanus-etcd-1.vanus-etcd:2379
+      - vanus-etcd-2.vanus-etcd:2379
     metadata:
       key_prefix: "/vanus"
-    leaderelection:
+    leader_election:
       lease_duration: 15
     timingwheel:
       tick: 1
@@ -160,6 +151,36 @@ metadata:
 ---
 apiVersion: v1
 kind: Service
+metadata:
+  labels:
+    app: vanus-etcd
+  name: vanus-etcd
+  namespace: vanus
+spec:
+  clusterIP: None
+  clusterIPs:
+  - None
+  internalTrafficPolicy: Cluster
+  ipFamilies:
+  - IPv4
+  ipFamilyPolicy: SingleStack
+  ports:
+  - name: client
+    port: 2379
+    protocol: TCP
+    targetPort: client
+  - name: peer
+    port: 2380
+    protocol: TCP
+    targetPort: peer
+  publishNotReadyAddresses: true
+  selector:
+    app: vanus-etcd
+  sessionAffinity: None
+  type: ClusterIP
+---
+apiVersion: v1
+kind: Service
 metadata:
   name: vanus-controller
   namespace: vanus
@@ -417,18 +438,12 @@ spec:
         ports:
         - containerPort: 2048
           name: grpc
-        - containerPort: 2379
-          name: etcd-client
-        - containerPort: 2380
-          name: etcd-peer
         - containerPort: 2112
           name: metrics
           protocol: TCP
         volumeMounts:
         - mountPath: /vanus/config
           name: config-controller
-        - mountPath: /data
-          name: data
       - args:
         - --no-collector.wifi
         - --no-collector.hwmon
@@ -451,17 +466,6 @@ spec:
       - configMap:
           name: config-controller
         name: config-controller
-  volumeClaimTemplates:
-  - metadata:
-      labels:
-        app: vanus-controller
-      name: data
-    spec:
-      accessModes:
-      - ReadWriteOnce
-      resources:
-        requests:
-          storage: 20Gi
 ---
 apiVersion: apps/v1
 kind: StatefulSet
@@ -542,3 +546,140 @@ spec:
       resources:
         requests:
           storage: 50Gi
+---
+apiVersion: apps/v1
+kind: StatefulSet
+metadata:
+  labels:
+    app: vanus-etcd
+  name: vanus-etcd
+  namespace: vanus
+spec:
+  podManagementPolicy: Parallel
+  replicas: 3
+  revisionHistoryLimit: 10
+  selector:
+    matchLabels:
+      app: vanus-etcd
+  serviceName: vanus-etcd
+  template:
+    metadata:
+      labels:
+        app: vanus-etcd
+    spec:
+      containers:
+      - env:
+        - name: BITNAMI_DEBUG
+          value: "false"
+        - name: MY_POD_IP
+          valueFrom:
+            fieldRef:
+              apiVersion: v1
+              fieldPath: status.podIP
+        - name: MY_POD_NAME
+          valueFrom:
+            fieldRef:
+              apiVersion: v1
+              fieldPath: metadata.name
+        - name: MY_STS_NAME
+          value: vanus-etcd
+        - name: ETCDCTL_API
+          value: "3"
+        - name: ETCD_ON_K8S
+          value: "yes"
+        - name: ETCD_START_FROM_SNAPSHOT
+          value: "no"
+        - name: ETCD_INIT_SNAPSHOT_FILENAME
+          value: "snapshotdb"
+        - name: ETCD_DISASTER_RECOVERY
+          value: "no"
+        - name: ETCD_NAME
+          value: $(MY_POD_NAME)
+        - name: ETCD_DATA_DIR
+          value: /bitnami/etcd/data
+        - name: ETCD_LOG_LEVEL
+          value: info
+        - name: ALLOW_NONE_AUTHENTICATION
+          value: "yes"
+        - name: ETCD_ADVERTISE_CLIENT_URLS
+          value: http://$(MY_POD_NAME).vanus-etcd.vanus.svc.cluster.local:2379,http://vanus-etcd.vanus.svc.cluster.local:2379
+        - name: ETCD_LISTEN_CLIENT_URLS
+          value: http://0.0.0.0:2379
+        - name: ETCD_INITIAL_ADVERTISE_PEER_URLS
+          value: http://$(MY_POD_NAME).vanus-etcd.vanus.svc.cluster.local:2380
+        - name: ETCD_LISTEN_PEER_URLS
+          value: http://0.0.0.0:2380
+        - name: ETCD_INITIAL_CLUSTER_STATE
+          value: new
+        - name: ETCD_INITIAL_CLUSTER
+          value: vanus-etcd-0=http://vanus-etcd-0.vanus-etcd.vanus.svc.cluster.local:2380,vanus-etcd-1=http://vanus-etcd-1.vanus-etcd.vanus.svc.cluster.local:2380,vanus-etcd-2=http://vanus-etcd-2.vanus-etcd.vanus.svc.cluster.local:2380
+        - name: ETCD_CLUSTER_DOMAIN
+          value: vanus-etcd.vanus.svc.cluster.local
+        image: docker.io/bitnami/etcd:3.5.7-debian-11-r9
+        imagePullPolicy: IfNotPresent
+        lifecycle:
+          preStop:
+            exec:
+              command:
+              - /opt/bitnami/scripts/etcd/prestop.sh
+        livenessProbe:
+          exec:
+            command:
+            - /opt/bitnami/scripts/etcd/healthcheck.sh
+          failureThreshold: 5
+          initialDelaySeconds: 60
+          periodSeconds: 30
+          successThreshold: 1
+          timeoutSeconds: 5
+        name: etcd
+        ports:
+        - containerPort: 2379
+          name: client
+          protocol: TCP
+        - containerPort: 2380
+          name: peer
+          protocol: TCP
+        readinessProbe:
+          exec:
+            command:
+            - /opt/bitnami/scripts/etcd/healthcheck.sh
+          failureThreshold: 5
+          initialDelaySeconds: 60
+          periodSeconds: 10
+          successThreshold: 1
+          timeoutSeconds: 5
+        resources: {}
+        securityContext:
+          allowPrivilegeEscalation: false
+          runAsNonRoot: true
+          runAsUser: 1001
+        terminationMessagePath: /dev/termination-log
+        terminationMessagePolicy: File
+        volumeMounts:
+        - name: data
+          mountPath: /bitnami/etcd
+      dnsPolicy: ClusterFirst
+      restartPolicy: Always
+      schedulerName: default-scheduler
+      securityContext:
+        fsGroup: 1001
+      serviceAccount: default
+      serviceAccountName: default
+      terminationGracePeriodSeconds: 30
+  updateStrategy:
+    type: RollingUpdate
+  volumeClaimTemplates:
+  - apiVersion: v1
+    kind: PersistentVolumeClaim
+    metadata:
+      creationTimestamp: null
+      labels:
+        app: vanus-etcd
+      name: data
+    spec:
+      accessModes:
+      - ReadWriteOnce
+      resources:
+        requests:
+          storage: 10Gi
+      volumeMode: Filesystem
diff --git a/deploy/poc/yaml/controller.yaml b/deploy/poc/yaml/controller.yaml
index ee441fc..3182824 100644
--- a/deploy/poc/yaml/controller.yaml
+++ b/deploy/poc/yaml/controller.yaml
@@ -10,29 +10,20 @@ data:
     ip: ${POD_IP}
     port: 2048
     etcd:
-      - vanus-controller-0.vanus-controller:2379
-      - vanus-controller-1.vanus-controller:2379
-      - vanus-controller-2.vanus-controller:2379
+      - vanus-etcd-0.vanus-etcd:2379
+      - vanus-etcd-1.vanus-etcd:2379
+      - vanus-etcd-2.vanus-etcd:2379
     data_dir: /data
     segment_capacity: 268435456
     replicas: 3
     metadata:
       key_prefix: /vanus
+    leader_election:
+      lease_duration: 15
     topology:
       vanus-controller-0: vanus-controller-0.vanus-controller.vanus.svc:2048
       vanus-controller-1: vanus-controller-1.vanus-controller.vanus.svc:2048
       vanus-controller-2: vanus-controller-2.vanus-controller.vanus.svc:2048
-    embed_etcd:
-      # relative path to ${data_dir} above
-      data_dir: etcd/data
-      listen_client_addr: 0.0.0.0:2379
-      listen_peer_addr: 0.0.0.0:2380
-      advertise_client_addr: ${POD_NAME}.vanus-controller:2379
-      advertise_peer_addr: ${POD_NAME}.vanus-controller:2380
-      clusters:
-        - vanus-controller-0=http://vanus-controller-0.vanus-controller:2380
-        - vanus-controller-1=http://vanus-controller-1.vanus-controller:2380
-        - vanus-controller-2=http://vanus-controller-2.vanus-controller:2380
     observability:
       metrics:
         enable: true
@@ -94,18 +85,12 @@ spec:
           ports:
             - containerPort: 2048
               name: grpc
-            - containerPort: 2379
-              name: etcd-client
-            - containerPort: 2380
-              name: etcd-peer
             - containerPort: 2112
               protocol: TCP
               name: metrics
           volumeMounts:
             - mountPath: /vanus/config
               name: config-controller
-            - mountPath: /data
-              name: data
         - name: node-exporter
           image: prom/node-exporter
           args:
@@ -127,15 +112,4 @@ spec:
       volumes:
         - configMap:
             name: config-controller
-          name: config-controller
-  volumeClaimTemplates:
-    - metadata:
-        labels:
-          app: vanus-controller
-        name: data
-      spec:
-        accessModes:
-          - ReadWriteOnce
-        resources:
-          requests:
-            storage: 20Gi
\ No newline at end of file
+          name: config-controller
\ No newline at end of file
diff --git a/deploy/poc/yaml/etcd.yaml b/deploy/poc/yaml/etcd.yaml
new file mode 100644
index 0000000..35f6a41
--- /dev/null
+++ b/deploy/poc/yaml/etcd.yaml
@@ -0,0 +1,166 @@
+apiVersion: v1
+kind: Service
+metadata:
+  labels:
+    app: vanus-etcd
+  name: vanus-etcd
+  namespace: vanus
+spec:
+  clusterIP: None
+  clusterIPs:
+  - None
+  internalTrafficPolicy: Cluster
+  ipFamilies:
+  - IPv4
+  ipFamilyPolicy: SingleStack
+  ports:
+  - name: client
+    port: 2379
+    protocol: TCP
+    targetPort: client
+  - name: peer
+    port: 2380
+    protocol: TCP
+    targetPort: peer
+  publishNotReadyAddresses: true
+  selector:
+    app: vanus-etcd
+  sessionAffinity: None
+  type: ClusterIP
+---
+apiVersion: apps/v1
+kind: StatefulSet
+metadata:
+  labels:
+    app: vanus-etcd
+  name: vanus-etcd
+  namespace: vanus
+spec:
+  podManagementPolicy: Parallel
+  replicas: 3
+  revisionHistoryLimit: 10
+  selector:
+    matchLabels:
+      app: vanus-etcd
+  serviceName: vanus-etcd
+  template:
+    metadata:
+      labels:
+        app: vanus-etcd
+    spec:
+      containers:
+      - env:
+        - name: BITNAMI_DEBUG
+          value: "false"
+        - name: MY_POD_IP
+          valueFrom:
+            fieldRef:
+              apiVersion: v1
+              fieldPath: status.podIP
+        - name: MY_POD_NAME
+          valueFrom:
+            fieldRef:
+              apiVersion: v1
+              fieldPath: metadata.name
+        - name: MY_STS_NAME
+          value: vanus-etcd
+        - name: ETCDCTL_API
+          value: "3"
+        - name: ETCD_ON_K8S
+          value: "yes"
+        - name: ETCD_START_FROM_SNAPSHOT
+          value: "no"
+        - name: ETCD_INIT_SNAPSHOT_FILENAME
+          value: "snapshotdb"
+        - name: ETCD_DISASTER_RECOVERY
+          value: "no"
+        - name: ETCD_NAME
+          value: $(MY_POD_NAME)
+        - name: ETCD_DATA_DIR
+          value: /bitnami/etcd/data
+        - name: ETCD_LOG_LEVEL
+          value: info
+        - name: ALLOW_NONE_AUTHENTICATION
+          value: "yes"
+        - name: ETCD_ADVERTISE_CLIENT_URLS
+          value: http://$(MY_POD_NAME).vanus-etcd.vanus.svc.cluster.local:2379,http://vanus-etcd.vanus.svc.cluster.local:2379
+        - name: ETCD_LISTEN_CLIENT_URLS
+          value: http://0.0.0.0:2379
+        - name: ETCD_INITIAL_ADVERTISE_PEER_URLS
+          value: http://$(MY_POD_NAME).vanus-etcd.vanus.svc.cluster.local:2380
+        - name: ETCD_LISTEN_PEER_URLS
+          value: http://0.0.0.0:2380
+        - name: ETCD_INITIAL_CLUSTER_STATE
+          value: new
+        - name: ETCD_INITIAL_CLUSTER
+          value: vanus-etcd-0=http://vanus-etcd-0.vanus-etcd.vanus.svc.cluster.local:2380,vanus-etcd-1=http://vanus-etcd-1.vanus-etcd.vanus.svc.cluster.local:2380,vanus-etcd-2=http://vanus-etcd-2.vanus-etcd.vanus.svc.cluster.local:2380
+        - name: ETCD_CLUSTER_DOMAIN
+          value: vanus-etcd.vanus.svc.cluster.local
+        image: docker.io/bitnami/etcd:3.5.7-debian-11-r9
+        imagePullPolicy: IfNotPresent
+        lifecycle:
+          preStop:
+            exec:
+              command:
+              - /opt/bitnami/scripts/etcd/prestop.sh
+        livenessProbe:
+          exec:
+            command:
+            - /opt/bitnami/scripts/etcd/healthcheck.sh
+          failureThreshold: 5
+          initialDelaySeconds: 60
+          periodSeconds: 30
+          successThreshold: 1
+          timeoutSeconds: 5
+        name: etcd
+        ports:
+        - containerPort: 2379
+          name: client
+          protocol: TCP
+        - containerPort: 2380
+          name: peer
+          protocol: TCP
+        readinessProbe:
+          exec:
+            command:
+            - /opt/bitnami/scripts/etcd/healthcheck.sh
+          failureThreshold: 5
+          initialDelaySeconds: 60
+          periodSeconds: 10
+          successThreshold: 1
+          timeoutSeconds: 5
+        resources: {}
+        securityContext:
+          allowPrivilegeEscalation: false
+          runAsNonRoot: true
+          runAsUser: 1001
+        terminationMessagePath: /dev/termination-log
+        terminationMessagePolicy: File
+        volumeMounts:
+        - name: data
+          mountPath: /bitnami/etcd
+      dnsPolicy: ClusterFirst
+      restartPolicy: Always
+      schedulerName: default-scheduler
+      securityContext:
+        fsGroup: 1001
+      serviceAccount: default
+      serviceAccountName: default
+      terminationGracePeriodSeconds: 30
+  updateStrategy:
+    type: RollingUpdate
+  volumeClaimTemplates:
+  - apiVersion: v1
+    kind: PersistentVolumeClaim
+    metadata:
+      creationTimestamp: null
+      labels:
+        app: vanus-etcd
+      name: data
+    spec:
+      accessModes:
+      - ReadWriteOnce
+      resources:
+        requests:
+          storage: 10Gi
+      volumeMode: Filesystem
\ No newline at end of file
diff --git a/deploy/poc/yaml/timer.yaml b/deploy/poc/yaml/timer.yaml
index 0b52b00..4f24bd4 100644
--- a/deploy/poc/yaml/timer.yaml
+++ b/deploy/poc/yaml/timer.yaml
@@ -4,12 +4,12 @@ data:
     name: "timer"
     ip: ${POD_IP}
     etcd:
-      - vanus-controller-0.vanus-controller:2379
-      - vanus-controller-1.vanus-controller:2379
-      - vanus-controller-2.vanus-controller:2379
+      - vanus-etcd-0.vanus-etcd:2379
+      - vanus-etcd-1.vanus-etcd:2379
+      - vanus-etcd-2.vanus-etcd:2379
     metadata:
       key_prefix: "/vanus"
-    leaderelection:
+    leader_election:
       lease_duration: 15
     timingwheel:
       tick: 1
diff --git a/deploy/vanus.cn.yaml b/deploy/vanus.cn.yaml
index 27557f8..61a2f7a 100644
--- a/deploy/vanus.cn.yaml
+++ b/deploy/vanus.cn.yaml
@@ -11,28 +11,19 @@ data:
     ip: ${POD_IP}
     port: 2048
     etcd:
-      - vanus-controller-0.vanus-controller:2379
-      - vanus-controller-1.vanus-controller:2379
-      - vanus-controller-2.vanus-controller:2379
+      - vanus-etcd-0.vanus-etcd:2379
+      - vanus-etcd-1.vanus-etcd:2379
+      - vanus-etcd-2.vanus-etcd:2379
     data_dir: /data
     replicas: 3
     metadata:
       key_prefix: /vanus
+    leader_election:
+      lease_duration: 15
     topology:
       vanus-controller-0: vanus-controller-0.vanus-controller.vanus.svc:2048
       vanus-controller-1: vanus-controller-1.vanus-controller.vanus.svc:2048
       vanus-controller-2: vanus-controller-2.vanus-controller.vanus.svc:2048
-    embed_etcd:
-      # relative path to ${data_dir} above
-      data_dir: etcd/data
-      listen_client_addr: 0.0.0.0:2379
-      listen_peer_addr: 0.0.0.0:2380
-      advertise_client_addr: ${POD_NAME}.vanus-controller:2379
-      advertise_peer_addr: ${POD_NAME}.vanus-controller:2380
-      clusters:
-        - vanus-controller-0=http://vanus-controller-0.vanus-controller:2380
-        - vanus-controller-1=http://vanus-controller-1.vanus-controller:2380
-        - vanus-controller-2=http://vanus-controller-2.vanus-controller:2380
 kind: ConfigMap
 metadata:
   name: config-controller
@@ -88,12 +79,12 @@ data:
     name: "timer"
     ip: ${POD_IP}
     etcd:
-      - vanus-controller-0.vanus-controller:2379
-      - vanus-controller-1.vanus-controller:2379
-      - vanus-controller-2.vanus-controller:2379
+      - vanus-etcd-0.vanus-etcd:2379
+      - vanus-etcd-1.vanus-etcd:2379
+      - vanus-etcd-2.vanus-etcd:2379
     metadata:
       key_prefix: "/vanus"
-    leaderelection:
+    leader_election:
       lease_duration: 15
     timingwheel:
       tick: 1
@@ -124,6 +115,36 @@ metadata:
 ---
 apiVersion: v1
 kind: Service
+metadata:
+  labels:
+    app: vanus-etcd
+  name: vanus-etcd
+  namespace: vanus
+spec:
+  clusterIP: None
+  clusterIPs:
+  - None
+  internalTrafficPolicy: Cluster
+  ipFamilies:
+  - IPv4
+  ipFamilyPolicy: SingleStack
+  ports:
+  - name: client
+    port: 2379
+    protocol: TCP
+    targetPort: client
+  - name: peer
+    port: 2380
+    protocol: TCP
+    targetPort: peer
+  publishNotReadyAddresses: true
+  selector:
+    app: vanus-etcd
+  sessionAffinity: None
+  type: ClusterIP
+---
+apiVersion: v1
+kind: Service
 metadata:
   name: vanus-controller
   namespace: vanus
@@ -320,32 +341,15 @@ spec:
           ports:
             - containerPort: 2048
               name: grpc
-            - containerPort: 2379
-              name: etcd-client
-            - containerPort: 2380
-              name: etcd-peer
             - containerPort: 2112
               name: metrics
           volumeMounts:
             - mountPath: /vanus/config
               name: config-controller
-            - mountPath: /data
-              name: data
       volumes:
         - configMap:
             name: config-controller
           name: config-controller
-  volumeClaimTemplates:
-    - metadata:
-        labels:
-          app: vanus-controller
-        name: data
-      spec:
-        accessModes:
-          - ReadWriteOnce
-        resources:
-          requests:
-            storage: 1Gi
 ---
 apiVersion: apps/v1
 kind: StatefulSet
@@ -405,3 +409,140 @@ spec:
         resources:
           requests:
             storage: 1Gi
+---
+apiVersion: apps/v1
+kind: StatefulSet
+metadata:
+  labels:
+    app: vanus-etcd
+  name: vanus-etcd
+  namespace: vanus
+spec:
+  podManagementPolicy: Parallel
+  replicas: 3
+  revisionHistoryLimit: 10
+  selector:
+    matchLabels:
+      app: vanus-etcd
+  serviceName: vanus-etcd
+  template:
+    metadata:
+      labels:
+        app: vanus-etcd
+    spec:
+      containers:
+      - env:
+        - name: BITNAMI_DEBUG
+          value: "false"
+        - name: MY_POD_IP
+          valueFrom:
+            fieldRef:
+              apiVersion: v1
+              fieldPath: status.podIP
+        - name: MY_POD_NAME
+          valueFrom:
+            fieldRef:
+              apiVersion: v1
+              fieldPath: metadata.name
+        - name: MY_STS_NAME
+          value: vanus-etcd
+        - name: ETCDCTL_API
+          value: "3"
+        - name: ETCD_ON_K8S
+          value: "yes"
+        - name: ETCD_START_FROM_SNAPSHOT
+          value: "no"
+        - name: ETCD_INIT_SNAPSHOT_FILENAME
+          value: "snapshotdb"
+        - name: ETCD_DISASTER_RECOVERY
+          value: "no"
+        - name: ETCD_NAME
+          value: $(MY_POD_NAME)
+        - name: ETCD_DATA_DIR
+          value: /bitnami/etcd/data
+        - name: ETCD_LOG_LEVEL
+          value: info
+        - name: ALLOW_NONE_AUTHENTICATION
+          value: "yes"
+        - name: ETCD_ADVERTISE_CLIENT_URLS
+          value: http://$(MY_POD_NAME).vanus-etcd.vanus.svc.cluster.local:2379,http://vanus-etcd.vanus.svc.cluster.local:2379
+        - name: ETCD_LISTEN_CLIENT_URLS
+          value: http://0.0.0.0:2379
+        - name: ETCD_INITIAL_ADVERTISE_PEER_URLS
+          value: http://$(MY_POD_NAME).vanus-etcd.vanus.svc.cluster.local:2380
+        - name: ETCD_LISTEN_PEER_URLS
+          value: http://0.0.0.0:2380
+        - name: ETCD_INITIAL_CLUSTER_STATE
+          value: new
+        - name: ETCD_INITIAL_CLUSTER
+          value: vanus-etcd-0=http://vanus-etcd-0.vanus-etcd.vanus.svc.cluster.local:2380,vanus-etcd-1=http://vanus-etcd-1.vanus-etcd.vanus.svc.cluster.local:2380,vanus-etcd-2=http://vanus-etcd-2.vanus-etcd.vanus.svc.cluster.local:2380
+        - name: ETCD_CLUSTER_DOMAIN
+          value: vanus-etcd.vanus.svc.cluster.local
+        image: docker.io/bitnami/etcd:3.5.7-debian-11-r9
+        imagePullPolicy: IfNotPresent
+        lifecycle:
+          preStop:
+            exec:
+              command:
+              - /opt/bitnami/scripts/etcd/prestop.sh
+        livenessProbe:
+          exec:
+            command:
+            - /opt/bitnami/scripts/etcd/healthcheck.sh
+          failureThreshold: 5
+          initialDelaySeconds: 60
+          periodSeconds: 30
+          successThreshold: 1
+          timeoutSeconds: 5
+        name: etcd
+        ports:
+        - containerPort: 2379
+          name: client
+          protocol: TCP
+        - containerPort: 2380
+          name: peer
+          protocol: TCP
+        readinessProbe:
+          exec:
+            command:
+            - /opt/bitnami/scripts/etcd/healthcheck.sh
+          failureThreshold: 5
+          initialDelaySeconds: 60
+          periodSeconds: 10
+          successThreshold: 1
+          timeoutSeconds: 5
+        resources: {}
+        securityContext:
+          allowPrivilegeEscalation: false
+          runAsNonRoot: true
+          runAsUser: 1001
+        terminationMessagePath: /dev/termination-log
+        terminationMessagePolicy: File
+        volumeMounts:
+        - name: data
+          mountPath: /bitnami/etcd
+      dnsPolicy: ClusterFirst
+      restartPolicy: Always
+      schedulerName: default-scheduler
+      securityContext:
+        fsGroup: 1001
+      serviceAccount: default
+      serviceAccountName: default
+      terminationGracePeriodSeconds: 30
+  updateStrategy:
+    type: RollingUpdate
+  volumeClaimTemplates:
+  - apiVersion: v1
+    kind: PersistentVolumeClaim
+    metadata:
+      creationTimestamp: null
+      labels:
+        app: vanus-etcd
+      name: data
+    spec:
+      accessModes:
+      - ReadWriteOnce
+      resources:
+        requests:
+          storage: 10Gi
+      volumeMode: Filesystem
diff --git a/deploy/vanus.yaml b/deploy/vanus.yaml
index c62d749..3bbe415 100644
--- a/deploy/vanus.yaml
+++ b/deploy/vanus.yaml
@@ -11,28 +11,19 @@ data:
     ip: ${POD_IP}
     port: 2048
     etcd:
-      - vanus-controller-0.vanus-controller:2379
-      - vanus-controller-1.vanus-controller:2379
-      - vanus-controller-2.vanus-controller:2379
+      - vanus-etcd-0.vanus-etcd:2379
+      - vanus-etcd-1.vanus-etcd:2379
+      - vanus-etcd-2.vanus-etcd:2379
     data_dir: /data
     replicas: 3
     metadata:
       key_prefix: /vanus
+    leader_election:
+      lease_duration: 15
     topology:
       vanus-controller-0: vanus-controller-0.vanus-controller.vanus.svc:2048
       vanus-controller-1: vanus-controller-1.vanus-controller.vanus.svc:2048
       vanus-controller-2: vanus-controller-2.vanus-controller.vanus.svc:2048
-    embed_etcd:
-      # relative path to ${data_dir} above
-      data_dir: etcd/data
-      listen_client_addr: 0.0.0.0:2379
-      listen_peer_addr: 0.0.0.0:2380
-      advertise_client_addr: ${POD_NAME}.vanus-controller:2379
-      advertise_peer_addr: ${POD_NAME}.vanus-controller:2380
-      clusters:
-        - vanus-controller-0=http://vanus-controller-0.vanus-controller:2380
-        - vanus-controller-1=http://vanus-controller-1.vanus-controller:2380
-        - vanus-controller-2=http://vanus-controller-2.vanus-controller:2380
 kind: ConfigMap
 metadata:
   name: config-controller
@@ -88,12 +79,12 @@ data:
     name: "timer"
     ip: ${POD_IP}
     etcd:
-      - vanus-controller-0.vanus-controller:2379
-      - vanus-controller-1.vanus-controller:2379
-      - vanus-controller-2.vanus-controller:2379
+      - vanus-etcd-0.vanus-etcd:2379
+      - vanus-etcd-1.vanus-etcd:2379
+      - vanus-etcd-2.vanus-etcd:2379
     metadata:
       key_prefix: "/vanus"
-    leaderelection:
+    leader_election:
       lease_duration: 15
     timingwheel:
       tick: 1
@@ -137,6 +128,36 @@ spec:
 ---
 apiVersion: v1
 kind: Service
+metadata:
+  labels:
+    app: vanus-etcd
+  name: vanus-etcd
+  namespace: vanus
+spec:
+  clusterIP: None
+  clusterIPs:
+  - None
+  internalTrafficPolicy: Cluster
+  ipFamilies:
+  - IPv4
+  ipFamilyPolicy: SingleStack
+  ports:
+  - name: client
+    port: 2379
+    protocol: TCP
+    targetPort: client
+  - name: peer
+    port: 2380
+    protocol: TCP
+    targetPort: peer
+  publishNotReadyAddresses: true
+  selector:
+    app: vanus-etcd
+  sessionAffinity: None
+  type: ClusterIP
+---
+apiVersion: v1
+kind: Service
 metadata:
   name: vanus-gateway
   namespace: vanus
@@ -320,32 +341,15 @@ spec:
         ports:
         - containerPort: 2048
           name: grpc
-        - containerPort: 2379
-          name: etcd-client
-        - containerPort: 2380
-          name: etcd-peer
         - containerPort: 2112
           name: metrics
         volumeMounts:
         - mountPath: /vanus/config
           name: config-controller
-        - mountPath: /data
-          name: data
       volumes:
       - configMap:
           name: config-controller
         name: config-controller
-  volumeClaimTemplates:
-  - metadata:
-      labels:
-        app: vanus-controller
-      name: data
-    spec:
-      accessModes:
-      - ReadWriteOnce
-      resources:
-        requests:
-          storage: 1Gi
 ---
 apiVersion: apps/v1
 kind: StatefulSet
@@ -405,3 +409,140 @@ spec:
       resources:
         requests:
           storage: 1Gi
+---
+apiVersion: apps/v1
+kind: StatefulSet
+metadata:
+  labels:
+    app: vanus-etcd
+  name: vanus-etcd
+  namespace: vanus
+spec:
+  podManagementPolicy: Parallel
+  replicas: 3
+  revisionHistoryLimit: 10
+  selector:
+    matchLabels:
+      app: vanus-etcd
+  serviceName: vanus-etcd
+  template:
+    metadata:
+      labels:
+        app: vanus-etcd
+    spec:
+      containers:
+      - env:
+        - name: BITNAMI_DEBUG
+          value: "false"
+        - name: MY_POD_IP
+          valueFrom:
+            fieldRef:
+              apiVersion: v1
+              fieldPath: status.podIP
+        - name: MY_POD_NAME
+          valueFrom:
+            fieldRef:
+              apiVersion: v1
+              fieldPath: metadata.name
+        - name: MY_STS_NAME
+          value: vanus-etcd
+        - name: ETCDCTL_API
+          value: "3"
+        - name: ETCD_ON_K8S
+          value: "yes"
+        - name: ETCD_START_FROM_SNAPSHOT
+          value: "no"
+        - name: ETCD_INIT_SNAPSHOT_FILENAME
+          value: "snapshotdb"
+        - name: ETCD_DISASTER_RECOVERY
+          value: "no"
+        - name: ETCD_NAME
+          value: $(MY_POD_NAME)
+        - name: ETCD_DATA_DIR
+          value: /bitnami/etcd/data
+        - name: ETCD_LOG_LEVEL
+          value: info
+        - name: ALLOW_NONE_AUTHENTICATION
+          value: "yes"
+        - name: ETCD_ADVERTISE_CLIENT_URLS
+          value: http://$(MY_POD_NAME).vanus-etcd.vanus.svc.cluster.local:2379,http://vanus-etcd.vanus.svc.cluster.local:2379
+        - name: ETCD_LISTEN_CLIENT_URLS
+          value: http://0.0.0.0:2379
+        - name: ETCD_INITIAL_ADVERTISE_PEER_URLS
+          value: http://$(MY_POD_NAME).vanus-etcd.vanus.svc.cluster.local:2380
+        - name: ETCD_LISTEN_PEER_URLS
+          value: http://0.0.0.0:2380
+        - name: ETCD_INITIAL_CLUSTER_STATE
+          value: new
+        - name: ETCD_INITIAL_CLUSTER
+          value: vanus-etcd-0=http://vanus-etcd-0.vanus-etcd.vanus.svc.cluster.local:2380,vanus-etcd-1=http://vanus-etcd-1.vanus-etcd.vanus.svc.cluster.local:2380,vanus-etcd-2=http://vanus-etcd-2.vanus-etcd.vanus.svc.cluster.local:2380
+        - name: ETCD_CLUSTER_DOMAIN
+          value: vanus-etcd.vanus.svc.cluster.local
+        image: docker.io/bitnami/etcd:3.5.7-debian-11-r9
+        imagePullPolicy: IfNotPresent
+        lifecycle:
+          preStop:
+            exec:
+              command:
+              - /opt/bitnami/scripts/etcd/prestop.sh
+        livenessProbe:
+          exec:
+            command:
+            - /opt/bitnami/scripts/etcd/healthcheck.sh
+          failureThreshold: 5
+          initialDelaySeconds: 60
+          periodSeconds: 30
+          successThreshold: 1
+          timeoutSeconds: 5
+        name: etcd
+        ports:
+        - containerPort: 2379
+          name: client
+          protocol: TCP
+        - containerPort: 2380
+          name: peer
+          protocol: TCP
+        readinessProbe:
+          exec:
+            command:
+            - /opt/bitnami/scripts/etcd/healthcheck.sh
+          failureThreshold: 5
+          initialDelaySeconds: 60
+          periodSeconds: 10
+          successThreshold: 1
+          timeoutSeconds: 5
+        resources: {}
+        securityContext:
+          allowPrivilegeEscalation: false
+          runAsNonRoot: true
+          runAsUser: 1001
+        terminationMessagePath: /dev/termination-log
+        terminationMessagePolicy: File
+        volumeMounts:
+        - name: data
+          mountPath: /bitnami/etcd
+      dnsPolicy: ClusterFirst
+      restartPolicy: Always
+      schedulerName: default-scheduler
+      securityContext:
+        fsGroup: 1001
+      serviceAccount: default
+      serviceAccountName: default
+      terminationGracePeriodSeconds: 30
+  updateStrategy:
+    type: RollingUpdate
+  volumeClaimTemplates:
+  - apiVersion: v1
+    kind: PersistentVolumeClaim
+    metadata:
+      creationTimestamp: null
+      labels:
+        app: vanus-etcd
+      name: data
+    spec:
+      accessModes:
+      - ReadWriteOnce
+      resources:
+        requests:
+          storage: 10Gi
+      volumeMode: Filesystem
\ No newline at end of file
diff --git a/go.mod b/go.mod
index 2d62ca2..f6cf632 100644
--- a/go.mod
+++ b/go.mod
@@ -22,7 +22,6 @@ require (
 	github.com/iceber/iouring-go v0.0.0-20220609112130-b1dc8dd9fbfd
 	github.com/jedib0t/go-pretty/v6 v6.3.1
 	github.com/json-iterator/go v1.1.12
-	github.com/linkall-labs/embed-etcd v0.1.2
 	github.com/linkall-labs/sdk/golang v0.2.0
 	github.com/linkall-labs/vanus/client v0.5.7
 	github.com/linkall-labs/vanus/observability v0.5.7
@@ -39,7 +38,7 @@ require (
 	github.com/sony/sonyflake v1.1.0
 	github.com/spf13/cobra v1.4.0
 	github.com/tidwall/gjson v1.14.1
-	go.etcd.io/etcd/client/v3 v3.6.0-alpha.0
+	go.etcd.io/etcd/client/v3 v3.5.7
 	go.mongodb.org/mongo-driver v1.11.0
 	go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.36.4
 	go.opentelemetry.io/otel v1.11.2
@@ -62,7 +61,6 @@ require (
 require (
 	cloud.google.com/go/compute v1.12.1 // indirect
 	cloud.google.com/go/compute/metadata v0.2.1 // indirect
-	github.com/BurntSushi/toml v1.2.1 // indirect
 	github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 // indirect
 	github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20220209173558-ad29539cd2e9 // indirect
 	github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.18 // indirect
@@ -75,23 +73,16 @@ require (
 	github.com/coreos/go-systemd/v22 v22.3.2 // indirect
 	github.com/davecgh/go-spew v1.1.1 // indirect
 	github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
-	github.com/dustin/go-humanize v1.0.0 // indirect
 	github.com/go-logr/logr v1.2.3 // indirect
 	github.com/go-logr/stdr v1.2.2 // indirect
-	github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
 	github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
 	github.com/golang/snappy v0.0.3 // indirect
-	github.com/google/btree v1.0.1 // indirect
 	github.com/google/go-cmp v0.5.9 // indirect
 	github.com/googleapis/enterprise-certificate-proxy v0.2.0 // indirect
 	github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 // indirect
-	github.com/gorilla/websocket v1.4.2 // indirect
-	github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect
-	github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect
 	github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 // indirect
 	github.com/inconshreveable/mousetrap v1.0.0 // indirect
 	github.com/jmespath/go-jmespath v0.4.0 // indirect
-	github.com/jonboulle/clockwork v0.2.2 // indirect
 	github.com/jtolds/gls v4.20.0+incompatible // indirect
 	github.com/klauspost/compress v1.13.6 // indirect
 	github.com/mattn/go-colorable v0.1.11 // indirect
@@ -109,25 +100,17 @@ require (
 	github.com/scylladb/go-set v1.0.2 // indirect
 	github.com/sirupsen/logrus v1.9.0 // indirect
 	github.com/smartystreets/assertions v1.2.0 // indirect
-	github.com/soheilhy/cmux v0.1.5 // indirect
 	github.com/spf13/pflag v1.0.5 // indirect
 	github.com/stoewer/go-strcase v1.2.0 // indirect
 	github.com/stretchr/testify v1.8.1 // indirect
 	github.com/tidwall/match v1.1.1 // indirect
 	github.com/tidwall/pretty v1.2.0 // indirect
-	github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802 // indirect
 	github.com/xdg-go/pbkdf2 v1.0.0 // indirect
 	github.com/xdg-go/scram v1.1.1 // indirect
 	github.com/xdg-go/stringprep v1.0.3 // indirect
-	github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect
 	github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
-	go.etcd.io/bbolt v1.3.6 // indirect
-	go.etcd.io/etcd/api/v3 v3.6.0-alpha.0 // indirect
-	go.etcd.io/etcd/client/pkg/v3 v3.6.0-alpha.0 // indirect
-	go.etcd.io/etcd/client/v2 v2.306.0-alpha.0 // indirect
-	go.etcd.io/etcd/pkg/v3 v3.6.0-alpha.0 // indirect
-	go.etcd.io/etcd/raft/v3 v3.6.0-alpha.0 // indirect
-	go.etcd.io/etcd/server/v3 v3.6.0-alpha.0 // indirect
+	go.etcd.io/etcd/api/v3 v3.5.7 // indirect
+	go.etcd.io/etcd/client/pkg/v3 v3.5.7 // indirect
 	go.opencensus.io v0.24.0 // indirect
 	go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.11.2 // indirect
 	go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.11.2 // indirect
diff --git a/go.sum b/go.sum
index 5a12a0f..6200935 100644
--- a/go.sum
+++ b/go.sum
@@ -36,8 +36,6 @@ cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RX
 cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
-github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak=
-github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
 github.com/HdrHistogram/hdrhistogram-go v1.1.2 h1:5IcZpTvzydCQeHzK4Ef/D5rrSqwxob0t8PQPMybUNFM=
 github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo=
@@ -110,8 +108,6 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
 github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
-github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
-github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
 github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
 github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
 github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
@@ -154,8 +150,6 @@ github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5x
 github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
 github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
 github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
-github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
-github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
 github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
 github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ=
@@ -196,8 +190,6 @@ github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA=
 github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
 github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
-github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
-github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
 github.com/google/cel-go v0.11.2 h1:o16cOggWWtH1a3ZHQ8uWqt8nd255vDrEK1mDE1cFRSQ=
 github.com/google/cel-go v0.11.2/go.mod h1:drz+knCRsctDZ180KZHwIEEUb9IdK/nxPoyhxi+O1K0=
 github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
@@ -235,13 +227,8 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+
 github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
-github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
-github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
 github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw=
 github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y=
-github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho=
-github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
-github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo=
 github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
 github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 h1:BZHcxBETFHIdVyhyEfOvn/RdU/QGdLI4y34qQGjGWO0=
 github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks=
@@ -262,8 +249,6 @@ github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9Y
 github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
 github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
 github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
-github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ=
-github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
 github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
 github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
 github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
@@ -290,8 +275,6 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
-github.com/linkall-labs/embed-etcd v0.1.2 h1:1mTdXLwVvn9gi3XWh/PGhaEAfG8Zmxvjqwnfontb+fA=
-github.com/linkall-labs/embed-etcd v0.1.2/go.mod h1:QnecHaKt3WQBO9YGBckCDUTBd44VBR2VO8220BtWZ5U=
 github.com/linkall-labs/sdk/golang v0.2.0 h1:VZ4n1mvwwnxCmVopktgBYuwp/FjtusxeR+f7Snfgi4k=
 github.com/linkall-labs/sdk/golang v0.2.0/go.mod h1:ytY4b1U5jdXlJjbSN2BGqqMZ0oUNcI0jaml1Wu2H4N0=
 github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
@@ -379,8 +362,6 @@ github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N
 github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
 github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg=
 github.com/smartystreets/goconvey v1.7.2/go.mod h1:Vw0tHAZW6lzCRk3xgdin6fKYcG+G3Pg9vgXWeJpQFMM=
-github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js=
-github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0=
 github.com/sony/sonyflake v1.1.0 h1:wnrEcL3aOkWmPlhScLEGAXKkLAIslnBteNUq4Bw6MM4=
 github.com/sony/sonyflake v1.1.0/go.mod h1:LORtCywH/cq10ZbyfhKrHYgAUGH7mOBa76enV9txy/Y=
 github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
@@ -412,8 +393,6 @@ github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JT
 github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
 github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
 github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
-github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802 h1:uruHq4dN7GR16kFc5fp3d1RIYzJW5onx8Ybykw2YQFA=
-github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
 github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
 github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
 github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
@@ -421,8 +400,6 @@ github.com/xdg-go/scram v1.1.1 h1:VOMT+81stJgXW3CpHyqHN3AXDYIMsx56mEFrB37Mb/E=
 github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g=
 github.com/xdg-go/stringprep v1.0.3 h1:kdwGpVNwPFtjs98xCGkHjQtGKh86rDcRZN17QEMCOIs=
 github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8=
-github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8=
-github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
 github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA=
 github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
 github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@@ -430,22 +407,12 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
 github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
-go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU=
-go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
-go.etcd.io/etcd/api/v3 v3.6.0-alpha.0 h1:se+XckWlVTTfwjZSsAZJ2zGPzmIMq3j7fKBCmHoB9UA=
-go.etcd.io/etcd/api/v3 v3.6.0-alpha.0/go.mod h1:z13pg39zewDLZeXIKeM0xELOeFKcqjLocfwl5M820+w=
-go.etcd.io/etcd/client/pkg/v3 v3.6.0-alpha.0 h1:2UyRzFWbZZzgu/xzxoRukgixvafiJtGyxO+3IKUyJ6c=
-go.etcd.io/etcd/client/pkg/v3 v3.6.0-alpha.0/go.mod h1:Vl/FkH40bHqmBFwhr8WVKtV47neyts36zl1voccRq8s=
-go.etcd.io/etcd/client/v2 v2.306.0-alpha.0 h1:9VRJ698EFIMfjOQtcjKMM7CWXOIxp9R4I8JA1mk+WT4=
-go.etcd.io/etcd/client/v2 v2.306.0-alpha.0/go.mod h1:eW78BCfOzS1HJgTNzDrb2E6xV1p6kqlpLpKkz7ErzCs=
-go.etcd.io/etcd/client/v3 v3.6.0-alpha.0 h1:hHaJ8CvTPJ9iv7xPz3G0gxt3csEqJW8evgty/kYICwo=
-go.etcd.io/etcd/client/v3 v3.6.0-alpha.0/go.mod h1:a9JuChoQBDnw7WclHYBYCtTOIC12Wwj+Fw0LX4TI/Gs=
-go.etcd.io/etcd/pkg/v3 v3.6.0-alpha.0 h1:cV/VsaYde/tcc2G9aHN5DQwx6CtUsWSEW4UqYzXuyyk=
-go.etcd.io/etcd/pkg/v3 v3.6.0-alpha.0/go.mod h1:tXqWms0MpOJAS6L0B9nhFqZr0C/WEYzj/OtN90G8xzo=
-go.etcd.io/etcd/raft/v3 v3.6.0-alpha.0 h1:BQ6CnNP4pIpy5rusFlTBxAacDgPXhuiHFwoTsBNsVpI=
-go.etcd.io/etcd/raft/v3 v3.6.0-alpha.0/go.mod h1:/kZdrBXlc5fUgYXfIEQ0B5sb7ejXPKbtF4jWzF1exiQ=
-go.etcd.io/etcd/server/v3 v3.6.0-alpha.0 h1:BQUVqBqNFZZyrRbfydrRLzq9hYvCcRj97SsX1YwD7CA=
-go.etcd.io/etcd/server/v3 v3.6.0-alpha.0/go.mod h1:3QM2rLq3B3hSXmVEvgVt3vEEbG/AumSs0Is7EgrlKzU=
+go.etcd.io/etcd/api/v3 v3.5.7 h1:sbcmosSVesNrWOJ58ZQFitHMdncusIifYcrBfwrlJSY=
+go.etcd.io/etcd/api/v3 v3.5.7/go.mod h1:9qew1gCdDDLu+VwmeG+iFpL+QlpHTo7iubavdVDgCAA=
+go.etcd.io/etcd/client/pkg/v3 v3.5.7 h1:y3kf5Gbp4e4q7egZdn5T7W9TSHUvkClN6u+Rq9mEOmg=
+go.etcd.io/etcd/client/pkg/v3 v3.5.7/go.mod h1:o0Abi1MK86iad3YrWhgUsbGx1pmTS+hrORWc2CamuhY=
+go.etcd.io/etcd/client/v3 v3.5.7 h1:u/OhpiuCgYY8awOHlhIhmGIGpxfBU/GZBUP3m/3/Iz4=
+go.etcd.io/etcd/client/v3 v3.5.7/go.mod h1:sOWmj9DZUMyAngS7QQwCyAXXAL6WhgTOPLNS/NabQgw=
 go.mongodb.org/mongo-driver v1.11.0 h1:FZKhBSTydeuffHj9CBjXlR8vQLee1cQyTWYPA6/tqiE=
 go.mongodb.org/mongo-driver v1.11.0/go.mod h1:s7p5vEtfbeR1gYi6pnj3c3/urpbLv2T5Sfd6Rp2HBB8=
 go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
@@ -560,7 +527,6 @@ golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81R
 golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
 golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
-golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
 golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
@@ -815,8 +781,6 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8
 gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
 gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
-gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
-gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
 gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
@@ -851,7 +815,3 @@ rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8
 rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
 rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
 rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
-sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 h1:iXTIw73aPyC+oRdyqqvVJuloN1p0AC/kzH07hu3NE+k=
-sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
-sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
-sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
diff --git a/internal/controller/config.go b/internal/controller/config.go
index 6dc759b..c2996e6 100644
--- a/internal/controller/config.go
+++ b/internal/controller/config.go
@@ -15,10 +15,8 @@
 package controller
 
 import (
-	"path/filepath"
-
-	embedetcd "github.com/linkall-labs/embed-etcd"
 	"github.com/linkall-labs/vanus/internal/controller/eventbus"
+	"github.com/linkall-labs/vanus/internal/controller/member"
 	"github.com/linkall-labs/vanus/internal/controller/snowflake"
 	"github.com/linkall-labs/vanus/internal/controller/trigger"
 	"github.com/linkall-labs/vanus/internal/primitive"
@@ -34,7 +32,7 @@ type Config struct {
 	EtcdEndpoints        []string             `yaml:"etcd"`
 	DataDir              string               `yaml:"data_dir"`
 	MetadataConfig       MetadataConfig       `yaml:"metadata"`
-	EtcdConfig           embedetcd.Config     `yaml:"embed_etcd"`
+	LeaderElectionConfig LeaderElectionConfig `yaml:"leader_election"`
 	Topology             map[string]string    `yaml:"topology"`
 	Replicas             uint                 `yaml:"replicas"`
 	SecretEncryptionSalt string               `yaml:"secret_encryption_salt"`
@@ -42,10 +40,20 @@ type Config struct {
 	Observability        observability.Config `yaml:"observability"`
 }
 
-func (c *Config) GetEtcdConfig() embedetcd.Config {
-	c.EtcdConfig.DataDir = filepath.Join(c.DataDir, c.EtcdConfig.DataDir)
-	c.EtcdConfig.Name = c.Name
-	return c.EtcdConfig
+type LeaderElectionConfig struct {
+	LeaseDuration int64 `yaml:"lease_duration"`
+}
+
+const (
+	resourceLockName = "controller"
+)
+
+func (c *Config) GetMemberConfig() member.Config {
+	return member.Config{
+		LeaseDuration: c.LeaderElectionConfig.LeaseDuration,
+		Name:          resourceLockName,
+		EtcdEndpoints: c.EtcdEndpoints,
+	}
 }
 
 func (c *Config) GetEventbusCtrlConfig() eventbus.Config {
diff --git a/internal/controller/eventbus/config.go b/internal/controller/eventbus/config.go
index f672fe1..b3906fb 100644
--- a/internal/controller/eventbus/config.go
+++ b/internal/controller/eventbus/config.go
@@ -14,14 +14,11 @@
 
 package eventbus
 
-import embedetcd "github.com/linkall-labs/embed-etcd"
-
 type Config struct {
 	IP               string            `yaml:"ip"`
 	Port             int               `yaml:"port"`
 	KVStoreEndpoints []string          `yaml:"kv_store_endpoints"`
 	KVKeyPrefix      string            `yaml:"kv_key_prefix"`
-	EtcdConfig       embedetcd.Config  `yaml:"etcd"`
 	Replicas         uint              `yaml:"replicas"`
 	Topology         map[string]string `yaml:"topology"`
 	SegmentCapacity  int64             `yaml:"segment_capacity"`
diff --git a/internal/controller/eventbus/controller.go b/internal/controller/eventbus/controller.go
index 1fc5f0f..e9d6cb2 100644
--- a/internal/controller/eventbus/controller.go
+++ b/internal/controller/eventbus/controller.go
@@ -27,11 +27,11 @@ import (
 	"sync/atomic"
 	"time"
 
-	embedetcd "github.com/linkall-labs/embed-etcd"
 	"github.com/linkall-labs/vanus/internal/controller/eventbus/eventlog"
 	"github.com/linkall-labs/vanus/internal/controller/eventbus/metadata"
 	"github.com/linkall-labs/vanus/internal/controller/eventbus/server"
 	"github.com/linkall-labs/vanus/internal/controller/eventbus/volume"
+	"github.com/linkall-labs/vanus/internal/controller/member"
 	"github.com/linkall-labs/vanus/internal/kv"
 	"github.com/linkall-labs/vanus/internal/kv/etcd"
 	"github.com/linkall-labs/vanus/internal/primitive"
@@ -58,12 +58,12 @@ const (
 	maximumEventlogNum = 64
 )
 
-func NewController(cfg Config, member embedetcd.Member) *controller {
+func NewController(cfg Config, mem member.Member) *controller {
 	c := &controller{
 		cfg:         &cfg,
 		ssMgr:       server.NewServerManager(),
 		eventBusMap: map[string]*metadata.Eventbus{},
-		member:      member,
+		member:      mem,
 		isLeader:    false,
 		readyNotify: make(chan error, 1),
 		stopNotify:  make(chan error, 1),
@@ -80,7 +80,7 @@ type controller struct {
 	eventLogMgr          eventlog.Manager
 	ssMgr                server.Manager
 	eventBusMap          map[string]*metadata.Eventbus
-	member               embedetcd.Member
+	member               member.Member
 	cancelCtx            context.Context
 	cancelFunc           context.CancelFunc
 	membershipMutex      sync.Mutex
@@ -589,12 +589,12 @@ func (ctrl *controller) recordMetrics() {
 	}
 }
 
-func (ctrl *controller) membershipChangedProcessor(ctx context.Context, event embedetcd.MembershipChangedEvent) error {
+func (ctrl *controller) membershipChangedProcessor(ctx context.Context, event member.MembershipChangedEvent) error {
 	ctrl.membershipMutex.Lock()
 	defer ctrl.membershipMutex.Unlock()
 
 	switch event.Type {
-	case embedetcd.EventBecomeLeader:
+	case member.EventBecomeLeader:
 		if ctrl.isLeader {
 			return nil
 		}
@@ -618,7 +618,7 @@ func (ctrl *controller) membershipChangedProcessor(ctx context.Context, event em
 			ctrl.stop(ctx, err)
 			return err
 		}
-	case embedetcd.EventBecomeFollower:
+	case member.EventBecomeFollower:
 		if !ctrl.isLeader {
 			return nil
 		}
diff --git a/internal/controller/member/config.go b/internal/controller/member/config.go
new file mode 100644
index 0000000..0611f3b
--- /dev/null
+++ b/internal/controller/member/config.go
@@ -0,0 +1,26 @@
+// Copyright 2023 Linkall Inc.
+//
+// Licensed 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 member
+
+type Config struct {
+	LeaseDuration int64    `yaml:"lease_duration"`
+	Name          string   `yaml:"name"`
+	EtcdEndpoints []string `yaml:"etcd"`
+}
+
+const (
+	ResourceLockKeyPrefixInKVStore = "/vanus/internal/resource/resource_lock"
+	LeaderInfoKeyPrefixInKVStore   = "/vanus/internal/resource/leader_info"
+)
diff --git a/internal/controller/member/member.go b/internal/controller/member/member.go
new file mode 100644
index 0000000..f28bb24
--- /dev/null
+++ b/internal/controller/member/member.go
@@ -0,0 +1,370 @@
+// Copyright 2023 Linkall Inc.
+//
+// Licensed 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 member
+
+import (
+	"context"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"os"
+	"sync"
+	"time"
+
+	"github.com/linkall-labs/vanus/observability/log"
+	clientv3 "go.etcd.io/etcd/client/v3"
+	"go.etcd.io/etcd/client/v3/concurrency"
+	"go.uber.org/atomic"
+)
+
+var (
+	ErrStartEtcd            = errors.New("start etcd failed")
+	ErrStartEtcdCanceled    = errors.New("etcd start canceled")
+	defaultEtcdStartTimeout = 2 * time.Minute
+)
+
+type Member interface {
+	Init(context.Context, Config) error
+	Start(context.Context) (<-chan struct{}, error)
+	Stop(context.Context)
+	RegisterMembershipChangedProcessor(MembershipEventProcessor)
+	ResignIfLeader()
+	IsLeader() bool
+	GetLeaderID() string
+	GetLeaderAddr() string
+	IsReady() bool
+}
+
+func New(topology map[string]string) *member { //nolint:revive // it's ok
+	return &member{
+		topology: topology,
+	}
+}
+
+type LeaderInfo struct {
+	LeaderID   string
+	LeaderAddr string
+}
+
+type EventType string
+
+const (
+	EventBecomeLeader   EventType = "leader"
+	EventBecomeFollower EventType = "follower"
+)
+
+type MembershipChangedEvent struct {
+	Type EventType
+}
+
+type MembershipEventProcessor func(ctx context.Context, event MembershipChangedEvent) error
+
+type member struct {
+	cfg           *Config
+	client        *clientv3.Client
+	resourcelock  string
+	leaseDuration int64
+	session       *concurrency.Session
+	mutex         *concurrency.Mutex
+	isLeader      atomic.Bool
+	handlers      []MembershipEventProcessor
+	topology      map[string]string
+	wg            sync.WaitGroup
+	handlerMu     sync.RWMutex
+	sessionMu     sync.RWMutex
+	exit          chan struct{}
+	isReady       atomic.Bool
+}
+
+const (
+	dialTimeout          = 5
+	dialKeepAliveTime    = 1
+	dialKeepAliveTimeout = 3
+	acquireLockDuration  = 5
+)
+
+func (m *member) Init(ctx context.Context, cfg Config) error {
+	m.cfg = &cfg
+	m.resourcelock = fmt.Sprintf("%s/%s", ResourceLockKeyPrefixInKVStore, cfg.Name)
+	m.leaseDuration = cfg.LeaseDuration
+	m.exit = make(chan struct{})
+
+	err := m.waitForEtcdReady(ctx, cfg.EtcdEndpoints)
+	if err != nil {
+		log.Error(context.Background(), "etcd is not ready", nil)
+		return err
+	}
+
+	m.session, err = concurrency.NewSession(m.client, concurrency.WithTTL(int(m.leaseDuration)))
+	if err != nil {
+		log.Error(context.Background(), "new session failed", map[string]interface{}{
+			log.KeyError: err,
+		})
+		panic("new session failed")
+	}
+	m.mutex = concurrency.NewMutex(m.session, m.resourcelock)
+	log.Info(context.Background(), "new leaderelection manager", map[string]interface{}{
+		"name":           m.cfg.Name,
+		"key":            m.resourcelock,
+		"lease_duration": m.leaseDuration,
+	})
+	return nil
+}
+
+func (m *member) waitForEtcdReady(ctx context.Context, endpoints []string) error {
+	start := time.Now()
+	log.Info(context.Background(), "wait for etcd is ready", nil)
+	t := time.NewTicker(defaultEtcdStartTimeout)
+	defer t.Stop()
+	for !m.ready(ctx, endpoints) {
+		select {
+		case <-t.C:
+			return errors.New("etcd isn't ready")
+		default:
+			time.Sleep(time.Second)
+		}
+	}
+
+	log.Info(context.Background(), "etcd is ready", map[string]interface{}{
+		"waiting_time": time.Since(start),
+	})
+	return nil
+}
+
+func (m *member) ready(ctx context.Context, endpoints []string) bool {
+	client, err := clientv3.New(clientv3.Config{
+		Endpoints:            endpoints,
+		DialTimeout:          dialTimeout * time.Second,
+		DialKeepAliveTime:    dialKeepAliveTime * time.Second,
+		DialKeepAliveTimeout: dialKeepAliveTimeout * time.Second,
+	})
+	if err != nil {
+		log.Warning(ctx, "new etcd v3client failed", map[string]interface{}{
+			log.KeyError: err,
+		})
+		return false
+	}
+	m.client = client
+	return true
+}
+
+func (m *member) Start(ctx context.Context) (<-chan struct{}, error) {
+	log.Info(ctx, "start leaderelection", nil)
+	return m.tryAcquireLockLoop(ctx)
+}
+
+func (m *member) tryAcquireLockLoop(ctx context.Context) (<-chan struct{}, error) {
+	m.wg.Add(1)
+	go func() {
+		defer m.wg.Done()
+		ticker := time.NewTicker(acquireLockDuration)
+		defer ticker.Stop()
+		for {
+			select {
+			case <-ctx.Done():
+				log.Warning(ctx, "context canceled at try acquire lock loop", nil)
+				return
+			case <-m.session.Done():
+				log.Warning(ctx, "lose lock", nil)
+				m.isLeader.Store(false)
+				m.isReady.Store(false)
+				_ = m.execHandlers(ctx, MembershipChangedEvent{
+					Type: EventBecomeFollower,
+				})
+				// refresh session until success
+				for {
+					if m.refresh(ctx) {
+						break
+					}
+					time.Sleep(time.Second)
+				}
+			case <-ticker.C:
+				_ = m.tryLock(ctx)
+			}
+		}
+	}()
+	log.Info(ctx, "start try to acquire lock loop...", nil)
+	return m.exit, nil
+}
+
+func (m *member) tryLock(ctx context.Context) error {
+	if m.isLeader.Load() {
+		return nil
+	}
+	err := m.mutex.TryLock(ctx)
+	if err != nil {
+		if errors.Is(err, concurrency.ErrLocked) {
+			m.isReady.Store(true)
+			log.Debug(ctx, "try acquire lock, already locked in another session", map[string]interface{}{
+				"identity":      m.cfg.Name,
+				"resource_lock": m.resourcelock,
+			})
+			return err
+		}
+		log.Error(ctx, "acquire lock failed", map[string]interface{}{
+			log.KeyError: err,
+		})
+		return err
+	}
+
+	log.Info(ctx, "acquired lock", map[string]interface{}{
+		"identity":      m.cfg.Name,
+		"resource_lock": m.resourcelock,
+	})
+
+	err = m.setLeader(ctx)
+	if err != nil {
+		log.Error(ctx, "set leader info failed", map[string]interface{}{
+			"leader_id":   os.Getenv("POD_NAME"),
+			"leader_addr": m.topology[os.Getenv("POD_NAME")],
+			log.KeyError:  err,
+		})
+		_ = m.mutex.Unlock(ctx)
+		return err
+	}
+
+	m.isLeader.Store(true)
+	m.isReady.Store(true)
+	log.Info(ctx, "controller become leader", nil)
+	_ = m.execHandlers(ctx, MembershipChangedEvent{
+		Type: EventBecomeLeader,
+	})
+	return nil
+}
+
+func (m *member) setLeader(ctx context.Context) error {
+	data, _ := json.Marshal(&LeaderInfo{
+		LeaderID:   os.Getenv("POD_NAME"),
+		LeaderAddr: m.topology[os.Getenv("POD_NAME")],
+	})
+	_, err := m.client.Put(ctx, LeaderInfoKeyPrefixInKVStore, string(data))
+	return err
+}
+
+func (m *member) refresh(ctx context.Context) bool {
+	var err error
+	m.sessionMu.Lock()
+	defer m.sessionMu.Unlock()
+	m.session.Close()
+	m.session, err = concurrency.NewSession(m.client, concurrency.WithTTL(int(m.leaseDuration)))
+	if err != nil {
+		log.Error(context.Background(), "refresh session failed", map[string]interface{}{
+			log.KeyError: err,
+		})
+		return false
+	}
+	m.mutex = concurrency.NewMutex(m.session, m.resourcelock)
+	return true
+}
+
+func (m *member) Stop(ctx context.Context) {
+	log.Info(ctx, "stop leaderelection", nil)
+	err := m.release(ctx)
+	if err != nil {
+		log.Error(ctx, "release lock failed", map[string]interface{}{
+			log.KeyError: err,
+		})
+		return
+	}
+	m.wg.Wait()
+}
+
+func (m *member) release(ctx context.Context) error {
+	m.sessionMu.Lock()
+	defer m.sessionMu.Unlock()
+	err := m.mutex.Unlock(ctx)
+	if err != nil {
+		log.Error(ctx, "unlock error", map[string]interface{}{
+			log.KeyError: err,
+		})
+		return err
+	}
+	err = m.session.Close()
+	if err != nil {
+		log.Error(ctx, "session close error", map[string]interface{}{
+			log.KeyError: err,
+		})
+		return err
+	}
+	log.Info(ctx, "released lock", nil)
+	return nil
+}
+
+func (m *member) execHandlers(ctx context.Context, event MembershipChangedEvent) error {
+	m.handlerMu.RLock()
+	defer m.handlerMu.RUnlock()
+	for _, handler := range m.handlers {
+		err := handler(ctx, event)
+		if err != nil {
+			log.Error(ctx, "exec handler failed and exit", map[string]interface{}{
+				log.KeyError: err,
+			})
+			panic("exec handler failed")
+		}
+	}
+	return nil
+}
+
+func (m *member) RegisterMembershipChangedProcessor(handler MembershipEventProcessor) {
+	m.handlerMu.Lock()
+	defer m.handlerMu.Unlock()
+	m.handlers = append(m.handlers, handler)
+}
+
+func (m *member) ResignIfLeader() {
+	// TODO(jiangkai)
+}
+
+func (m *member) IsLeader() bool {
+	return m.isLeader.Load()
+}
+
+func (m *member) GetLeaderID() string {
+	// TODO(jiangkai): maybe lookup etcd per call has low performance.
+	resp, err := m.client.Get(context.Background(), LeaderInfoKeyPrefixInKVStore)
+	if err != nil {
+		log.Warning(context.Background(), "get leader info failed", map[string]interface{}{
+			log.KeyError: err,
+		})
+		return ""
+	}
+	if len(resp.Kvs) == 0 {
+		return ""
+	}
+	leader := &LeaderInfo{}
+	_ = json.Unmarshal(resp.Kvs[0].Value, leader)
+	return leader.LeaderID
+}
+
+func (m *member) GetLeaderAddr() string {
+	// TODO(jiangkai): maybe lookup etcd per call has low performance.
+	resp, err := m.client.Get(context.Background(), LeaderInfoKeyPrefixInKVStore)
+	if err != nil {
+		log.Warning(context.Background(), "get leader info failed", map[string]interface{}{
+			log.KeyError: err,
+		})
+		return ""
+	}
+	if len(resp.Kvs) == 0 {
+		return ""
+	}
+	leader := &LeaderInfo{}
+	_ = json.Unmarshal(resp.Kvs[0].Value, leader)
+	return leader.LeaderAddr
+}
+
+func (m *member) IsReady() bool {
+	return m.isReady.Load()
+}
diff --git a/internal/controller/member/mock_member.go b/internal/controller/member/mock_member.go
new file mode 100644
index 0000000..c98087f
--- /dev/null
+++ b/internal/controller/member/mock_member.go
@@ -0,0 +1,156 @@
+// Code generated by MockGen. DO NOT EDIT.
+// Source: member.go
+
+// Package member is a generated GoMock package.
+package member
+
+import (
+	context "context"
+	reflect "reflect"
+
+	gomock "github.com/golang/mock/gomock"
+)
+
+// MockMember is a mock of Member interface.
+type MockMember struct {
+	ctrl     *gomock.Controller
+	recorder *MockMemberMockRecorder
+}
+
+// MockMemberMockRecorder is the mock recorder for MockMember.
+type MockMemberMockRecorder struct {
+	mock *MockMember
+}
+
+// NewMockMember creates a new mock instance.
+func NewMockMember(ctrl *gomock.Controller) *MockMember {
+	mock := &MockMember{ctrl: ctrl}
+	mock.recorder = &MockMemberMockRecorder{mock}
+	return mock
+}
+
+// EXPECT returns an object that allows the caller to indicate expected use.
+func (m *MockMember) EXPECT() *MockMemberMockRecorder {
+	return m.recorder
+}
+
+// GetLeaderAddr mocks base method.
+func (m *MockMember) GetLeaderAddr() string {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GetLeaderAddr")
+	ret0, _ := ret[0].(string)
+	return ret0
+}
+
+// GetLeaderAddr indicates an expected call of GetLeaderAddr.
+func (mr *MockMemberMockRecorder) GetLeaderAddr() *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLeaderAddr", reflect.TypeOf((*MockMember)(nil).GetLeaderAddr))
+}
+
+// GetLeaderID mocks base method.
+func (m *MockMember) GetLeaderID() string {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GetLeaderID")
+	ret0, _ := ret[0].(string)
+	return ret0
+}
+
+// GetLeaderID indicates an expected call of GetLeaderID.
+func (mr *MockMemberMockRecorder) GetLeaderID() *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLeaderID", reflect.TypeOf((*MockMember)(nil).GetLeaderID))
+}
+
+// Init mocks base method.
+func (m *MockMember) Init(arg0 context.Context, arg1 Config) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "Init", arg0, arg1)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// Init indicates an expected call of Init.
+func (mr *MockMemberMockRecorder) Init(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Init", reflect.TypeOf((*MockMember)(nil).Init), arg0, arg1)
+}
+
+// IsLeader mocks base method.
+func (m *MockMember) IsLeader() bool {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "IsLeader")
+	ret0, _ := ret[0].(bool)
+	return ret0
+}
+
+// IsLeader indicates an expected call of IsLeader.
+func (mr *MockMemberMockRecorder) IsLeader() *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsLeader", reflect.TypeOf((*MockMember)(nil).IsLeader))
+}
+
+// IsReady mocks base method.
+func (m *MockMember) IsReady() bool {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "IsReady")
+	ret0, _ := ret[0].(bool)
+	return ret0
+}
+
+// IsReady indicates an expected call of IsReady.
+func (mr *MockMemberMockRecorder) IsReady() *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsReady", reflect.TypeOf((*MockMember)(nil).IsReady))
+}
+
+// RegisterMembershipChangedProcessor mocks base method.
+func (m *MockMember) RegisterMembershipChangedProcessor(arg0 MembershipEventProcessor) {
+	m.ctrl.T.Helper()
+	m.ctrl.Call(m, "RegisterMembershipChangedProcessor", arg0)
+}
+
+// RegisterMembershipChangedProcessor indicates an expected call of RegisterMembershipChangedProcessor.
+func (mr *MockMemberMockRecorder) RegisterMembershipChangedProcessor(arg0 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RegisterMembershipChangedProcessor", reflect.TypeOf((*MockMember)(nil).RegisterMembershipChangedProcessor), arg0)
+}
+
+// ResignIfLeader mocks base method.
+func (m *MockMember) ResignIfLeader() {
+	m.ctrl.T.Helper()
+	m.ctrl.Call(m, "ResignIfLeader")
+}
+
+// ResignIfLeader indicates an expected call of ResignIfLeader.
+func (mr *MockMemberMockRecorder) ResignIfLeader() *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ResignIfLeader", reflect.TypeOf((*MockMember)(nil).ResignIfLeader))
+}
+
+// Start mocks base method.
+func (m *MockMember) Start(arg0 context.Context) (<-chan struct{}, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "Start", arg0)
+	ret0, _ := ret[0].(<-chan struct{})
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// Start indicates an expected call of Start.
+func (mr *MockMemberMockRecorder) Start(arg0 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Start", reflect.TypeOf((*MockMember)(nil).Start), arg0)
+}
+
+// Stop mocks base method.
+func (m *MockMember) Stop(arg0 context.Context) {
+	m.ctrl.T.Helper()
+	m.ctrl.Call(m, "Stop", arg0)
+}
+
+// Stop indicates an expected call of Stop.
+func (mr *MockMemberMockRecorder) Stop(arg0 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Stop", reflect.TypeOf((*MockMember)(nil).Stop), arg0)
+}
diff --git a/internal/controller/snowflake/snowflake.go b/internal/controller/snowflake/snowflake.go
index 7bcb040..1d4871c 100644
--- a/internal/controller/snowflake/snowflake.go
+++ b/internal/controller/snowflake/snowflake.go
@@ -24,7 +24,7 @@ import (
 	"sync"
 	"time"
 
-	embedetcd "github.com/linkall-labs/embed-etcd"
+	"github.com/linkall-labs/vanus/internal/controller/member"
 	"github.com/linkall-labs/vanus/internal/kv"
 	"github.com/linkall-labs/vanus/internal/kv/etcd"
 	"github.com/linkall-labs/vanus/observability/log"
@@ -44,13 +44,13 @@ type Config struct {
 	KVPrefix    string
 }
 
-func NewSnowflakeController(cfg Config, member embedetcd.Member) *snowflake { //nolint:revive // it's ok
+func NewSnowflakeController(cfg Config, mem member.Member) *snowflake { //nolint:revive // it's ok
 	sf := &snowflake{
 		cfg:    cfg,
-		member: member,
+		member: mem,
 		r:      rand.New(rand.NewSource(time.Now().UnixNano())),
 	}
-	member.RegisterMembershipChangedProcessor(sf.membershipChangedProcessor)
+	mem.RegisterMembershipChangedProcessor(sf.membershipChangedProcessor)
 	return sf
 }
 
@@ -59,7 +59,7 @@ type snowflake struct {
 	cfg      Config
 	kvStore  kv.Client
 	isLeader bool
-	member   embedetcd.Member
+	member   member.Member
 	nodes    map[uint16]*node
 	mutex    sync.RWMutex
 	r        *rand.Rand
@@ -160,12 +160,12 @@ func (sf *snowflake) Stop() {
 	_ = sf.kvStore.Close()
 }
 
-func (sf *snowflake) membershipChangedProcessor(ctx context.Context, event embedetcd.MembershipChangedEvent) error {
+func (sf *snowflake) membershipChangedProcessor(ctx context.Context, event member.MembershipChangedEvent) error {
 	sf.mutex.Lock()
 	defer sf.mutex.Unlock()
 
 	switch event.Type {
-	case embedetcd.EventBecomeLeader:
+	case member.EventBecomeLeader:
 		if sf.isLeader {
 			return nil
 		}
@@ -208,7 +208,7 @@ func (sf *snowflake) membershipChangedProcessor(ctx context.Context, event embed
 			sf.nodes[n.ID] = n
 		}
 		sf.isLeader = true
-	case embedetcd.EventBecomeFollower:
+	case member.EventBecomeFollower:
 		if !sf.isLeader {
 			return nil
 		}
diff --git a/internal/controller/trigger/controller.go b/internal/controller/trigger/controller.go
index 55e7f54..22ca926 100644
--- a/internal/controller/trigger/controller.go
+++ b/internal/controller/trigger/controller.go
@@ -24,8 +24,8 @@ import (
 	"sync"
 	"time"
 
-	embedetcd "github.com/linkall-labs/embed-etcd"
 	eb "github.com/linkall-labs/vanus/client"
+	"github.com/linkall-labs/vanus/internal/controller/member"
 	"github.com/linkall-labs/vanus/internal/controller/trigger/metadata"
 	"github.com/linkall-labs/vanus/internal/controller/trigger/secret"
 	"github.com/linkall-labs/vanus/internal/controller/trigger/storage"
@@ -54,10 +54,10 @@ const (
 	defaultGcSubscriptionInterval = time.Second * 10
 )
 
-func NewController(config Config, member embedetcd.Member) *controller {
+func NewController(config Config, mem member.Member) *controller {
 	ctrl := &controller{
 		config:                config,
-		member:                member,
+		member:                mem,
 		needCleanSubscription: map[vanus.ID]string{},
 		state:                 primitive.ServerStateCreated,
 		cl:                    cluster.NewClusterController(config.ControllerAddr, insecure.NewCredentials()),
@@ -69,7 +69,7 @@ func NewController(config Config, member embedetcd.Member) *controller {
 
 type controller struct {
 	config                Config
-	member                embedetcd.Member
+	member                member.Member
 	storage               storage.Storage
 	secretStorage         secret.Storage
 	subscriptionManager   subscription.Manager
@@ -531,11 +531,11 @@ func (ctrl *controller) init(ctx context.Context) error {
 }
 
 func (ctrl *controller) membershipChangedProcessor(ctx context.Context,
-	event embedetcd.MembershipChangedEvent) error {
+	event member.MembershipChangedEvent) error {
 	ctrl.membershipMutex.Lock()
 	defer ctrl.membershipMutex.Unlock()
 	switch event.Type {
-	case embedetcd.EventBecomeLeader:
+	case member.EventBecomeLeader:
 		if ctrl.isLeader {
 			return nil
 		}
@@ -559,7 +559,7 @@ func (ctrl *controller) membershipChangedProcessor(ctx context.Context,
 		go ctrl.gcSubscriptions(ctx)
 		ctrl.state = primitive.ServerStateRunning
 		ctrl.isLeader = true
-	case embedetcd.EventBecomeFollower:
+	case member.EventBecomeFollower:
 		if !ctrl.isLeader {
 			return nil
 		}
@@ -621,7 +621,9 @@ func (ctrl *controller) initTriggerSystemEventbus() {
 		log.Info(ctx, "trigger controller is ready to check system eventbus", nil)
 		if err := ctrl.cl.WaitForControllerReady(true); err != nil {
 			log.Error(ctx, "trigger controller try to create system eventbus, "+
-				"but Vanus cluster hasn't ready, exit", nil)
+				"but Vanus cluster hasn't ready, exit", map[string]interface{}{
+				log.KeyError: err,
+			})
 			os.Exit(-1)
 		}
 
diff --git a/internal/primitive/interceptor/memberinterceptor/member.go b/internal/primitive/interceptor/memberinterceptor/member.go
index dacc16a..ef3e72a 100644
--- a/internal/primitive/interceptor/memberinterceptor/member.go
+++ b/internal/primitive/interceptor/memberinterceptor/member.go
@@ -18,30 +18,30 @@ import (
 	"context"
 	"fmt"
 
-	embedetcd "github.com/linkall-labs/embed-etcd"
+	"github.com/linkall-labs/vanus/internal/controller/member"
 	"github.com/linkall-labs/vanus/pkg/errors"
 	"google.golang.org/grpc"
 )
 
-func StreamServerInterceptor(member embedetcd.Member) grpc.StreamServerInterceptor {
+func StreamServerInterceptor(mem member.Member) grpc.StreamServerInterceptor {
 	return func(srv interface{}, stream grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
-		if !member.IsLeader() {
+		if !mem.IsLeader() {
 			// TODO  read-only request bypass
 			return errors.ErrNotLeader.WithMessage(
-				fmt.Sprintf("i'm not leader, please connect to: %s", member.GetLeaderAddr()))
+				fmt.Sprintf("i'm not leader, please connect to: %s", mem.GetLeaderAddr()))
 		}
 		return handler(srv, stream)
 	}
 }
 
-func UnaryServerInterceptor(member embedetcd.Member) grpc.UnaryServerInterceptor {
+func UnaryServerInterceptor(mem member.Member) grpc.UnaryServerInterceptor {
 	return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo,
 		handler grpc.UnaryHandler) (interface{}, error) {
 		if info.FullMethod != "/linkall.vanus.controller.PingServer/Ping" &&
-			!member.IsLeader() {
+			!mem.IsLeader() {
 			// TODO  read-only request bypass
 			return nil, errors.ErrNotLeader.WithMessage(
-				fmt.Sprintf("i'm not leader, please connect to: %s", member.GetLeaderAddr()))
+				fmt.Sprintf("i'm not leader, please connect to: %s", mem.GetLeaderAddr()))
 		}
 		return handler(ctx, req)
 	}
diff --git a/internal/timer/config.go b/internal/timer/config.go
index ef6905f..a3753dd 100644
--- a/internal/timer/config.go
+++ b/internal/timer/config.go
@@ -31,7 +31,7 @@ type Config struct {
 	EtcdEndpoints        []string             `yaml:"etcd"`
 	CtrlEndpoints        []string             `yaml:"controllers"`
 	MetadataConfig       MetadataConfig       `yaml:"metadata"`
-	LeaderElectionConfig LeaderElectionConfig `yaml:"leaderelection"`
+	LeaderElectionConfig LeaderElectionConfig `yaml:"leader_election"`
 	TimingWheelConfig    TimingWheelConfig    `yaml:"timingwheel"`
 	Observability        observability.Config `yaml:"observability"`
 }
diff --git a/internal/timer/metadata/key.go b/internal/timer/metadata/key.go
index 7a8e9e1..be07c55 100644
--- a/internal/timer/metadata/key.go
+++ b/internal/timer/metadata/key.go
@@ -15,6 +15,6 @@
 package metadata
 
 const (
-	ResourceLockKeyPrefixInKVStore = "/vanus/internal/resource/resourcelock"
+	ResourceLockKeyPrefixInKVStore = "/vanus/internal/resource/resource_lock"
 	MetadataKeyPrefixInKVStore     = "/vanus/internal/resource/timer/metadata"
 )
diff --git a/raft/go.mod b/raft/go.mod
index c3ee1b3..4731404 100644
--- a/raft/go.mod
+++ b/raft/go.mod
@@ -6,7 +6,7 @@ require (
 	github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5
 	github.com/gogo/protobuf v1.3.2
 	github.com/golang/protobuf v1.5.2
-	go.etcd.io/etcd/client/pkg/v3 v3.6.0-alpha.0
+	go.etcd.io/etcd/client/pkg/v3 v3.5.7
 	go.opentelemetry.io/otel/trace v1.11.2
 )
 
@@ -14,17 +14,13 @@ require (
 	github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054 // indirect
 	github.com/cockroachdb/errors v1.2.4 // indirect
 	github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f // indirect
-	github.com/davecgh/go-spew v1.1.1 // indirect
 	github.com/getsentry/raven-go v0.2.0 // indirect
 	github.com/kr/pretty v0.3.0 // indirect
 	github.com/pkg/errors v0.9.1 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
 	github.com/rogpeppe/go-internal v1.8.0 // indirect
-	github.com/stretchr/testify v1.8.1 // indirect
 	go.opentelemetry.io/otel v1.11.2 // indirect
 	google.golang.org/protobuf v1.28.1 // indirect
-	gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
-	gopkg.in/yaml.v3 v3.0.1 // indirect
 )
 
 // Bad imports are sometimes causing attempts to pull that code.
diff --git a/raft/go.sum b/raft/go.sum
index 83a7fdd..c984ee9 100644
--- a/raft/go.sum
+++ b/raft/go.sum
@@ -32,16 +32,20 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI
 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
+github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
 github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
+github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
 github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
@@ -53,8 +57,8 @@ github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKs
 github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
 github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
-go.etcd.io/etcd/client/pkg/v3 v3.6.0-alpha.0 h1:2UyRzFWbZZzgu/xzxoRukgixvafiJtGyxO+3IKUyJ6c=
-go.etcd.io/etcd/client/pkg/v3 v3.6.0-alpha.0/go.mod h1:Vl/FkH40bHqmBFwhr8WVKtV47neyts36zl1voccRq8s=
+go.etcd.io/etcd/client/pkg/v3 v3.5.7 h1:y3kf5Gbp4e4q7egZdn5T7W9TSHUvkClN6u+Rq9mEOmg=
+go.etcd.io/etcd/client/pkg/v3 v3.5.7/go.mod h1:o0Abi1MK86iad3YrWhgUsbGx1pmTS+hrORWc2CamuhY=
 go.opentelemetry.io/otel v1.11.2 h1:YBZcQlsVekzFsFbjygXMOXSs6pialIZxcjfO/mBDmR0=
 go.opentelemetry.io/otel v1.11.2/go.mod h1:7p4EUV+AqgdlNV9gL97IgUZiVR3yrFXYo53f9BM3tRI=
 go.opentelemetry.io/otel/trace v1.11.2 h1:Xf7hWSF2Glv0DE3MH7fBHvtpSBsjcBUe5MYAmZM/+y0=
@@ -77,7 +81,7 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -94,7 +98,8 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ
 google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
 google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
 gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=


Test patch:
diff --git a/internal/controller/eventbus/controller_test.go b/internal/controller/eventbus/controller_test.go
index 6ae98f7..b00d180 100644
--- a/internal/controller/eventbus/controller_test.go
+++ b/internal/controller/eventbus/controller_test.go
@@ -21,9 +21,9 @@ import (
 	"testing"
 
 	"github.com/golang/mock/gomock"
-	embedetcd "github.com/linkall-labs/embed-etcd"
 	"github.com/linkall-labs/vanus/internal/controller/eventbus/eventlog"
 	"github.com/linkall-labs/vanus/internal/controller/eventbus/metadata"
+	"github.com/linkall-labs/vanus/internal/controller/member"
 	"github.com/linkall-labs/vanus/internal/kv"
 	"github.com/linkall-labs/vanus/internal/primitive"
 	"github.com/linkall-labs/vanus/internal/primitive/vanus"
@@ -45,7 +45,7 @@ func TestController_CreateEventBus(t *testing.T) {
 		ctrl.eventLogMgr = elMgr
 		ctx := stdCtx.Background()
 
-		mockMember := embedetcd.NewMockMember(mockCtrl)
+		mockMember := member.NewMockMember(mockCtrl)
 		ctrl.member = mockMember
 		mockMember.EXPECT().IsLeader().AnyTimes().Return(true)
 		mockMember.EXPECT().IsReady().AnyTimes().Return(true)