You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@skywalking.apache.org by wu...@apache.org on 2019/07/27 14:02:46 UTC

[skywalking] branch master updated: Support etcd configuration. (#2973)

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

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


The following commit(s) were added to refs/heads/master by this push:
     new 76d9163  Support etcd configuration. (#2973)
76d9163 is described below

commit 76d91635eeeb07ff5f5ff665a1cba88b5ec0de8d
Author: Alan Lau <li...@cmss.chinamobile.com>
AuthorDate: Sat Jul 27 22:02:40 2019 +0800

    Support etcd configuration. (#2973)
    
    * Support etcd configuration.
---
 apm-dist/release-docs/LICENSE                      |   1 +
 oap-server/pom.xml                                 |  68 +++++++++
 .../cluster-etcd-plugin/pom.xml                    |  46 ++----
 .../configuration-etcd}/pom.xml                    |  86 ++++++-----
 .../configuration/etcd/EtcdConfigException.java    |  33 +++++
 .../etcd/EtcdConfigWatcherRegister.java            | 162 +++++++++++++++++++++
 .../etcd/EtcdConfigurationProvider.java            |  67 +++++++++
 .../configuration/etcd/EtcdServerSettings.java     |  52 +++++++
 .../oap/server/configuration/etcd/EtcdUtils.java   |  72 +++++++++
 ...alking.oap.server.library.module.ModuleProvider |  19 +++
 .../etcd/EtcdConfigWatcherRegisterTest.java        | 132 +++++++++++++++++
 .../etcd/EtcdConfigurationTestModule.java          |  37 +++++
 .../etcd/EtcdConfigurationTestProvider.java        |  89 +++++++++++
 .../etcd/ITEtcdConfigurationTest.java              | 153 +++++++++++++++++++
 .../server/configuration/etcd/TestEtcdUtils.java   |  64 ++++++++
 ...ywalking.oap.server.library.module.ModuleDefine |  20 +++
 ...alking.oap.server.library.module.ModuleProvider |  19 +++
 .../src/test/resources/application.yml             |  33 +++++
 .../src/test/resources/log4j2.xml}                 |  31 ++--
 oap-server/server-configuration/pom.xml            |   1 +
 oap-server/server-starter/pom.xml                  |   5 +
 .../src/main/assembly/application.yml              |   5 +
 .../src/main/resources/application.yml             |   5 +
 23 files changed, 1101 insertions(+), 99 deletions(-)

diff --git a/apm-dist/release-docs/LICENSE b/apm-dist/release-docs/LICENSE
index bddc04a..95e0c08 100644
--- a/apm-dist/release-docs/LICENSE
+++ b/apm-dist/release-docs/LICENSE
@@ -325,6 +325,7 @@ The text of each license is the standard Apache 2.0 license.
     Ctripcorp: apollo 1.4.0: https://github.com/ctripcorp/apollo Apache 2.0
     etcd4j 2.17.0: https://github.com/jurmous/etcd4j Apache 2.0
     javaassist 3.25.0-GA: https://github.com/jboss-javassist/javassist Apache 2.0
+    jackson-module-afterburner 2.9.5: https://github.com/FasterXML/jackson-modules-base, Apache 2.0
 
 ========================================================================
 MIT licenses
diff --git a/oap-server/pom.xml b/oap-server/pom.xml
index b2e2186..8cd33bc 100644
--- a/oap-server/pom.xml
+++ b/oap-server/pom.xml
@@ -82,10 +82,14 @@
         <curator-test.version>2.12.0</curator-test.version>
         <etcd4j.version>2.17.0</etcd4j.version>
         <etcd.version>v3.2.3</etcd.version>
+        <netty.version>4.1.27.Final</netty.version>
+        <jackson-module-afterburner.version>2.9.5</jackson-module-afterburner.version>
         <antlr.version>4.7.1</antlr.version>
         <freemarker.version>2.3.28</freemarker.version>
         <javaassist.version>3.25.0-GA</javaassist.version>
+
         <zookeeper.image.version>3.5</zookeeper.image.version>
+
     </properties>
 
     <dependencies>
@@ -378,11 +382,75 @@
                 <artifactId>nacos-client</artifactId>
                 <version>${nacos.version}</version>
             </dependency>
+
             <dependency>
                 <groupId>org.mousio</groupId>
                 <artifactId>etcd4j</artifactId>
+                <exclusions>
+                    <exclusion>
+                        <artifactId>netty-codec-dns</artifactId>
+                        <groupId>io.netty</groupId>
+                    </exclusion>
+
+                    <exclusion>
+                        <artifactId>netty-codec-dns</artifactId>
+                        <groupId>io.netty</groupId>
+                    </exclusion>
+
+                    <exclusion>
+                        <artifactId>netty-codec-http</artifactId>
+                        <groupId>io.netty</groupId>
+                    </exclusion>
+
+                    <exclusion>
+                        <artifactId>netty-handler</artifactId>
+                        <groupId>io.netty</groupId>
+                    </exclusion>
+
+                    <exclusion>
+                        <artifactId>netty-resolver-dns</artifactId>
+                        <groupId>io.netty</groupId>
+                    </exclusion>
+
+                    <exclusion>
+                        <groupId>com.fasterxml.jackson.module</groupId>
+                        <artifactId>jackson-module-afterburner</artifactId>
+                    </exclusion>
+                </exclusions>
                 <version>${etcd4j.version}</version>
             </dependency>
+
+            <dependency>
+                <groupId>io.netty</groupId>
+                <artifactId>netty-handler</artifactId>
+                <version>${netty.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>io.netty</groupId>
+                <artifactId>netty-resolver-dns</artifactId>
+                <version>${netty.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>io.netty</groupId>
+                <artifactId>netty-codec-dns</artifactId>
+                <version>${netty.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>io.netty</groupId>
+                <artifactId>netty-codec-http</artifactId>
+                <version>${netty.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>com.fasterxml.jackson.module</groupId>
+                <artifactId>jackson-module-afterburner</artifactId>
+                <version>${jackson-module-afterburner.version}</version>
+            </dependency>
+
+
             <dependency>
                 <groupId>org.apache.curator</groupId>
                 <artifactId>curator-x-discovery</artifactId>
diff --git a/oap-server/server-cluster-plugin/cluster-etcd-plugin/pom.xml b/oap-server/server-cluster-plugin/cluster-etcd-plugin/pom.xml
index d2315ec..7673108 100644
--- a/oap-server/server-cluster-plugin/cluster-etcd-plugin/pom.xml
+++ b/oap-server/server-cluster-plugin/cluster-etcd-plugin/pom.xml
@@ -38,58 +38,32 @@
 
         <dependency>
             <groupId>io.netty</groupId>
+            <artifactId>netty-codec-dns</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>io.netty</groupId>
+            <artifactId>netty-codec-http</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>io.netty</groupId>
             <artifactId>netty-handler</artifactId>
-            <version>4.1.27.Final</version>
         </dependency>
 
         <dependency>
             <groupId>io.netty</groupId>
             <artifactId>netty-resolver-dns</artifactId>
-            <version>4.1.27.Final</version>
         </dependency>
 
-
         <dependency>
             <groupId>org.mousio</groupId>
             <artifactId>etcd4j</artifactId>
-            <exclusions>
-                <exclusion>
-                    <artifactId>netty-codec-dns</artifactId>
-                    <groupId>io.netty</groupId>
-                </exclusion>
-
-                <exclusion>
-                    <artifactId>netty-codec-dns</artifactId>
-                    <groupId>io.netty</groupId>
-                </exclusion>
-
-                <exclusion>
-                    <artifactId>netty-codec-http</artifactId>
-                    <groupId>io.netty</groupId>
-                </exclusion>
-
-                <exclusion>
-                    <artifactId>netty-handler</artifactId>
-                    <groupId>io.netty</groupId>
-                </exclusion>
-
-                <exclusion>
-                    <artifactId>netty-resolver-dns</artifactId>
-                    <groupId>io.netty</groupId>
-                </exclusion>
-
-                <exclusion>
-                    <groupId>com.fasterxml.jackson.module</groupId>
-                    <artifactId>jackson-module-afterburner</artifactId>
-                </exclusion>
-            </exclusions>
         </dependency>
 
-
         <dependency>
             <groupId>com.fasterxml.jackson.module</groupId>
             <artifactId>jackson-module-afterburner</artifactId>
-            <version>2.9.5</version>
         </dependency>
 
     </dependencies>
diff --git a/oap-server/server-cluster-plugin/cluster-etcd-plugin/pom.xml b/oap-server/server-configuration/configuration-etcd/pom.xml
similarity index 76%
copy from oap-server/server-cluster-plugin/cluster-etcd-plugin/pom.xml
copy to oap-server/server-configuration/configuration-etcd/pom.xml
index d2315ec..4ee2145 100644
--- a/oap-server/server-cluster-plugin/cluster-etcd-plugin/pom.xml
+++ b/oap-server/server-configuration/configuration-etcd/pom.xml
@@ -16,82 +16,57 @@
   ~ limitations under the License.
   ~
   -->
-
 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
     <parent>
-        <artifactId>server-cluster-plugin</artifactId>
+        <artifactId>server-configuration</artifactId>
         <groupId>org.apache.skywalking</groupId>
         <version>6.3.0-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
-    <artifactId>cluster-etcd-plugin</artifactId>
-
-    <name>cluster-etcd-plugin</name>
+    <artifactId>configuration-etcd</artifactId>
 
     <dependencies>
         <dependency>
             <groupId>org.apache.skywalking</groupId>
-            <artifactId>server-core</artifactId>
+            <artifactId>configuration-api</artifactId>
             <version>${project.version}</version>
         </dependency>
 
         <dependency>
             <groupId>io.netty</groupId>
+            <artifactId>netty-codec-dns</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>io.netty</groupId>
+            <artifactId>netty-codec-http</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>io.netty</groupId>
             <artifactId>netty-handler</artifactId>
-            <version>4.1.27.Final</version>
         </dependency>
 
         <dependency>
             <groupId>io.netty</groupId>
             <artifactId>netty-resolver-dns</artifactId>
-            <version>4.1.27.Final</version>
         </dependency>
 
-
         <dependency>
             <groupId>org.mousio</groupId>
             <artifactId>etcd4j</artifactId>
-            <exclusions>
-                <exclusion>
-                    <artifactId>netty-codec-dns</artifactId>
-                    <groupId>io.netty</groupId>
-                </exclusion>
-
-                <exclusion>
-                    <artifactId>netty-codec-dns</artifactId>
-                    <groupId>io.netty</groupId>
-                </exclusion>
-
-                <exclusion>
-                    <artifactId>netty-codec-http</artifactId>
-                    <groupId>io.netty</groupId>
-                </exclusion>
-
-                <exclusion>
-                    <artifactId>netty-handler</artifactId>
-                    <groupId>io.netty</groupId>
-                </exclusion>
-
-                <exclusion>
-                    <artifactId>netty-resolver-dns</artifactId>
-                    <groupId>io.netty</groupId>
-                </exclusion>
-
-                <exclusion>
-                    <groupId>com.fasterxml.jackson.module</groupId>
-                    <artifactId>jackson-module-afterburner</artifactId>
-                </exclusion>
-            </exclusions>
         </dependency>
 
-
         <dependency>
             <groupId>com.fasterxml.jackson.module</groupId>
             <artifactId>jackson-module-afterburner</artifactId>
-            <version>2.9.5</version>
         </dependency>
 
+        <dependency>
+            <groupId>org.yaml</groupId>
+            <artifactId>snakeyaml</artifactId>
+        </dependency>
     </dependencies>
 
     <profiles>
@@ -122,10 +97,10 @@
                                             <alias>etcd-client-integration-test</alias>
                                             <run>
                                                 <ports>
-                                                    <port>+etcd.host:etcd.port:2379</port>
+                                                    <port>etcd.port:2379</port>
                                                 </ports>
                                                 <wait>
-                                                    <time>20000</time>
+                                                    <time>5000</time>
                                                 </wait>
                                                 <entrypoint>
                                                     <!-- exec form  -->
@@ -151,6 +126,29 @@
                     </plugin>
 
                     <plugin>
+                        <groupId>org.codehaus.gmaven</groupId>
+                        <artifactId>gmaven-plugin</artifactId>
+                        <version>1.5</version>
+                        <executions>
+                            <execution>
+                                <id>add-default-properties</id>
+                                <phase>initialize</phase>
+                                <goals>
+                                    <goal>execute</goal>
+                                </goals>
+                                <configuration>
+                                    <providerSelection>2.0</providerSelection>
+                                    <source>
+                                        project.properties.setProperty('etcd.host', 'localhost')
+
+                                        log.info("Etcd host is " + project.properties['etcd.host'])
+                                    </source>
+                                </configuration>
+                            </execution>
+                        </executions>
+                    </plugin>
+
+                    <plugin>
                         <groupId>org.apache.maven.plugins</groupId>
                         <artifactId>maven-failsafe-plugin</artifactId>
                         <configuration>
diff --git a/oap-server/server-configuration/configuration-etcd/src/main/java/org/apache/skywalking/oap/server/configuration/etcd/EtcdConfigException.java b/oap-server/server-configuration/configuration-etcd/src/main/java/org/apache/skywalking/oap/server/configuration/etcd/EtcdConfigException.java
new file mode 100644
index 0000000..e2ef9c8
--- /dev/null
+++ b/oap-server/server-configuration/configuration-etcd/src/main/java/org/apache/skywalking/oap/server/configuration/etcd/EtcdConfigException.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.skywalking.oap.server.configuration.etcd;
+
+/**
+ * exception type throw by Etcd Configuration.
+ *
+ * @author Alan Lau
+ */
+public class EtcdConfigException extends RuntimeException {
+
+
+    public EtcdConfigException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+}
diff --git a/oap-server/server-configuration/configuration-etcd/src/main/java/org/apache/skywalking/oap/server/configuration/etcd/EtcdConfigWatcherRegister.java b/oap-server/server-configuration/configuration-etcd/src/main/java/org/apache/skywalking/oap/server/configuration/etcd/EtcdConfigWatcherRegister.java
new file mode 100644
index 0000000..17899ea
--- /dev/null
+++ b/oap-server/server-configuration/configuration-etcd/src/main/java/org/apache/skywalking/oap/server/configuration/etcd/EtcdConfigWatcherRegister.java
@@ -0,0 +1,162 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.skywalking.oap.server.configuration.etcd;
+
+import java.net.URI;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import mousio.client.promises.ResponsePromise;
+import mousio.etcd4j.EtcdClient;
+import mousio.etcd4j.promises.EtcdResponsePromise;
+import mousio.etcd4j.responses.EtcdErrorCode;
+import mousio.etcd4j.responses.EtcdException;
+import mousio.etcd4j.responses.EtcdKeysResponse;
+import org.apache.skywalking.oap.server.configuration.api.ConfigTable;
+import org.apache.skywalking.oap.server.configuration.api.ConfigWatcherRegister;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * @author Alan Lau
+ */
+public class EtcdConfigWatcherRegister extends ConfigWatcherRegister {
+
+    private final static Logger logger = LoggerFactory.getLogger(EtcdConfigWatcherRegister.class);
+
+    /**
+     * server settings for Etcd configuration
+     */
+    private EtcdServerSettings settings;
+
+    /**
+     * etcd client.
+     */
+    private final EtcdClient client;
+
+    private final Map<String, ResponsePromise.IsSimplePromiseResponseHandler> listenersByKey;
+
+    private final Map<String, Optional<String>> configItemKeyedByName;
+
+    private final Map<String, EtcdResponsePromise<EtcdKeysResponse>> responsePromiseByKey;
+
+    public EtcdConfigWatcherRegister(EtcdServerSettings settings) {
+        super(settings.getPeriod());
+        this.settings = settings;
+        this.configItemKeyedByName = new ConcurrentHashMap<>();
+        this.client = new EtcdClient(EtcdUtils.parse(settings).toArray(new URI[] {}));
+        this.listenersByKey = new ConcurrentHashMap<>();
+        responsePromiseByKey = new ConcurrentHashMap<>();
+    }
+
+    @Override public ConfigTable readConfig(Set<String> keys) {
+        removeUninterestedKeys(keys);
+        registerKeyListeners(keys);
+        final ConfigTable table = new ConfigTable();
+
+        for (Map.Entry<String, Optional<String>> entry : configItemKeyedByName.entrySet()) {
+            final String key = entry.getKey();
+            final Optional<String> value = entry.getValue();
+
+            if (value.isPresent()) {
+                table.add(new ConfigTable.ConfigItem(key, value.get()));
+            } else {
+                table.add(new ConfigTable.ConfigItem(key, null));
+            }
+        }
+
+        return table;
+    }
+
+    private void registerKeyListeners(final Set<String> keys) {
+        for (final String key : keys) {
+            String dataId = "/" + settings.getGroup() + "/" + key;
+            if (listenersByKey.containsKey(dataId)) {
+                continue;
+            }
+
+            listenersByKey.putIfAbsent(dataId, p -> {
+                onDataValueChanged(p, dataId);
+            });
+
+            try {
+                EtcdResponsePromise<EtcdKeysResponse> responsePromise = client.get(dataId).waitForChange().send();
+                responsePromise.addListener(listenersByKey.get(dataId));
+                responsePromiseByKey.putIfAbsent(dataId, responsePromise);
+
+                // the key is newly added, read the config for the first time
+                EtcdResponsePromise<EtcdKeysResponse> promise = client.get(dataId).send();
+                onDataValueChanged(promise, dataId);
+            } catch (Exception e) {
+                throw new EtcdConfigException("wait for etcd value change fail", e);
+            }
+        }
+    }
+
+    private void removeUninterestedKeys(final Set<String> interestedKeys) {
+        final Set<String> uninterestedKeys = new HashSet<>(listenersByKey.keySet());
+        uninterestedKeys.removeAll(interestedKeys);
+
+        uninterestedKeys.forEach(k -> {
+            final ResponsePromise.IsSimplePromiseResponseHandler listener = listenersByKey.remove(k);
+            if (listener != null) {
+                responsePromiseByKey.remove(k).removeListener(listener);
+            }
+        });
+    }
+
+    private void onDataValueChanged(ResponsePromise<EtcdKeysResponse> promise, String dataId) {
+        String key = getRealKey(dataId, settings.getGroup());
+        try {
+            EtcdKeysResponse.EtcdNode node = promise.get().getNode();
+            String value = node.getValue();
+            if (logger.isInfoEnabled()) {
+                logger.info("Etcd config changed: {}: {}", key, node.getValue());
+            }
+
+            configItemKeyedByName.put(key, Optional.ofNullable(value));
+        } catch (Exception e) {
+            if (e instanceof EtcdException) {
+                if (EtcdErrorCode.KeyNotFound == ((EtcdException)e).errorCode) {
+                    configItemKeyedByName.put(key, Optional.empty());
+                    return;
+                }
+            }
+            throw new EtcdConfigException("wait for value changed fail", e);
+        }
+    }
+
+    /**
+     * get real key in etcd cluster which is removed "/${group}" from the key retrive from etcd.
+     *
+     * @param key
+     * @param group
+     * @return
+     */
+    private String getRealKey(String key, String group) {
+        int index = key.indexOf(group);
+        if (index <= 0) {
+            throw new RuntimeException("the group doesn't match");
+        }
+        String realKey = key.substring(index + group.length() + 1);
+        return realKey;
+    }
+}
diff --git a/oap-server/server-configuration/configuration-etcd/src/main/java/org/apache/skywalking/oap/server/configuration/etcd/EtcdConfigurationProvider.java b/oap-server/server-configuration/configuration-etcd/src/main/java/org/apache/skywalking/oap/server/configuration/etcd/EtcdConfigurationProvider.java
new file mode 100644
index 0000000..f63314c
--- /dev/null
+++ b/oap-server/server-configuration/configuration-etcd/src/main/java/org/apache/skywalking/oap/server/configuration/etcd/EtcdConfigurationProvider.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.skywalking.oap.server.configuration.etcd;
+
+import com.google.common.base.Strings;
+import org.apache.skywalking.oap.server.configuration.api.AbstractConfigurationProvider;
+import org.apache.skywalking.oap.server.configuration.api.ConfigWatcherRegister;
+import org.apache.skywalking.oap.server.library.module.ModuleConfig;
+import org.apache.skywalking.oap.server.library.module.ModuleStartException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Get Configuration from etcd.
+ *
+ * @author Alan Lau
+ */
+public class EtcdConfigurationProvider extends AbstractConfigurationProvider {
+
+    private final static Logger logger = LoggerFactory.getLogger(EtcdConfigurationProvider.class);
+
+    private EtcdServerSettings settings;
+
+    public EtcdConfigurationProvider() {
+        settings = new EtcdServerSettings();
+    }
+
+    @Override protected ConfigWatcherRegister initConfigReader() throws ModuleStartException {
+        logger.info("settings: {}", settings);
+        if (Strings.isNullOrEmpty(settings.getServerAddr())) {
+            throw new ModuleStartException("Etcd serverAddr cannot be null or empty.");
+        }
+        if (Strings.isNullOrEmpty(settings.getGroup())) {
+            throw new ModuleStartException("Etcd group cannot be null or empty.");
+        }
+
+        try {
+            return new EtcdConfigWatcherRegister(settings);
+        } catch (Exception e) {
+            throw new ModuleStartException(e.getMessage(), e);
+        }
+    }
+
+    @Override public String name() {
+        return "etcd";
+    }
+
+    @Override public ModuleConfig createConfigBeanIfAbsent() {
+        return settings;
+    }
+}
diff --git a/oap-server/server-configuration/configuration-etcd/src/main/java/org/apache/skywalking/oap/server/configuration/etcd/EtcdServerSettings.java b/oap-server/server-configuration/configuration-etcd/src/main/java/org/apache/skywalking/oap/server/configuration/etcd/EtcdServerSettings.java
new file mode 100644
index 0000000..6893824
--- /dev/null
+++ b/oap-server/server-configuration/configuration-etcd/src/main/java/org/apache/skywalking/oap/server/configuration/etcd/EtcdServerSettings.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.skywalking.oap.server.configuration.etcd;
+
+import lombok.Getter;
+import lombok.Setter;
+import lombok.ToString;
+import org.apache.skywalking.oap.server.library.module.ModuleConfig;
+
+/**
+ * entity wrapps the etcd cluster configuration.
+ *
+ * @author Alan Lau
+ */
+@ToString
+@Getter
+@Setter
+public class EtcdServerSettings extends ModuleConfig {
+
+    private String clusterName = "default";
+    /**
+     * etcd cluster address, like "10.10.10.1:2379, 10.10.10.2:2379,10.10.10.3.2379".
+     */
+    private String serverAddr;
+
+    /**
+     * directory for configuration
+     */
+    private String group;
+
+    /**
+     * sec for interval refresh config data.
+     */
+    private int period = 60;
+
+}
diff --git a/oap-server/server-configuration/configuration-etcd/src/main/java/org/apache/skywalking/oap/server/configuration/etcd/EtcdUtils.java b/oap-server/server-configuration/configuration-etcd/src/main/java/org/apache/skywalking/oap/server/configuration/etcd/EtcdUtils.java
new file mode 100644
index 0000000..71834d7
--- /dev/null
+++ b/oap-server/server-configuration/configuration-etcd/src/main/java/org/apache/skywalking/oap/server/configuration/etcd/EtcdUtils.java
@@ -0,0 +1,72 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.skywalking.oap.server.configuration.etcd;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Properties;
+import org.apache.skywalking.oap.server.library.util.Address;
+import org.apache.skywalking.oap.server.library.util.ConnectUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * a util for etcd serverAddr parse.
+ *
+ * @author Alan Lau
+ */
+public class EtcdUtils {
+
+    private final static Logger logger = LoggerFactory.getLogger(EtcdUtils.class);
+
+    public EtcdUtils() {
+    }
+
+    public static List<URI> parse(EtcdServerSettings settings) {
+        List<URI> uris = new ArrayList<>();
+        try {
+            logger.info("etcd settings is {}", settings);
+            List<Address> addressList = ConnectUtils.parse(settings.getServerAddr());
+            for (Address address : addressList) {
+                uris.add(new URI("http", null, address.getHost(), address.getPort(), null, null, null));
+            }
+        } catch (Exception e) {
+            throw new EtcdConfigException(e.getMessage(), e);
+        }
+
+        return uris;
+    }
+
+    public static List<URI> parseProp(Properties properties) {
+        List<URI> uris = new ArrayList<>();
+        try {
+            logger.info("etcd server addr is {}", properties);
+            List<Address> addressList = ConnectUtils.parse(properties.getProperty("serverAddr"));
+            for (Address address : addressList) {
+                uris.add(new URI("http", null, address.getHost(), address.getPort(), null, null, null));
+            }
+        } catch (Exception e) {
+            throw new EtcdConfigException(e.getMessage(), e);
+        }
+
+        return uris;
+    }
+
+}
diff --git a/oap-server/server-configuration/configuration-etcd/src/main/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleProvider b/oap-server/server-configuration/configuration-etcd/src/main/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleProvider
new file mode 100644
index 0000000..a238639
--- /dev/null
+++ b/oap-server/server-configuration/configuration-etcd/src/main/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleProvider
@@ -0,0 +1,19 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+#
+
+org.apache.skywalking.oap.server.configuration.etcd.EtcdConfigurationProvider
\ No newline at end of file
diff --git a/oap-server/server-configuration/configuration-etcd/src/test/java/org/apache/skywalking/oap/server/configuration/etcd/EtcdConfigWatcherRegisterTest.java b/oap-server/server-configuration/configuration-etcd/src/test/java/org/apache/skywalking/oap/server/configuration/etcd/EtcdConfigWatcherRegisterTest.java
new file mode 100644
index 0000000..2d4fcf6
--- /dev/null
+++ b/oap-server/server-configuration/configuration-etcd/src/test/java/org/apache/skywalking/oap/server/configuration/etcd/EtcdConfigWatcherRegisterTest.java
@@ -0,0 +1,132 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.skywalking.oap.server.configuration.etcd;
+
+import com.google.common.collect.Sets;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import mousio.client.promises.ResponsePromise;
+import mousio.etcd4j.EtcdClient;
+import mousio.etcd4j.promises.EtcdResponsePromise;
+import mousio.etcd4j.requests.EtcdKeyGetRequest;
+import mousio.etcd4j.responses.EtcdKeysResponse;
+import org.apache.skywalking.oap.server.configuration.api.ConfigTable;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.powermock.api.mockito.PowerMockito;
+import org.powermock.core.classloader.annotations.PowerMockIgnore;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+import org.powermock.reflect.Whitebox;
+
+import static junit.framework.TestCase.assertEquals;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.spy;
+import static org.powermock.api.mockito.PowerMockito.mock;
+import static org.powermock.api.mockito.PowerMockito.mockStatic;
+import static org.powermock.api.mockito.PowerMockito.when;
+import static org.powermock.api.mockito.PowerMockito.whenNew;
+
+/**
+ * @author Alan Lau
+ */
+@RunWith(PowerMockRunner.class)
+@PrepareForTest({EtcdKeysResponse.class, EtcdUtils.class, EtcdClient.class, URI.class})
+@PowerMockIgnore({"javax.management.*"})
+public class EtcdConfigWatcherRegisterTest {
+
+    @Before
+    @Test
+    public void shouldReadConfigs() throws Exception {
+        final String group = "skywalking";
+        final String testKey1 = "receiver-trace.default.slowDBAccessThreshold";
+        final String testVal1 = "test";
+        final String testKey2 = "testKey";
+        final String testVal2 = "testVal";
+
+        final EtcdServerSettings mockSettings = mock(EtcdServerSettings.class);
+        when(mockSettings.getGroup()).thenReturn(group);
+        mockStatic(EtcdUtils.class);
+
+        List<URI> uris = mock(List.class);
+        when(EtcdUtils.parse(any())).thenReturn(uris);
+
+        final EtcdClient client = PowerMockito.mock(EtcdClient.class);
+        whenNew(EtcdClient.class).withAnyArguments().thenReturn(client);
+
+        String port = System.getProperty("etcd.port");
+        URI uri = new URI("http://localhost:" + port);
+        List<URI> urisArray = spy(ArrayList.class);
+        urisArray.add(uri);
+        URI[] array = urisArray.toArray(new URI[] {});
+        when(uris.toArray(new URI[] {})).thenReturn(array);
+
+        final EtcdConfigWatcherRegister mockRegister = spy(new EtcdConfigWatcherRegister(mockSettings));
+
+        Whitebox.setInternalState(mockRegister, "client", client);
+        Whitebox.setInternalState(mockRegister, "settings", mockSettings);
+
+        final EtcdKeysResponse response = PowerMockito.mock(EtcdKeysResponse.class);
+        final EtcdKeysResponse response1 = PowerMockito.mock(EtcdKeysResponse.class);
+
+        final EtcdKeyGetRequest request = PowerMockito.mock(EtcdKeyGetRequest.class);
+
+        when(client.get("/skywalking/receiver-trace.default.slowDBAccessThreshold")).thenReturn(request);
+        when(request.waitForChange()).thenReturn(request);
+
+        final EtcdResponsePromise<EtcdKeysResponse> promise = mock(EtcdResponsePromise.class);
+        final ResponsePromise<EtcdKeysResponse> responseResponsePromise = mock(ResponsePromise.class);
+        when(request.send()).thenReturn(promise);
+        when(promise.get()).thenReturn(response);
+        when(responseResponsePromise.get()).thenReturn(response);
+
+        final EtcdKeysResponse.EtcdNode node = mock(EtcdKeysResponse.EtcdNode.class);
+        when(response.getNode()).thenReturn(node);
+        when(node.getKey()).thenReturn("/skywalking/receiver-trace.default.slowDBAccessThreshold");
+        when(node.getValue()).thenReturn("test");
+
+        final EtcdKeyGetRequest request1 = mock(EtcdKeyGetRequest.class);
+        when(client.get("/skywalking/testKey")).thenReturn(request1);
+        when(request1.waitForChange()).thenReturn(request1);
+        final EtcdResponsePromise<EtcdKeysResponse> promise1 = mock(EtcdResponsePromise.class);
+        final ResponsePromise<EtcdKeysResponse> responseResponsePromise1 = mock(ResponsePromise.class);
+        when(request1.send()).thenReturn(promise1);
+        when(promise1.get()).thenReturn(response1);
+        when(responseResponsePromise1.get()).thenReturn(response1);
+
+        final EtcdKeysResponse.EtcdNode node1 = mock(EtcdKeysResponse.EtcdNode.class);
+        when(response1.getNode()).thenReturn(node1);
+        when(node1.getKey()).thenReturn("/skywalking/testKey");
+        when(node1.getValue()).thenReturn("testVal");
+
+        final ConfigTable configTable = mockRegister.readConfig(Sets.newHashSet(testKey1, testKey2));
+
+        assertEquals(2, configTable.getItems().size());
+        Map<String, String> kvs = new HashMap<>();
+        for (ConfigTable.ConfigItem item : configTable.getItems()) {
+            kvs.put(item.getName(), item.getValue());
+        }
+        assertEquals(testVal1, kvs.get(testKey1));
+        assertEquals(testVal2, kvs.get(testKey2));
+    }
+}
diff --git a/oap-server/server-configuration/configuration-etcd/src/test/java/org/apache/skywalking/oap/server/configuration/etcd/EtcdConfigurationTestModule.java b/oap-server/server-configuration/configuration-etcd/src/test/java/org/apache/skywalking/oap/server/configuration/etcd/EtcdConfigurationTestModule.java
new file mode 100644
index 0000000..c03fae4
--- /dev/null
+++ b/oap-server/server-configuration/configuration-etcd/src/test/java/org/apache/skywalking/oap/server/configuration/etcd/EtcdConfigurationTestModule.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.skywalking.oap.server.configuration.etcd;
+
+import org.apache.skywalking.oap.server.library.module.ModuleDefine;
+
+/**
+ * @author Alan Lau
+ */
+public class EtcdConfigurationTestModule extends ModuleDefine {
+
+    public static final String NAME = "test-module";
+
+    public EtcdConfigurationTestModule() {
+        super(NAME);
+    }
+
+    @Override public Class[] services() {
+        return new Class[0];
+    }
+}
diff --git a/oap-server/server-configuration/configuration-etcd/src/test/java/org/apache/skywalking/oap/server/configuration/etcd/EtcdConfigurationTestProvider.java b/oap-server/server-configuration/configuration-etcd/src/test/java/org/apache/skywalking/oap/server/configuration/etcd/EtcdConfigurationTestProvider.java
new file mode 100644
index 0000000..93d5496
--- /dev/null
+++ b/oap-server/server-configuration/configuration-etcd/src/test/java/org/apache/skywalking/oap/server/configuration/etcd/EtcdConfigurationTestProvider.java
@@ -0,0 +1,89 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.skywalking.oap.server.configuration.etcd;
+
+import org.apache.skywalking.oap.server.configuration.api.ConfigChangeWatcher;
+import org.apache.skywalking.oap.server.configuration.api.ConfigurationModule;
+import org.apache.skywalking.oap.server.configuration.api.DynamicConfigurationService;
+import org.apache.skywalking.oap.server.library.module.ModuleConfig;
+import org.apache.skywalking.oap.server.library.module.ModuleDefine;
+import org.apache.skywalking.oap.server.library.module.ModuleProvider;
+import org.apache.skywalking.oap.server.library.module.ModuleStartException;
+import org.apache.skywalking.oap.server.library.module.ServiceNotProvidedException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * @author Alan Lau
+ */
+public class EtcdConfigurationTestProvider extends ModuleProvider {
+
+    private final static Logger LOGGER = LoggerFactory.getLogger(EtcdConfigurationTestProvider.class);
+
+    ConfigChangeWatcher watcher;
+
+    @Override public String name() {
+        return "default";
+    }
+
+    @Override public Class<? extends ModuleDefine> module() {
+        return EtcdConfigurationTestModule.class;
+    }
+
+    @Override public ModuleConfig createConfigBeanIfAbsent() {
+        return new ModuleConfig() {
+        };
+    }
+
+    @Override public void prepare() throws ServiceNotProvidedException, ModuleStartException {
+        watcher = new ConfigChangeWatcher(EtcdConfigurationTestModule.NAME, this, "testKey") {
+            private volatile String testValue;
+
+            @Override
+            public void notify(ConfigChangeWatcher.ConfigChangeEvent value) {
+                LOGGER.info("ConfigChangeWatcher.ConfigChangeEvent: {}", value);
+                if (EventType.DELETE.equals(value.getEventType())) {
+                    testValue = null;
+                } else {
+                    testValue = value.getNewValue();
+                }
+            }
+
+            @Override
+            public String value() {
+                return testValue;
+            }
+        };
+    }
+
+    @Override public void start() throws ServiceNotProvidedException, ModuleStartException {
+        getManager().find(ConfigurationModule.NAME)
+            .provider()
+            .getService(DynamicConfigurationService.class)
+            .registerConfigChangeWatcher(watcher);
+    }
+
+    @Override public void notifyAfterCompleted() throws ServiceNotProvidedException, ModuleStartException {
+
+    }
+
+    @Override public String[] requiredModules() {
+        return new String[0];
+    }
+}
diff --git a/oap-server/server-configuration/configuration-etcd/src/test/java/org/apache/skywalking/oap/server/configuration/etcd/ITEtcdConfigurationTest.java b/oap-server/server-configuration/configuration-etcd/src/test/java/org/apache/skywalking/oap/server/configuration/etcd/ITEtcdConfigurationTest.java
new file mode 100644
index 0000000..c157652
--- /dev/null
+++ b/oap-server/server-configuration/configuration-etcd/src/test/java/org/apache/skywalking/oap/server/configuration/etcd/ITEtcdConfigurationTest.java
@@ -0,0 +1,153 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.skywalking.oap.server.configuration.etcd;
+
+import java.io.FileNotFoundException;
+import java.io.Reader;
+import java.net.URI;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import mousio.etcd4j.EtcdClient;
+import mousio.etcd4j.promises.EtcdResponsePromise;
+import mousio.etcd4j.responses.EtcdKeysResponse;
+import org.apache.skywalking.apm.util.PropertyPlaceholderHelper;
+import org.apache.skywalking.oap.server.library.module.ApplicationConfiguration;
+import org.apache.skywalking.oap.server.library.module.ModuleManager;
+import org.apache.skywalking.oap.server.library.util.CollectionUtils;
+import org.apache.skywalking.oap.server.library.util.ResourceUtils;
+import org.junit.Before;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.yaml.snakeyaml.Yaml;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author Alan Lau
+ */
+public class ITEtcdConfigurationTest {
+
+    private static final Logger logger = LoggerFactory.getLogger(ITEtcdConfigurationTest.class);
+
+    private final Yaml yaml = new Yaml();
+
+    private EtcdServerSettings settings;
+
+    private EtcdConfigurationTestProvider provider;
+
+    private EtcdClient client;
+
+    @Before
+    public void setUp() throws Exception {
+        final ApplicationConfiguration applicationConfiguration = new ApplicationConfiguration();
+        loadConfig(applicationConfiguration);
+
+        final ModuleManager moduleManager = new ModuleManager();
+        moduleManager.init(applicationConfiguration);
+
+        final String etcdHost = System.getProperty("etcd.host");
+        final String etcdPort = System.getProperty("etcd.port");
+        logger.info("etcdHost: {}, etcdPort: {}", etcdHost, etcdPort);
+        Properties properties = new Properties();
+        properties.setProperty("serverAddr", etcdHost + ":" + etcdPort);
+
+        List<URI> uris = EtcdUtils.parseProp(properties);
+        client = new EtcdClient(uris.toArray(new URI[] {}));
+
+        provider =
+            (EtcdConfigurationTestProvider)moduleManager
+                .find(EtcdConfigurationTestModule.NAME)
+                .provider();
+
+        assertNotNull(provider);
+    }
+
+    @Test(timeout = 20000)
+    public void shouldReadUpdated() throws Exception {
+        assertNull(provider.watcher.value());
+
+        assertTrue(publishConfig("test-module.default.testKey", "skywalking", "500"));
+
+        for (String v = provider.watcher.value(); v == null; v = provider.watcher.value()) {
+            logger.info("value is : {}", provider.watcher.value());
+        }
+
+        assertEquals("500", provider.watcher.value());
+
+        assertTrue(removeConfig("test-module.default.testKey", "skywalking"));
+
+        for (String v = provider.watcher.value(); v != null; v = provider.watcher.value()) {
+        }
+
+        assertNull(provider.watcher.value());
+    }
+
+    @SuppressWarnings("unchecked")
+    private void loadConfig(ApplicationConfiguration configuration) throws FileNotFoundException {
+        Reader applicationReader = ResourceUtils.read("application.yml");
+        Map<String, Map<String, Map<String, ?>>> moduleConfig = yaml.loadAs(applicationReader, Map.class);
+        if (CollectionUtils.isNotEmpty(moduleConfig)) {
+            moduleConfig.forEach((moduleName, providerConfig) -> {
+                if (providerConfig.size() > 0) {
+                    ApplicationConfiguration.ModuleConfiguration moduleConfiguration = configuration.addModule(moduleName);
+                    providerConfig.forEach((name, propertiesConfig) -> {
+                        Properties properties = new Properties();
+                        if (propertiesConfig != null) {
+                            propertiesConfig.forEach((key, value) -> {
+                                properties.put(key, value);
+                                final Object replaceValue = yaml.load(PropertyPlaceholderHelper.INSTANCE
+                                    .replacePlaceholders(value + "", properties));
+                                if (replaceValue != null) {
+                                    properties.replace(key, replaceValue);
+                                }
+                            });
+                        }
+                        moduleConfiguration.addProviderConfiguration(name, properties);
+                    });
+                }
+            });
+        }
+    }
+
+    private boolean publishConfig(String key, String group, String value) {
+        try {
+            client.putDir(group).send().get();
+            EtcdResponsePromise<EtcdKeysResponse> promise = client.put(generateKey(key, group), value).send();
+            promise.get();
+            return true;
+        } catch (Exception e) {
+            return false;
+        }
+    }
+
+    private boolean removeConfig(String key, String group) throws Exception {
+        client.delete(generateKey(key, group)).send().get();
+        return true;
+    }
+
+    private String generateKey(String key, String group) {
+        return new StringBuilder("/").append(group).append("/").append(key).toString();
+    }
+
+}
diff --git a/oap-server/server-configuration/configuration-etcd/src/test/java/org/apache/skywalking/oap/server/configuration/etcd/TestEtcdUtils.java b/oap-server/server-configuration/configuration-etcd/src/test/java/org/apache/skywalking/oap/server/configuration/etcd/TestEtcdUtils.java
new file mode 100644
index 0000000..0977fab
--- /dev/null
+++ b/oap-server/server-configuration/configuration-etcd/src/test/java/org/apache/skywalking/oap/server/configuration/etcd/TestEtcdUtils.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.skywalking.oap.server.configuration.etcd;
+
+import java.net.URI;
+import java.util.List;
+import java.util.Properties;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * @author Alan Lau
+ */
+public class TestEtcdUtils {
+
+    private EtcdServerSettings settings;
+
+    private Properties properties;
+
+    @Before
+    public void setUp() {
+        settings = new EtcdServerSettings();
+        settings.setServerAddr("localhost:2379");
+        properties = new Properties();
+        properties.setProperty("serverAddr", "localhost:2379");
+    }
+
+    @Test
+    public void testParse() {
+        List<URI> list = EtcdUtils.parse(settings);
+        Assert.assertEquals(1, list.size());
+        URI uri = list.get(0);
+        Assert.assertEquals("http", uri.getScheme());
+        Assert.assertEquals("localhost", uri.getHost());
+        Assert.assertEquals(2379, uri.getPort());
+    }
+
+    @Test
+    public void testProp() {
+        List<URI> list = EtcdUtils.parseProp(properties);
+        Assert.assertEquals(1, list.size());
+        URI uri = list.get(0);
+        Assert.assertEquals("http", uri.getScheme());
+        Assert.assertEquals("localhost", uri.getHost());
+        Assert.assertEquals(2379, uri.getPort());
+    }
+}
diff --git a/oap-server/server-configuration/configuration-etcd/src/test/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleDefine b/oap-server/server-configuration/configuration-etcd/src/test/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleDefine
new file mode 100644
index 0000000..cbbb641
--- /dev/null
+++ b/oap-server/server-configuration/configuration-etcd/src/test/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleDefine
@@ -0,0 +1,20 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+#
+
+org.apache.skywalking.oap.server.configuration.api.ConfigurationModule
+org.apache.skywalking.oap.server.configuration.etcd.EtcdConfigurationTestModule
diff --git a/oap-server/server-configuration/configuration-etcd/src/test/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleProvider b/oap-server/server-configuration/configuration-etcd/src/test/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleProvider
new file mode 100644
index 0000000..0afae5f
--- /dev/null
+++ b/oap-server/server-configuration/configuration-etcd/src/test/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleProvider
@@ -0,0 +1,19 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+#
+
+org.apache.skywalking.oap.server.configuration.etcd.EtcdConfigurationTestProvider
diff --git a/oap-server/server-configuration/configuration-etcd/src/test/resources/application.yml b/oap-server/server-configuration/configuration-etcd/src/test/resources/application.yml
new file mode 100644
index 0000000..be3009c
--- /dev/null
+++ b/oap-server/server-configuration/configuration-etcd/src/test/resources/application.yml
@@ -0,0 +1,33 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+test-module:
+  default:
+    testKey: 300
+
+
+configuration:
+  etcd:
+    # Etcd Server Host
+    serverAddr: ${etcd.host}:${etcd.port}
+    # Etcd Server Port
+    port: ${etcd.port}
+    # Etcd Configuration Group
+    group: 'skywalking'
+    # Unit seconds, sync period. Default fetch every 60 seconds.
+    period: 1
+    # the name of current cluster, set the name if you want to upstream system known.
+    clusterName: "default"
diff --git a/oap-server/server-configuration/pom.xml b/oap-server/server-configuration/configuration-etcd/src/test/resources/log4j2.xml
similarity index 53%
copy from oap-server/server-configuration/pom.xml
copy to oap-server/server-configuration/configuration-etcd/src/test/resources/log4j2.xml
index 5d89c4e..c9eec4f 100644
--- a/oap-server/server-configuration/pom.xml
+++ b/oap-server/server-configuration/configuration-etcd/src/test/resources/log4j2.xml
@@ -17,22 +17,15 @@
   ~
   -->
 
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
-    <parent>
-        <artifactId>oap-server</artifactId>
-        <groupId>org.apache.skywalking</groupId>
-        <version>6.3.0-SNAPSHOT</version>
-    </parent>
-    <modelVersion>4.0.0</modelVersion>
-
-    <artifactId>server-configuration</artifactId>
-    <packaging>pom</packaging>
-    <modules>
-        <module>configuration-api</module>
-        <module>grpc-configuration-sync</module>
-        <module>configuration-apollo</module>
-        <module>configuration-nacos</module>
-        <module>configuration-zookeeper</module>
-    </modules>
-
-</project>
+<Configuration status="info">
+    <Appenders>
+        <Console name="Console" target="SYSTEM_OUT">
+            <PatternLayout charset="UTF-8" pattern="%d - %c -%-4r [%t] %-5p %x - %m%n"/>
+        </Console>
+    </Appenders>
+    <Loggers>
+        <Root level="info">
+            <AppenderRef ref="Console"/>
+        </Root>
+    </Loggers>
+</Configuration>
diff --git a/oap-server/server-configuration/pom.xml b/oap-server/server-configuration/pom.xml
index 5d89c4e..be0196f 100644
--- a/oap-server/server-configuration/pom.xml
+++ b/oap-server/server-configuration/pom.xml
@@ -33,6 +33,7 @@
         <module>configuration-apollo</module>
         <module>configuration-nacos</module>
         <module>configuration-zookeeper</module>
+        <module>configuration-etcd</module>
     </modules>
 
 </project>
diff --git a/oap-server/server-starter/pom.xml b/oap-server/server-starter/pom.xml
index cd9b471..5eb911e 100644
--- a/oap-server/server-starter/pom.xml
+++ b/oap-server/server-starter/pom.xml
@@ -203,6 +203,11 @@
             <artifactId>configuration-zookeeper</artifactId>
             <version>${project.version}</version>
         </dependency>
+        <dependency>
+            <groupId>org.apache.skywalking</groupId>
+            <artifactId>configuration-etcd</artifactId>
+            <version>${project.version}</version>
+        </dependency>
     </dependencies>
     <build>
         <finalName>skywalking-oap</finalName>
diff --git a/oap-server/server-starter/src/main/assembly/application.yml b/oap-server/server-starter/src/main/assembly/application.yml
index e45666c..50d7ecf 100644
--- a/oap-server/server-starter/src/main/assembly/application.yml
+++ b/oap-server/server-starter/src/main/assembly/application.yml
@@ -149,6 +149,11 @@ configuration:
 #    #Retry Policy
 #    baseSleepTimeMs: 1000 # initial amount of time to wait between retries
 #    maxRetries: 3 # max number of times to retry
+#  etcd:
+#    period : 60 # Unit seconds, sync period. Default fetch every 60 seconds.
+#    group :  'skywalking'
+#    serverAddr: localhost:2379
+#    clusterName: "default"
 #exporter:
 #  grpc:
 #    targetHost: ${SW_EXPORTER_GRPC_HOST:127.0.0.1}
diff --git a/oap-server/server-starter/src/main/resources/application.yml b/oap-server/server-starter/src/main/resources/application.yml
index 85a5b8b..bb4fc85 100644
--- a/oap-server/server-starter/src/main/resources/application.yml
+++ b/oap-server/server-starter/src/main/resources/application.yml
@@ -168,6 +168,11 @@ configuration:
 #    #Retry Policy
 #    baseSleepTimeMs: 1000 # initial amount of time to wait between retries
 #    maxRetries: 3 # max number of times to retry
+#  etcd:
+#    period : 60 # Unit seconds, sync period. Default fetch every 60 seconds.
+#    group :  'skywalking'
+#    serverAddr: localhost:2379
+#    clusterName: "default"
 
 #exporter:
 #  grpc: