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

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


The following commit(s) were added to refs/heads/master by this push:
     new 5a630b878 feat: support multiple kubernets clusters discovery (#7895)
5a630b878 is described below

commit 5a630b878e01c1fc9efdfe3661141658e4f3dc42
Author: zhixiongdu <[email protected]>
AuthorDate: Thu Sep 15 11:25:37 2022 +0800

    feat: support multiple kubernets clusters discovery (#7895)
---
 .github/workflows/kubernetes-ci.yml                |  19 +-
 apisix/cli/ngx_tpl.lua                             |   7 +-
 apisix/cli/ops.lua                                 |  57 +-
 apisix/discovery/kubernetes/init.lua               | 277 ++++++--
 apisix/discovery/kubernetes/schema.lua             | 254 ++++---
 t/APISIX.pm                                        |   2 +
 t/cli/test_kubernetes.sh                           |  81 ++-
 t/kubernetes/discovery/kubernetes.t                | 751 +++++----------------
 .../discovery/{kubernetes.t => kubernetes2.t}      | 411 +++++------
 .../discovery/{kubernetes.t => kubernetes3.t}      | 411 +++++------
 10 files changed, 1082 insertions(+), 1188 deletions(-)

diff --git a/.github/workflows/kubernetes-ci.yml 
b/.github/workflows/kubernetes-ci.yml
index 9800e681f..b6495d765 100644
--- a/.github/workflows/kubernetes-ci.yml
+++ b/.github/workflows/kubernetes-ci.yml
@@ -44,22 +44,17 @@ jobs:
 
       - name: Setup kubernetes cluster
         run: |
-          KIND_VERSION="v0.11.1"
-          KUBECTL_VERSION="v1.22.0"
-          curl -Lo ./kind 
"https://kind.sigs.k8s.io/dl/${KIND_VERSION}/kind-$(uname)-amd64"
-          curl -Lo ./kubectl 
"https://dl.k8s.io/release/${KUBECTL_VERSION}/bin/linux/amd64/kubectl";
-          chmod +x ./kind
-          chmod +x ./kubectl
+          KUBERNETES_VERSION="v1.22.7"
 
-          ./kind create cluster --name apisix-test --config 
./t/kubernetes/configs/kind.yaml
+          kind create cluster --name apisix-test --config 
./t/kubernetes/configs/kind.yaml --image kindest/node:${KUBERNETES_VERSION}
 
-          ./kubectl wait --for=condition=Ready nodes --all --timeout=180s
+          kubectl wait --for=condition=Ready nodes --all --timeout=180s
 
-          ./kubectl apply -f ./t/kubernetes/configs/account.yaml
+          kubectl apply -f ./t/kubernetes/configs/account.yaml
 
-          ./kubectl apply -f ./t/kubernetes/configs/endpoint.yaml
+          kubectl apply -f ./t/kubernetes/configs/endpoint.yaml
 
-          KUBERNETES_CLIENT_TOKEN_CONTENT=$(./kubectl get secrets | grep 
apisix-test | awk '{system("./kubectl get secret -o jsonpath={.data.token} "$1" 
| base64 --decode")}')
+          KUBERNETES_CLIENT_TOKEN_CONTENT=$(kubectl get secrets | grep 
apisix-test | awk '{system("kubectl get secret -o jsonpath={.data.token} "$1" | 
base64 --decode")}')
 
           
KUBERNETES_CLIENT_TOKEN_DIR="/tmp/var/run/secrets/kubernetes.io/serviceaccount"
 
@@ -73,7 +68,7 @@ jobs:
           echo 'KUBERNETES_CLIENT_TOKEN='"${KUBERNETES_CLIENT_TOKEN_CONTENT}"
           echo 'KUBERNETES_CLIENT_TOKEN_FILE='${KUBERNETES_CLIENT_TOKEN_FILE}
 
-          ./kubectl proxy -p 6445 &
+          kubectl proxy -p 6445 &
 
       - name: Linux Install
         run: |
diff --git a/apisix/cli/ngx_tpl.lua b/apisix/cli/ngx_tpl.lua
index 5d814a091..4d1bc5a6b 100644
--- a/apisix/cli/ngx_tpl.lua
+++ b/apisix/cli/ngx_tpl.lua
@@ -238,8 +238,11 @@ http {
     lua_shared_dict balancer-ewma-last-touched-at {* 
http.lua_shared_dict["balancer-ewma-last-touched-at"] *};
     lua_shared_dict etcd-cluster-health-check {* 
http.lua_shared_dict["etcd-cluster-health-check"] *}; # etcd health check
 
-    {% if enabled_discoveries["kubernetes"] then %}
-    lua_shared_dict kubernetes {* http.lua_shared_dict["kubernetes"] *};
+    # for discovery shared dict
+    {% if discovery_shared_dicts then %}
+    {% for key, size in pairs(discovery_shared_dicts) do %}
+    lua_shared_dict {*key*} {*size*};
+    {% end %}
     {% end %}
 
     {% if enabled_discoveries["tars"] then %}
diff --git a/apisix/cli/ops.lua b/apisix/cli/ops.lua
index a362d111c..d00ce072d 100644
--- a/apisix/cli/ops.lua
+++ b/apisix/cli/ops.lua
@@ -662,36 +662,52 @@ Please modify "admin_key" in conf/config.yaml .
         end
     end
 
-    -- inject kubernetes discovery environment variable
+    -- inject kubernetes discovery shared dict and environment variable
     if enabled_discoveries["kubernetes"] then
 
-        local kubernetes_conf = yaml_conf.discovery["kubernetes"]
+        if not sys_conf["discovery_shared_dicts"] then
+            sys_conf["discovery_shared_dicts"] = {}
+        end
 
-        local keys = {
-            kubernetes_conf.service.host,
-            kubernetes_conf.service.port,
-        }
+        local kubernetes_conf = yaml_conf.discovery["kubernetes"]
 
-        if kubernetes_conf.client.token then
-            table_insert(keys, kubernetes_conf.client.token)
-        end
+        local inject_environment = function(conf, envs)
+            local keys = {
+                conf.service.host,
+                conf.service.port,
+            }
 
-        if kubernetes_conf.client.token_file then
-            table_insert(keys, kubernetes_conf.client.token_file)
-        end
+            if conf.client.token then
+                table_insert(keys, conf.client.token)
+            end
 
-        local envs = {}
+            if conf.client.token_file then
+                table_insert(keys, conf.client.token_file)
+            end
 
-        for _, key in ipairs(keys) do
-            if #key > 3 then
-                local first, second = str_byte(key, 1, 2)
-                if first == str_byte('$') and second == str_byte('{') then
-                    local last = str_byte(key, #key)
-                    if last == str_byte('}') then
-                        envs[str_sub(key, 3, #key - 1)] = ""
+            for _, key in ipairs(keys) do
+                if #key > 3 then
+                    local first, second = str_byte(key, 1, 2)
+                    if first == str_byte('$') and second == str_byte('{') then
+                        local last = str_byte(key, #key)
+                        if last == str_byte('}') then
+                            envs[str_sub(key, 3, #key - 1)] = ""
+                        end
                     end
                 end
             end
+
+        end
+
+        local envs = {}
+        if #kubernetes_conf == 0 then
+            sys_conf["discovery_shared_dicts"]["kubernetes"] = 
kubernetes_conf.shared_size
+            inject_environment(kubernetes_conf, envs)
+        else
+            for _, item in ipairs(kubernetes_conf) do
+                sys_conf["discovery_shared_dicts"]["kubernetes-" .. item.id] = 
item.shared_size
+                inject_environment(item, envs)
+            end
         end
 
         if not sys_conf["envs"] then
@@ -701,6 +717,7 @@ Please modify "admin_key" in conf/config.yaml .
         for item in pairs(envs) do
             table_insert(sys_conf["envs"], item)
         end
+
     end
 
     -- fix up lua path
diff --git a/apisix/discovery/kubernetes/init.lua 
b/apisix/discovery/kubernetes/init.lua
index a0491be45..d7258a556 100644
--- a/apisix/discovery/kubernetes/init.lua
+++ b/apisix/discovery/kubernetes/init.lua
@@ -24,15 +24,15 @@ local tostring = tostring
 local os = os
 local error = error
 local pcall = pcall
+local setmetatable = setmetatable
 local process = require("ngx.process")
 local core = require("apisix.core")
 local util = require("apisix.cli.util")
 local local_conf = require("apisix.core.config_local").local_conf()
 local informer_factory = 
require("apisix.discovery.kubernetes.informer_factory")
 
-local endpoint_dict
 
-local default_weight
+local ctx
 
 local endpoint_lrucache = core.lrucache.new({
     ttl = 300,
@@ -50,9 +50,9 @@ local function sort_nodes_cmp(left, right)
 end
 
 
-local function on_endpoint_modified(informer, endpoint)
-    if informer.namespace_selector and
-            not informer:namespace_selector(endpoint.metadata.namespace) then
+local function on_endpoint_modified(handle, endpoint)
+    if handle.namespace_selector and
+            not handle:namespace_selector(endpoint.metadata.namespace) then
         return
     end
 
@@ -83,7 +83,7 @@ local function on_endpoint_modified(informer, endpoint)
                     core.table.insert(nodes, {
                         host = address.ip,
                         port = port.port,
-                        weight = default_weight
+                        weight = handle.default_weight
                     })
                 end
             end
@@ -101,39 +101,39 @@ local function on_endpoint_modified(informer, endpoint)
     local endpoint_version = ngx.crc32_long(endpoint_content)
 
     local _, err
-    _, err = endpoint_dict:safe_set(endpoint_key .. "#version", 
endpoint_version)
+    _, err = handle.endpoint_dict:safe_set(endpoint_key .. "#version", 
endpoint_version)
     if err then
         core.log.error("set endpoint version into discovery DICT failed, ", 
err)
         return
     end
-    _, err = endpoint_dict:safe_set(endpoint_key, endpoint_content)
+    _, err = handle.endpoint_dict:safe_set(endpoint_key, endpoint_content)
     if err then
         core.log.error("set endpoint into discovery DICT failed, ", err)
-        endpoint_dict:delete(endpoint_key .. "#version")
+        handle.endpoint_dict:delete(endpoint_key .. "#version")
     end
 end
 
 
-local function on_endpoint_deleted(informer, endpoint)
-    if informer.namespace_selector and
-            not informer:namespace_selector(endpoint.metadata.namespace) then
+local function on_endpoint_deleted(handle, endpoint)
+    if handle.namespace_selector and
+            not handle:namespace_selector(endpoint.metadata.namespace) then
         return
     end
 
     core.log.debug(core.json.delay_encode(endpoint))
     local endpoint_key = endpoint.metadata.namespace .. "/" .. 
endpoint.metadata.name
-    endpoint_dict:delete(endpoint_key .. "#version")
-    endpoint_dict:delete(endpoint_key)
+    handle.endpoint_dict:delete(endpoint_key .. "#version")
+    handle.endpoint_dict:delete(endpoint_key)
 end
 
 
-local function pre_list(informer)
-    endpoint_dict:flush_all()
+local function pre_list(handle)
+    handle.endpoint_dict:flush_all()
 end
 
 
-local function post_list(informer)
-    endpoint_dict:flush_expired()
+local function post_list(handle)
+    handle.endpoint_dict:flush_expired()
 end
 
 
@@ -184,7 +184,7 @@ local function setup_namespace_selector(conf, informer)
             local not_match = conf.namespace_selector.not_match
             local m, err
             for _, v in ipairs(not_match) do
-                m, err = ngx.re.match(namespace, v, "j")
+                m, err = ngx.re.match(namespace, v, "jo")
                 if m and m[0] == namespace then
                     return false
                 end
@@ -196,24 +196,26 @@ local function setup_namespace_selector(conf, informer)
         end
         return
     end
+
+    return
 end
 
 
 local function read_env(key)
     if #key > 3 then
-        local a, b = string.byte(key, 1, 2)
-        local c = string.byte(key, #key, #key)
-        -- '$', '{', '}' == 36,123,125
-        if a == 36 and b == 123 and c == 125 then
-            local env = string.sub(key, 3, #key - 1)
-            local value = os.getenv(env)
-            if not value then
-                return nil, "not found environment variable " .. env
+        local first, second = string.byte(key, 1, 2)
+        if first == string.byte('$') and second == string.byte('{') then
+            local last = string.byte(key, #key)
+            if last == string.byte('}') then
+                local env = string.sub(key, 3, #key - 1)
+                local value = os.getenv(env)
+                if not value then
+                    return nil, "not found environment variable " .. env
+                end
+                return value
             end
-            return value, nil
         end
     end
-
     return key
 end
 
@@ -272,6 +274,9 @@ local function get_apiserver(conf)
         return nil, "one of [client.token,client.token_file] should be set but 
none"
     end
 
+    -- remove possible extra whitespace
+    apiserver.token = apiserver.token:gsub("%s+", "")
+
     if apiserver.schema == "https" and apiserver.token == "" then
         return nil, "apiserver.token should set to non-empty string when 
service.schema is https"
     end
@@ -279,8 +284,7 @@ local function get_apiserver(conf)
     return apiserver
 end
 
-
-local function create_endpoint_lrucache(endpoint_key, endpoint_port)
+local function create_endpoint_lrucache(endpoint_dict, endpoint_key, 
endpoint_port)
     local endpoint_content = endpoint_dict:get_stale(endpoint_key)
     if not endpoint_content then
         core.log.error("get empty endpoint content from discovery DIC, this 
should not happen ",
@@ -298,60 +302,64 @@ local function create_endpoint_lrucache(endpoint_key, 
endpoint_port)
     return endpoint[endpoint_port]
 end
 
+
 local _M = {
     version = "0.0.1"
 }
 
-function _M.nodes(service_name)
-    local pattern = "^(.*):(.*)$"  -- namespace/name:port_name
-    local match = ngx.re.match(service_name, pattern, "jo")
-    if not match then
-        core.log.info("get unexpected upstream service_name: ", service_name)
-        return nil
-    end
 
-    local endpoint_key = match[1]
-    local endpoint_port = match[2]
-    local endpoint_version = endpoint_dict:get_stale(endpoint_key .. 
"#version")
-    if not endpoint_version then
-        core.log.info("get empty endpoint version from discovery DICT ", 
endpoint_key)
-        return nil
-    end
+local function start_fetch(handle)
+    local timer_runner
+    timer_runner = function(premature)
+        if premature then
+            return
+        end
 
-    return endpoint_lrucache(service_name, endpoint_version,
-            create_endpoint_lrucache, endpoint_key, endpoint_port)
+        local ok, status = pcall(handle.list_watch, handle, handle.apiserver)
+
+        local retry_interval = 0
+        if not ok then
+            core.log.error("list_watch failed, kind: ", handle.kind,
+                    ", reason: ", "RuntimeException", ", message : ", status)
+            retry_interval = 40
+        elseif not status then
+            retry_interval = 40
+        end
+
+        ngx.timer.at(retry_interval, timer_runner)
+    end
+    ngx.timer.at(0, timer_runner)
 end
 
 
-function _M.init_worker()
-    endpoint_dict = ngx.shared.kubernetes
+local function single_mode_init(conf)
+    local endpoint_dict = ngx.shared.kubernetes
     if not endpoint_dict then
-        error("failed to get lua_shared_dict: kubernetes, please check your 
APISIX version")
+        error("failed to get lua_shared_dict: ngx.shared.kubernetes, " ..
+                "please check your APISIX version")
     end
 
     if process.type() ~= "privileged agent" then
+        ctx = endpoint_dict
         return
     end
 
-    local discovery_conf = local_conf.discovery.kubernetes
-
-    default_weight = discovery_conf.default_weight
-
-    local apiserver, err = get_apiserver(discovery_conf)
+    local apiserver, err = get_apiserver(conf)
     if err then
         error(err)
         return
     end
 
-    local endpoints_informer, err = informer_factory.new("", "v1",
-            "Endpoints", "endpoints", "")
+    local default_weight = conf.default_weight
+
+    local endpoints_informer, err = informer_factory.new("", "v1", 
"Endpoints", "endpoints", "")
     if err then
         error(err)
         return
     end
 
-    setup_namespace_selector(discovery_conf, endpoints_informer)
-    setup_label_selector(discovery_conf, endpoints_informer)
+    setup_namespace_selector(conf, endpoints_informer)
+    setup_label_selector(conf, endpoints_informer)
 
     endpoints_informer.on_added = on_endpoint_modified
     endpoints_informer.on_modified = on_endpoint_modified
@@ -359,27 +367,152 @@ function _M.init_worker()
     endpoints_informer.pre_list = pre_list
     endpoints_informer.post_list = post_list
 
-    local timer_runner
-    timer_runner = function(premature)
-        if premature then
+    ctx = setmetatable({
+        endpoint_dict = endpoint_dict,
+        apiserver = apiserver,
+        default_weight = default_weight
+    }, { __index = endpoints_informer })
+
+    start_fetch(ctx)
+end
+
+
+local function single_mode_nodes(service_name)
+    local pattern = "^(.*):(.*)$" -- namespace/name:port_name
+    local match = ngx.re.match(service_name, pattern, "jo")
+    if not match then
+        core.log.error("get unexpected upstream service_name: ", service_name)
+        return nil
+    end
+
+    local endpoint_dict = ctx
+    local endpoint_key = match[1]
+    local endpoint_port = match[2]
+    local endpoint_version = endpoint_dict:get_stale(endpoint_key .. 
"#version")
+    if not endpoint_version then
+        core.log.info("get empty endpoint version from discovery DICT ", 
endpoint_key)
+        return nil
+    end
+
+    return endpoint_lrucache(service_name, endpoint_version,
+            create_endpoint_lrucache, endpoint_dict, endpoint_key, 
endpoint_port)
+end
+
+
+local function multiple_mode_worker_init(confs)
+    for _, conf in ipairs(confs) do
+
+        local id = conf.id
+        if ctx[id] then
+            error("duplicate id value")
+        end
+
+        local endpoint_dict = ngx.shared["kubernetes-" .. id]
+        if not endpoint_dict then
+            error(string.format("failed to get lua_shared_dict: 
ngx.shared.kubernetes-%s, ", id) ..
+                    "please check your APISIX version")
+        end
+
+        ctx[id] = endpoint_dict
+    end
+end
+
+
+local function multiple_mode_init(confs)
+    ctx = core.table.new(#confs, 0)
+
+    if process.type() ~= "privileged agent" then
+        multiple_mode_worker_init(confs)
+        return
+    end
+
+    for _, conf in ipairs(confs) do
+        local id = conf.id
+
+        if ctx[id] then
+            error("duplicate id value")
+        end
+
+        local endpoint_dict = ngx.shared["kubernetes-" .. id]
+        if not endpoint_dict then
+            error(string.format("failed to get lua_shared_dict: 
ngx.shared.kubernetes-%s, ", id) ..
+                    "please check your APISIX version")
+        end
+
+        local apiserver, err = get_apiserver(conf)
+        if err then
+            error(err)
             return
         end
 
-        local ok, status = pcall(endpoints_informer.list_watch, 
endpoints_informer, apiserver)
+        local default_weight = conf.default_weight
 
-        local retry_interval = 0
-        if not ok then
-            core.log.error("list_watch failed, kind: ", 
endpoints_informer.kind,
-                    ", reason: ", "RuntimeException", ", message : ", status)
-            retry_interval = 40
-        elseif not status then
-            retry_interval = 40
+        local endpoints_informer, err = informer_factory.new("", "v1", 
"Endpoints", "endpoints", "")
+        if err then
+            error(err)
+            return
         end
 
-        ngx.timer.at(retry_interval, timer_runner)
+        setup_namespace_selector(conf, endpoints_informer)
+        setup_label_selector(conf, endpoints_informer)
+
+        endpoints_informer.on_added = on_endpoint_modified
+        endpoints_informer.on_modified = on_endpoint_modified
+        endpoints_informer.on_deleted = on_endpoint_deleted
+        endpoints_informer.pre_list = pre_list
+        endpoints_informer.post_list = post_list
+
+        ctx[id] = setmetatable({
+            endpoint_dict = endpoint_dict,
+            apiserver = apiserver,
+            default_weight = default_weight
+        }, { __index = endpoints_informer })
     end
 
-    ngx.timer.at(0, timer_runner)
+    for _, item in pairs(ctx) do
+        start_fetch(item)
+    end
+end
+
+
+local function multiple_mode_nodes(service_name)
+    local pattern = "^(.*)/(.*/.*):(.*)$" -- id/namespace/name:port_name
+    local match = ngx.re.match(service_name, pattern, "jo")
+    if not match then
+        core.log.error("get unexpected upstream service_name: ", service_name)
+        return nil
+    end
+
+    local id = match[1]
+    local endpoint_dict = ctx[id]
+    if not endpoint_dict then
+        core.log.error("id not exist")
+        return nil
+    end
+
+    local endpoint_key = match[2]
+    local endpoint_port = match[3]
+    local endpoint_version = endpoint_dict:get_stale(endpoint_key .. 
"#version")
+    if not endpoint_version then
+        core.log.info("get empty endpoint version from discovery DICT ", 
endpoint_key)
+        return nil
+    end
+
+    return endpoint_lrucache(service_name, endpoint_version,
+            create_endpoint_lrucache, endpoint_dict, endpoint_key, 
endpoint_port)
+end
+
+
+function _M.init_worker()
+    local discovery_conf = local_conf.discovery.kubernetes
+    core.log.info("kubernetes discovery conf: ", 
core.json.delay_encode(discovery_conf))
+    if #discovery_conf == 0 then
+        _M.nodes = single_mode_nodes
+        single_mode_init(discovery_conf)
+    else
+        _M.nodes = multiple_mode_nodes
+        multiple_mode_init(discovery_conf)
+    end
 end
 
 return _M
diff --git a/apisix/discovery/kubernetes/schema.lua 
b/apisix/discovery/kubernetes/schema.lua
index 4888de63c..170608f55 100644
--- a/apisix/discovery/kubernetes/schema.lua
+++ b/apisix/discovery/kubernetes/schema.lua
@@ -25,116 +25,186 @@ local port_patterns = {
     { pattern = 
[[^(([1-9]\d{0,3}|[1-5]\d{4}|6[0-4]\d{3}|65[0-4]\d{2}|655[0-2]\d|6553[0-5]))$]] 
},
 }
 
+local schema_schema = {
+    type = "string",
+    enum = { "http", "https" },
+    default = "https",
+}
+
+local token_patterns = {
+    { pattern = [[\${[_A-Za-z]([_A-Za-z0-9]*[_A-Za-z])*}$]] },
+    { pattern = [[^[A-Za-z0-9+\/._=-]{0,4096}$]] },
+}
+
+local token_schema = {
+    type = "string",
+    oneOf = token_patterns,
+}
+
+local token_file_schema = {
+    type = "string",
+    pattern = [[^[^\:*?"<>|]*$]],
+    minLength = 1,
+    maxLength = 500,
+}
+
 local namespace_pattern = [[^[a-z0-9]([-a-z0-9_.]*[a-z0-9])?$]]
+
 local namespace_regex_pattern = [[^[\x21-\x7e]*$]]
 
-return {
+local namespace_selector_schema = {
     type = "object",
     properties = {
-        service = {
-            type = "object",
-            properties = {
-                schema = {
-                    type = "string",
-                    enum = { "http", "https" },
-                    default = "https",
-                },
-                host = {
-                    type = "string",
-                    default = "${KUBERNETES_SERVICE_HOST}",
-                    oneOf = host_patterns,
-                },
-                port = {
-                    type = "string",
-                    default = "${KUBERNETES_SERVICE_PORT}",
-                    oneOf = port_patterns,
-                },
-            },
-            default = {
-                schema = "https",
-                host = "${KUBERNETES_SERVICE_HOST}",
-                port = "${KUBERNETES_SERVICE_PORT}",
-            }
+        equal = {
+            type = "string",
+            pattern = namespace_pattern,
         },
-        client = {
-            type = "object",
-            properties = {
-                token = {
-                    type = "string",
-                    oneOf = {
-                        { pattern = 
[[\${[_A-Za-z]([_A-Za-z0-9]*[_A-Za-z])*}$]] },
-                        { pattern = [[^[A-Za-z0-9+\/._=-]{0,4096}$]] },
-                    },
-                },
-                token_file = {
-                    type = "string",
-                    pattern = [[^[^\:*?"<>|]*$]],
-                    minLength = 1,
-                    maxLength = 500,
-                }
-            },
-            oneOf = {
-                { required = { "token" } },
-                { required = { "token_file" } },
+        not_equal = {
+            type = "string",
+            pattern = namespace_pattern,
+        },
+        match = {
+            type = "array",
+            items = {
+                type = "string",
+                pattern = namespace_regex_pattern
             },
-            default = {
-                token_file = 
"/var/run/secrets/kubernetes.io/serviceaccount/token"
-            }
+            minItems = 1
         },
-        default_weight = {
-            type = "integer",
-            default = 50,
-            minimum = 0,
+        not_match = {
+            type = "array",
+            items = {
+                type = "string",
+                pattern = namespace_regex_pattern
+            },
+            minItems = 1
         },
-        namespace_selector = {
+    },
+    oneOf = {
+        { required = {} },
+        { required = { "equal" } },
+        { required = { "not_equal" } },
+        { required = { "match" } },
+        { required = { "not_match" } }
+    },
+}
+
+local label_selector_schema = {
+    type = "string",
+}
+
+local default_weight_schema = {
+    type = "integer",
+    default = 50,
+    minimum = 0,
+}
+
+local shared_size_schema = {
+    type = "string",
+    pattern = [[^[1-9][0-9]?m$]],
+    default = "1m",
+}
+
+return {
+    anyOf = {
+        {
             type = "object",
             properties = {
-                equal = {
-                    type = "string",
-                    pattern = namespace_pattern,
-                },
-                not_equal = {
-                    type = "string",
-                    pattern = namespace_pattern,
+                service = {
+                    type = "object",
+                    properties = {
+                        schema = schema_schema,
+                        host = {
+                            type = "string",
+                            oneOf = host_patterns,
+                            default = "${KUBERNETES_SERVICE_HOST}",
+                        },
+                        port = {
+                            type = "string",
+                            oneOf = port_patterns,
+                            default = "${KUBERNETES_SERVICE_PORT}",
+                        },
+                    },
+                    default = {
+                        schema = "https",
+                        host = "${KUBERNETES_SERVICE_HOST}",
+                        port = "${KUBERNETES_SERVICE_PORT}",
+                    }
                 },
-                match = {
-                    type = "array",
-                    items = {
-                        type = "string",
-                        pattern = namespace_regex_pattern
+                client = {
+                    type = "object",
+                    properties = {
+                        token = token_schema,
+                        token_file = token_file_schema,
                     },
-                    minItems = 1
+                    default = {
+                        token_file = 
"/var/run/secrets/kubernetes.io/serviceaccount/token"
+                    },
+                    ["if"] = {
+                        ["not"] = {
+                            anyOf = {
+                                { required = { "token" } },
+                                { required = { "token_file" } },
+                            }
+                        }
+                    },
+                    ["then"] = {
+                        properties = {
+                            token_file = {
+                                default = 
"/var/run/secrets/kubernetes.io/serviceaccount/token"
+                            }
+                        }
+                    }
                 },
-                not_match = {
-                    type = "array",
-                    items = {
+                namespace_selector = namespace_selector_schema,
+                label_selector = label_selector_schema,
+                default_weight = default_weight_schema,
+                shared_size = shared_size_schema,
+            },
+        },
+        {
+            type = "array",
+            minItems = 1,
+            items = {
+                type = "object",
+                properties = {
+                    id = {
                         type = "string",
-                        pattern = namespace_regex_pattern
+                        pattern = [[^[a-z0-9]{1,8}$]]
+                    },
+                    service = {
+                        type = "object",
+                        properties = {
+                            schema = schema_schema,
+                            host = {
+                                type = "string",
+                                oneOf = host_patterns,
+                            },
+                            port = {
+                                type = "string",
+                                oneOf = port_patterns,
+                            },
+                        },
+                        required = { "host", "port" }
+                    },
+                    client = {
+                        type = "object",
+                        properties = {
+                            token = token_schema,
+                            token_file = token_file_schema,
+                        },
+                        oneOf = {
+                            { required = { "token" } },
+                            { required = { "token_file" } },
+                        },
                     },
-                    minItems = 1
+                    namespace_selector = namespace_selector_schema,
+                    label_selector = label_selector_schema,
+                    default_weight = default_weight_schema,
+                    shared_size = shared_size_schema,
                 },
+                required = { "id", "service", "client" }
             },
-            oneOf = {
-                { required = { } },
-                { required = { "equal" } },
-                { required = { "not_equal" } },
-                { required = { "match" } },
-                { required = { "not_match" } }
-            },
-        },
-        label_selector = {
-            type = "string",
         }
-    },
-    default = {
-        service = {
-            schema = "https",
-            host = "${KUBERNETES_SERVICE_HOST}",
-            port = "${KUBERNETES_SERVICE_PORT}",
-        },
-        client = {
-            token_file = "/var/run/secrets/kubernetes.io/serviceaccount/token"
-        },
-        default_weight = 50
     }
 }
diff --git a/t/APISIX.pm b/t/APISIX.pm
index 0e5d8b5e3..b6e90b45d 100644
--- a/t/APISIX.pm
+++ b/t/APISIX.pm
@@ -523,6 +523,8 @@ _EOC_
     lua_shared_dict etcd-cluster-health-check 10m; # etcd health check
     lua_shared_dict ext-plugin 1m;
     lua_shared_dict kubernetes 1m;
+    lua_shared_dict kubernetes-first 1m;
+    lua_shared_dict kubernetes-second 1m;
     lua_shared_dict tars 1m;
     lua_shared_dict xds-config 1m;
     lua_shared_dict xds-config-version 1m;
diff --git a/t/cli/test_kubernetes.sh b/t/cli/test_kubernetes.sh
index bc371ee01..f60f856b5 100755
--- a/t/cli/test_kubernetes.sh
+++ b/t/cli/test_kubernetes.sh
@@ -26,23 +26,88 @@ discovery:
         host: ${HOST_ENV}
       client:
         token: ${TOKEN_ENV}
-' > conf/config.yaml
+' >conf/config.yaml
 
 make init
 
 if ! grep "env HOST_ENV" conf/nginx.conf; then
-    echo "kubernetes discovery env inject failed"
-    exit 1
+  echo "kubernetes discovery env inject failed"
+  exit 1
 fi
 
 if ! grep "env KUBERNETES_SERVICE_PORT" conf/nginx.conf; then
-    echo "kubernetes discovery env inject failed"
-    exit 1
+  echo "kubernetes discovery env inject failed"
+  exit 1
 fi
 
 if ! grep "env TOKEN_ENV" conf/nginx.conf; then
-    echo "kubernetes discovery env inject failed"
-    exit 1
+  echo "kubernetes discovery env inject failed"
+  exit 1
 fi
 
-echo "kubernetes discovery env inject success"
+if ! grep "lua_shared_dict kubernetes 1m;" conf/nginx.conf; then
+  echo "kubernetes discovery lua_shared_dict inject failed"
+  exit 1
+fi
+
+echo '
+discovery:
+    kubernetes:
+      - id: dev
+        service:
+          host: ${DEV_HOST}
+          port: ${DEV_PORT}
+        client:
+          token: ${DEV_TOKEN}
+      - id: pro
+        service:
+          host: ${PRO_HOST}
+          port: ${PRO_PORT}
+        client:
+          token: ${PRO_TOKEN}
+        shared_size: 2m
+' >conf/config.yaml
+
+make init
+
+if ! grep "env DEV_HOST" conf/nginx.conf; then
+  echo "kubernetes discovery env inject failed"
+  exit 1
+fi
+
+if ! grep "env DEV_PORT" conf/nginx.conf; then
+  echo "kubernetes discovery env inject failed"
+  exit 1
+fi
+
+if ! grep "env DEV_TOKEN" conf/nginx.conf; then
+  echo "kubernetes discovery env inject failed"
+  exit 1
+fi
+
+if ! grep "env PRO_HOST" conf/nginx.conf; then
+  echo "kubernetes discovery env inject failed"
+  exit 1
+fi
+
+if ! grep "env PRO_PORT" conf/nginx.conf; then
+  echo "kubernetes discovery env inject failed"
+  exit 1
+fi
+
+if ! grep "env PRO_TOKEN" conf/nginx.conf; then
+  echo "kubernetes discovery env inject failed"
+  exit 1
+fi
+
+if ! grep "lua_shared_dict kubernetes-dev 1m;" conf/nginx.conf; then
+  echo "kubernetes discovery lua_shared_dict inject failed"
+  exit 1
+fi
+
+if ! grep "lua_shared_dict kubernetes-pro 2m;" conf/nginx.conf; then
+  echo "kubernetes discovery lua_shared_dict inject failed"
+  exit 1
+fi
+
+echo "kubernetes discovery inject success"
diff --git a/t/kubernetes/discovery/kubernetes.t 
b/t/kubernetes/discovery/kubernetes.t
index 4a7b8573b..a631cc98a 100644
--- a/t/kubernetes/discovery/kubernetes.t
+++ b/t/kubernetes/discovery/kubernetes.t
@@ -14,74 +14,10 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
-
-BEGIN {
-    my $token_var_file = "/var/run/secrets/kubernetes.io/serviceaccount/token";
-    my $token_from_var = eval {`cat $token_var_file 2>/dev/null`};
-    if ($token_from_var) {
-
-        our $yaml_config = <<_EOC_;
-apisix:
-  node_listen: 1984
-  config_center: yaml
-  enable_admin: false
-discovery:
-  kubernetes: {}
-_EOC_
-        our $token_file = $token_var_file;
-        our $token_value = $token_from_var;
-
-    }
-
-    my $token_tmp_file = 
"/tmp/var/run/secrets/kubernetes.io/serviceaccount/token";
-    my $token_from_tmp = eval {`cat $token_tmp_file 2>/dev/null`};
-    if ($token_from_tmp) {
-
-        our $yaml_config = <<_EOC_;
-apisix:
-  node_listen: 1984
-  config_center: yaml
-  enable_admin: false
-discovery:
-  kubernetes:
-    client:
-      token_file: /tmp/var/run/secrets/kubernetes.io/serviceaccount/token
-_EOC_
-        our $token_file = $token_tmp_file;
-        our $token_value = $token_from_tmp;
-    }
-
-    our $scale_ns_c = <<_EOC_;
-[
-  {
-    "op": "replace_subsets",
-    "name": "ep",
-    "namespace": "ns-c",
-    "subsets": [
-      {
-        "addresses": [
-          {
-            "ip": "10.0.0.1"
-          }
-        ],
-        "ports": [
-          {
-            "name": "p1",
-            "port": 5001
-          }
-        ]
-      }
-    ]
-  }
-]
-_EOC_
-
-}
-
 use t::APISIX 'no_plan';
 
 repeat_each(1);
-log_level('debug');
+log_level('warn');
 no_root_location();
 no_shuffle();
 workers(4);
@@ -96,317 +32,96 @@ _EOC_
 
     $block->set_value("apisix_yaml", $apisix_yaml);
 
-    my $main_config = $block->main_config // <<_EOC_;
-env KUBERNETES_SERVICE_HOST=127.0.0.1;
-env KUBERNETES_SERVICE_PORT=6443;
-env KUBERNETES_CLIENT_TOKEN=$::token_value;
-env KUBERNETES_CLIENT_TOKEN_FILE=$::token_file;
-_EOC_
-
-    $block->set_value("main_config", $main_config);
-
     my $config = $block->config // <<_EOC_;
-        location /queries {
-            content_by_lua_block {
-              local core = require("apisix.core")
-              local d = require("apisix.discovery.kubernetes")
-
-              ngx.sleep(1)
-
-              ngx.req.read_body()
-              local request_body = ngx.req.get_body_data()
-              local queries = core.json.decode(request_body)
-              local response_body = "{"
-              for _,query in ipairs(queries) do
-                local nodes = d.nodes(query)
-                if nodes==nil or #nodes==0 then
-                    response_body=response_body.." "..0
-                else
-                    response_body=response_body.." "..#nodes
-                end
-              end
-              ngx.say(response_body.." }")
-            }
-        }
 
-        location /operators {
+        location /compare {
             content_by_lua_block {
                 local http = require("resty.http")
                 local core = require("apisix.core")
-                local ipairs = ipairs
-
-                ngx.req.read_body()
-                local request_body = ngx.req.get_body_data()
-                local operators = core.json.decode(request_body)
-
-                core.log.info("get body ", request_body)
-                core.log.info("get operators ", #operators)
-                for _, op in ipairs(operators) do
-                    local method, path, body
-                    local headers = {
-                        ["Host"] = "127.0.0.1:6445"
-                    }
-
-                    if op.op == "replace_subsets" then
-                        method = "PATCH"
-                        path = "/api/v1/namespaces/" .. op.namespace .. 
"/endpoints/" .. op.name
-                        if #op.subsets == 0 then
-                            body = 
'[{"path":"/subsets","op":"replace","value":[]}]'
-                        else
-                            local t = { { op = "replace", path = "/subsets", 
value = op.subsets } }
-                            body = core.json.encode(t, true)
+                local local_conf = 
require("apisix.core.config_local").local_conf()
+
+                local function deep_compare(tbl1, tbl2)
+                    if tbl1 == tbl2 then
+                        return true
+                    elseif type(tbl1) == "table" and type(tbl2) == "table" then
+                        for key1, value1 in pairs(tbl1) do
+                            local value2 = tbl2[key1]
+                            if value2 == nil then
+                                -- avoid the type call for missing keys in 
tbl2 by directly comparing with nil
+                                return false
+                            elseif value1 ~= value2 then
+                                if type(value1) == "table" and type(value2) == 
"table" then
+                                    if not deep_compare(value1, value2) then
+                                        return false
+                                    end
+                                else
+                                    return false
+                                end
+                            end
                         end
-                        headers["Content-Type"] = "application/json-patch+json"
+                        for key2, _ in pairs(tbl2) do
+                            if tbl1[key2] == nil then
+                                return false
+                            end
+                        end
+                        return true
                     end
 
-                    if op.op == "replace_labels" then
-                        method = "PATCH"
-                        path = "/api/v1/namespaces/" .. op.namespace .. 
"/endpoints/" .. op.name
-                        local t = { { op = "replace", path = 
"/metadata/labels", value = op.labels } }
-                        body = core.json.encode(t, true)
-                        headers["Content-Type"] = "application/json-patch+json"
-                    end
+                    return false
+                end
 
-                    local httpc = http.new()
-                    core.log.info("begin to connect ", "127.0.0.1:6445")
-                    local ok, message = httpc:connect({
-                        scheme = "http",
-                        host = "127.0.0.1",
-                        port = 6445,
-                    })
-                    if not ok then
-                        core.log.error("connect 127.0.0.1:6445 failed, message 
: ", message)
-                        ngx.say("FAILED")
-                    end
-                    local res, err = httpc:request({
-                        method = method,
-                        path = path,
-                        headers = headers,
-                        body = body,
-                    })
-                    if err ~= nil then
-                        core.log.err("operator k8s cluster error: ", err)
-                        return 500
-                    end
-                    if res.status ~= 200 and res.status ~= 201 and res.status 
~= 409 then
-                        return res.status
-                    end
+                ngx.req.read_body()
+                local request_body = ngx.req.get_body_data()
+                local expect = core.json.decode(request_body)
+                local current = local_conf.discovery.kubernetes
+                if deep_compare(expect,current) then
+                  ngx.say("true")
+                else
+                  ngx.say("false, current is ",core.json.encode(current,true))
                 end
-                ngx.say("DONE")
             }
         }
 
 _EOC_
 
     $block->set_value("config", $config);
+
 });
 
 run_tests();
 
 __DATA__
 
-=== TEST 1: create namespace and endpoints
---- yaml_config eval: $::yaml_config
---- request
-POST /operators
-[
-  {
-    "op": "replace_subsets",
-    "namespace": "ns-a",
-    "name": "ep",
-    "subsets": [
-      {
-        "addresses": [
-          {
-            "ip": "10.0.0.1"
-          },
-          {
-            "ip": "10.0.0.2"
-          }
-        ],
-        "ports": [
-          {
-            "name": "p1",
-            "port": 5001
-          }
-        ]
-      },
-      {
-        "addresses": [
-          {
-            "ip": "20.0.0.1"
-          },
-          {
-            "ip": "20.0.0.2"
-          }
-        ],
-        "ports": [
-          {
-            "name": "p2",
-            "port": 5002
-          }
-        ]
-      }
-    ]
-  },
-  {
-    "op": "create_namespace",
-    "name": "ns-b"
-  },
-  {
-    "op": "replace_subsets",
-    "namespace": "ns-b",
-    "name": "ep",
-    "subsets": [
-      {
-        "addresses": [
-          {
-            "ip": "10.0.0.1"
-          },
-          {
-            "ip": "10.0.0.2"
-          }
-        ],
-        "ports": [
-          {
-            "name": "p1",
-            "port": 5001
-          }
-        ]
-      },
-      {
-        "addresses": [
-          {
-            "ip": "20.0.0.1"
-          },
-          {
-            "ip": "20.0.0.2"
-          }
-        ],
-        "ports": [
-          {
-            "name": "p2",
-            "port": 5002
-          }
-        ]
-      }
-    ]
-  },
-  {
-    "op": "create_namespace",
-    "name": "ns-c"
-  },
-  {
-    "op": "replace_subsets",
-    "namespace": "ns-c",
-    "name": "ep",
-    "subsets": [
-      {
-        "addresses": [
-          {
-            "ip": "10.0.0.1"
-          },
-          {
-            "ip": "10.0.0.2"
-          }
-        ],
-        "ports": [
-          {
-            "port": 5001
-          }
-        ]
-      },
-      {
-        "addresses": [
-          {
-            "ip": "20.0.0.1"
-          },
-          {
-            "ip": "20.0.0.2"
-          }
-        ],
-        "ports": [
-          {
-            "port": 5002
-          }
-        ]
-      }
-    ]
-  }
-]
---- more_headers
-Content-type: application/json
---- error_code: 200
---- no_error_log
-[error]
-
-
-
-=== TEST 2: use default parameters
---- yaml_config eval: $::yaml_config
---- request
-GET /queries
-["ns-a/ep:p1","ns-a/ep:p2","ns-b/ep:p1","ns-b/ep:p2","ns-c/ep:5001","ns-c/ep:5002"]
---- more_headers
-Content-type: application/json
---- response_body eval
-qr{ 2 2 2 2 2 2 }
---- no_error_log
-[error]
-
-
-
-=== TEST 3: use specify parameters
---- yaml_config
-apisix:
-  node_listen: 1984
-  config_center: yaml
-  enable_admin: false
-discovery:
-  kubernetes:
-    service:
-      host: "127.0.0.1"
-      port: "6443"
-    client:
-      token: "${KUBERNETES_CLIENT_TOKEN}"
---- request
-GET /queries
-["ns-a/ep:p1","ns-a/ep:p2","ns-b/ep:p1","ns-b/ep:p2","ns-c/ep:5001","ns-c/ep:5002"]
---- more_headers
-Content-type: application/json
---- response_body eval
-qr{ 2 2 2 2 2 2 }
---- no_error_log
-[error]
-
-
-
-=== TEST 4: use specify environment parameters
+=== TEST 1: default value with minimal configuration
 --- yaml_config
 apisix:
   node_listen: 1984
   config_center: yaml
   enable_admin: false
 discovery:
-  kubernetes:
-    service:
-      host: ${KUBERNETES_SERVICE_HOST}
-      port: ${KUBERNETES_SERVICE_PORT}
-    client:
-      token: ${KUBERNETES_CLIENT_TOKEN}
+  kubernetes: {}
 --- request
-GET /queries
-["ns-a/ep:p1","ns-a/ep:p2","ns-b/ep:p1","ns-b/ep:p2","ns-c/ep:5001","ns-c/ep:5002"]
+GET /compare
+{
+  "service": {
+    "schema": "https",
+    "host": "${KUBERNETES_SERVICE_HOST}",
+    "port": "${KUBERNETES_SERVICE_PORT}"
+  },
+  "client": {
+    "token_file": "/var/run/secrets/kubernetes.io/serviceaccount/token"
+  },
+  "shared_size": "1m",
+  "default_weight": 50
+}
 --- more_headers
 Content-type: application/json
---- response_body eval
-qr{ 2 2 2 2 2 2 }
---- no_error_log
-[error]
+--- response_body
+true
 
 
 
-=== TEST 5: use token_file
+=== TEST 2: default value with minimal service and client configuration
 --- yaml_config
 apisix:
   node_listen: 1984
@@ -414,21 +129,30 @@ apisix:
   enable_admin: false
 discovery:
   kubernetes:
-    client:
-      token_file: ${KUBERNETES_CLIENT_TOKEN_FILE}
+    service: {}
+    client: {}
 --- request
-GET /queries
-["ns-a/ep:p1","ns-a/ep:p2","ns-b/ep:p1","ns-b/ep:p2","ns-c/ep:5001","ns-c/ep:5002"]
+GET /compare
+{
+  "service": {
+    "schema": "https",
+    "host": "${KUBERNETES_SERVICE_HOST}",
+    "port": "${KUBERNETES_SERVICE_PORT}"
+  },
+  "client": {
+    "token_file": "/var/run/secrets/kubernetes.io/serviceaccount/token"
+  },
+  "shared_size": "1m",
+  "default_weight": 50
+}
 --- more_headers
 Content-type: application/json
---- response_body eval
-qr{ 2 2 2 2 2 2 }
---- no_error_log
-[error]
+--- response_body
+true
 
 
 
-=== TEST 6: use http
+=== TEST 3: mixing set custom and default values
 --- yaml_config
 apisix:
   node_listen: 1984
@@ -437,96 +161,30 @@ apisix:
 discovery:
   kubernetes:
     service:
-      schema: http
-      host: "127.0.0.1"
-      port: "6445"
-    client:
-      token: ""
---- request
-GET /queries
-["ns-a/ep:p1","ns-a/ep:p2","ns-b/ep:p1","ns-b/ep:p2","ns-c/ep:5001","ns-c/ep:5002"]
---- more_headers
-Content-type: application/json
---- response_body eval
-qr{ 2 2 2 2 2 2 }
---- no_error_log
-[error]
-
-
-
-=== TEST 7: use namespace selector equal
---- yaml_config
-apisix:
-  node_listen: 1984
-  config_center: yaml
-  enable_admin: false
-discovery:
-  kubernetes:
-    client:
-      token_file: ${KUBERNETES_CLIENT_TOKEN_FILE}
-    namespace_selector:
-      equal: ns-a
---- request
-GET /queries
-["ns-a/ep:p1","ns-a/ep:p2","ns-b/ep:p1","ns-b/ep:p2","ns-c/ep:5001","ns-c/ep:5002"]
---- more_headers
-Content-type: application/json
---- response_body eval
-qr{ 2 2 0 0 0 0 }
---- no_error_log
-[error]
-
-
-
-=== TEST 8: use namespace selector not_equal
---- yaml_config
-apisix:
-  node_listen: 1984
-  config_center: yaml
-  enable_admin: false
-discovery:
-  kubernetes:
-    client:
-      token_file: ${KUBERNETES_CLIENT_TOKEN_FILE}
-    namespace_selector:
-      not_equal: ns-a
+        host: "sample.com"
+    shared_size: "2m"
 --- request
-GET /queries
-["ns-a/ep:p1","ns-a/ep:p2","ns-b/ep:p1","ns-b/ep:p2","ns-c/ep:5001","ns-c/ep:5002"]
---- more_headers
-Content-type: application/json
---- response_body eval
-qr{ 0 0 2 2 2 2 }
---- no_error_log
-[error]
-
-
-
-=== TEST 9: use namespace selector match
---- yaml_config
-apisix:
-  node_listen: 1984
-  config_center: yaml
-  enable_admin: false
-discovery:
-  kubernetes:
-    client:
-      token_file: ${KUBERNETES_CLIENT_TOKEN_FILE}
-    namespace_selector:
-      match: [ns-a,ns-b]
---- request
-GET /queries
-["ns-a/ep:p1","ns-a/ep:p2","ns-b/ep:p1","ns-b/ep:p2","ns-c/ep:5001","ns-c/ep:5002"]
+GET /compare
+{
+  "service": {
+    "schema": "https",
+    "host": "sample.com",
+    "port": "${KUBERNETES_SERVICE_PORT}"
+  },
+  "client": {
+    "token_file" : "/var/run/secrets/kubernetes.io/serviceaccount/token"
+  },
+  "shared_size": "2m",
+  "default_weight": 50
+}
 --- more_headers
 Content-type: application/json
---- response_body eval
-qr{ 2 2 2 2 0 0 }
---- no_error_log
-[error]
+--- response_body
+true
 
 
 
-=== TEST 10: use namespace selector match with regex
+=== TEST 4: mixing set custom and default values
 --- yaml_config
 apisix:
   node_listen: 1984
@@ -534,23 +192,33 @@ apisix:
   enable_admin: false
 discovery:
   kubernetes:
+    service:
+        schema: "http"
     client:
-      token_file: ${KUBERNETES_CLIENT_TOKEN_FILE}
-    namespace_selector:
-      match: ["ns-[ab]"]
+        token: "test"
+    default_weight: 33
 --- request
-GET /queries
-["ns-a/ep:p1","ns-a/ep:p2","ns-b/ep:p1","ns-b/ep:p2","ns-c/ep:5001","ns-c/ep:5002"]
+GET /compare
+{
+  "service": {
+    "schema": "http",
+    "host": "${KUBERNETES_SERVICE_HOST}",
+    "port": "${KUBERNETES_SERVICE_PORT}"
+  },
+  "client": {
+    "token": "test"
+  },
+  "shared_size": "1m",
+  "default_weight": 33
+}
 --- more_headers
 Content-type: application/json
---- response_body eval
-qr{ 2 2 2 2 0 0 }
---- no_error_log
-[error]
+--- response_body
+true
 
 
 
-=== TEST 11: use namespace selector not_match
+=== TEST 5: multi cluster mode configuration
 --- yaml_config
 apisix:
   node_listen: 1984
@@ -558,157 +226,52 @@ apisix:
   enable_admin: false
 discovery:
   kubernetes:
+  - id: "debug"
+    service:
+        host: "1.cluster.com"
+        port: "6445"
     client:
-      token_file: ${KUBERNETES_CLIENT_TOKEN_FILE}
-    namespace_selector:
-      not_match: ["ns-a"]
---- request
-GET /queries
-["ns-a/ep:p1","ns-a/ep:p2","ns-b/ep:p1","ns-b/ep:p2","ns-c/ep:5001","ns-c/ep:5002"]
---- more_headers
-Content-type: application/json
---- response_body eval
-qr{ 0 0 2 2 2 2 }
---- no_error_log
-[error]
-
-
-
-=== TEST 12: use namespace selector not_match with regex
---- yaml_config
-apisix:
-  node_listen: 1984
-  config_center: yaml
-  enable_admin: false
-discovery:
-  kubernetes:
+        token: "token"
+  - id: "release"
+    service:
+        schema: "http"
+        host: "2.cluster.com"
+        port: "${MyPort}"
     client:
-      token_file: ${KUBERNETES_CLIENT_TOKEN_FILE}
-    namespace_selector:
-      not_match: ["ns-[ab]"]
+        token_file: "/var/token"
+    default_weight: 33
+    shared_size: "2m"
 --- request
-GET /queries
-["ns-a/ep:p1","ns-a/ep:p2","ns-b/ep:p1","ns-b/ep:p2","ns-c/ep:5001","ns-c/ep:5002"]
---- more_headers
-Content-type: application/json
---- response_body eval
-qr{ 0 0 0 0 2 2 }
---- no_error_log
-[error]
-
-
-
-=== TEST 13: use label selector
---- yaml_config
-apisix:
-  node_listen: 1984
-  config_center: yaml
-  enable_admin: false
-discovery:
-  kubernetes:
-    client:
-      token_file: ${KUBERNETES_CLIENT_TOKEN_FILE}
-    label_selector: |-
-       first=1,second
---- request eval
-[
-
-"POST /operators
-[{\"op\":\"replace_labels\",\"name\":\"ep\",\"namespace\":\"ns-a\",\"labels\":{}}]",
-
-"POST /operators
-[{\"op\":\"replace_labels\",\"name\":\"ep\",\"namespace\":\"ns-b\",\"labels\":{}}]",
-
-"POST /operators
-[{\"op\":\"replace_labels\",\"name\":\"ep\",\"namespace\":\"ns-c\",\"labels\":{}}]",
-
-"GET /queries
-[\"ns-a/ep:p1\",\"ns-b/ep:p1\",\"ns-c/ep:5001\"]",
-
-"POST /operators
-[{\"op\":\"replace_labels\",\"name\":\"ep\",\"namespace\":\"ns-a\",\"labels\":{\"first\":\"1\"
 }}]",
-
-"GET /queries
-[\"ns-a/ep:p1\",\"ns-b/ep:p1\",\"ns-c/ep:5001\"]",
-
-"POST /operators
-[{\"op\":\"replace_labels\",\"name\":\"ep\",\"namespace\":\"ns-b\",\"labels\":{\"first\":\"1\",\"second\":\"o\"
 }}]",
-
-"GET /queries
-[\"ns-a/ep:p1\",\"ns-b/ep:p1\",\"ns-c/ep:5001\"]",
-
-"POST /operators
-[{\"op\":\"replace_labels\",\"name\":\"ep\",\"namespace\":\"ns-c\",\"labels\":{\"first\":\"2\",\"second\":\"o\"
 }}]",
-
-"GET /queries
-[\"ns-a/ep:p1\",\"ns-b/ep:p1\",\"ns-c/ep:5001\"]",
-
-"POST /operators
-[{\"op\":\"replace_labels\",\"name\":\"ep\",\"namespace\":\"ns-c\",\"labels\":{\"first\":\"1\"
 }}]",
-
-"GET /queries
-[\"ns-a/ep:p1\",\"ns-b/ep:p1\",\"ns-c/ep:5001\"]",
-
-"POST /operators
-[{\"op\":\"replace_labels\",\"name\":\"ep\",\"namespace\":\"ns-c\",\"labels\":{\"first\":\"1\",\"second\":\"o\"
 }}]",
-
-"GET /queries
-[\"ns-a/ep:p1\",\"ns-b/ep:p1\",\"ns-c/ep:5001\"]",
-
-]
---- response_body eval
+GET /compare
 [
-    "DONE\n",
-    "DONE\n",
-    "DONE\n",
-    "{ 0 0 0 }\n",
-    "DONE\n",
-    "{ 0 0 0 }\n",
-    "DONE\n",
-    "{ 0 2 0 }\n",
-    "DONE\n",
-    "{ 0 2 0 }\n",
-    "DONE\n",
-    "{ 0 2 0 }\n",
-    "DONE\n",
-    "{ 0 2 2 }\n",
-]
---- no_error_log
-[error]
-
-
-
-=== TEST 14: scale endpoints
---- yaml_config eval: $::yaml_config
---- request eval
-[
-"GET /queries
-[\"ns-a/ep:p1\",\"ns-a/ep:p2\"]",
-
-"POST /operators
-[{\"op\":\"replace_subsets\",\"name\":\"ep\",\"namespace\":\"ns-a\",\"subsets\":[]}]",
-
-"GET /queries
-[\"ns-a/ep:p1\",\"ns-a/ep:p2\"]",
-
-"GET /queries
-[\"ns-c/ep:5001\",\"ns-c/ep:5002\",\"ns-c/ep:p1\"]",
-
-"POST /operators
-$::scale_ns_c",
-
-"GET /queries
-[\"ns-c/ep:5001\",\"ns-c/ep:5002\",\"ns-c/ep:p1\"]",
-
-]
---- response_body eval
-[
-    "{ 2 2 }\n",
-    "DONE\n",
-    "{ 0 0 }\n",
-    "{ 2 2 0 }\n",
-    "DONE\n",
-    "{ 0 0 1 }\n",
+  {
+    "id": "debug",
+    "service": {
+      "schema": "https",
+      "host": "1.cluster.com",
+      "port": "6445"
+    },
+    "client": {
+      "token": "token"
+    },
+    "default_weight": 50,
+    "shared_size": "1m"
+  },
+  {
+    "id": "release",
+    "service": {
+      "schema": "http",
+      "host": "2.cluster.com",
+      "port": "${MyPort}"
+    },
+    "client": {
+      "token_file": "/var/token"
+    },
+    "default_weight": 33,
+    "shared_size": "2m"
+  }
 ]
---- no_error_log
-[error]
+--- more_headers
+Content-type: application/json
+--- response_body
+true
diff --git a/t/kubernetes/discovery/kubernetes.t 
b/t/kubernetes/discovery/kubernetes2.t
similarity index 60%
copy from t/kubernetes/discovery/kubernetes.t
copy to t/kubernetes/discovery/kubernetes2.t
index 4a7b8573b..8a71cd8cd 100644
--- a/t/kubernetes/discovery/kubernetes.t
+++ b/t/kubernetes/discovery/kubernetes2.t
@@ -16,40 +16,31 @@
 #
 
 BEGIN {
-    my $token_var_file = "/var/run/secrets/kubernetes.io/serviceaccount/token";
-    my $token_from_var = eval {`cat $token_var_file 2>/dev/null`};
-    if ($token_from_var) {
+    our $token_file = 
"/tmp/var/run/secrets/kubernetes.io/serviceaccount/token";
+    our $token_value = eval {`cat $token_file 2>/dev/null`};
 
-        our $yaml_config = <<_EOC_;
-apisix:
-  node_listen: 1984
-  config_center: yaml
-  enable_admin: false
-discovery:
-  kubernetes: {}
-_EOC_
-        our $token_file = $token_var_file;
-        our $token_value = $token_from_var;
-
-    }
-
-    my $token_tmp_file = 
"/tmp/var/run/secrets/kubernetes.io/serviceaccount/token";
-    my $token_from_tmp = eval {`cat $token_tmp_file 2>/dev/null`};
-    if ($token_from_tmp) {
-
-        our $yaml_config = <<_EOC_;
+    our $yaml_config = <<_EOC_;
 apisix:
   node_listen: 1984
   config_center: yaml
   enable_admin: false
 discovery:
   kubernetes:
-    client:
-      token_file: /tmp/var/run/secrets/kubernetes.io/serviceaccount/token
+    - id: first
+      service:
+        host: "127.0.0.1"
+        port: "6443"
+      client:
+        token_file: "/tmp/var/run/secrets/kubernetes.io/serviceaccount/token"
+    - id: second
+      service:
+        schema: "http",
+        host: "127.0.0.1",
+        port: "6445"
+      client:
+        token_file: "/tmp/var/run/secrets/kubernetes.io/serviceaccount/token"
+
 _EOC_
-        our $token_file = $token_tmp_file;
-        our $token_value = $token_from_tmp;
-    }
 
     our $scale_ns_c = <<_EOC_;
 [
@@ -81,7 +72,7 @@ _EOC_
 use t::APISIX 'no_plan';
 
 repeat_each(1);
-log_level('debug');
+log_level('warn');
 no_root_location();
 no_shuffle();
 workers(4);
@@ -199,6 +190,11 @@ _EOC_
 _EOC_
 
     $block->set_value("config", $config);
+
+    if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
+        $block->set_value("no_error_log", "[error]");
+    }
+
 });
 
 run_tests();
@@ -336,9 +332,6 @@ POST /operators
 ]
 --- more_headers
 Content-type: application/json
---- error_code: 200
---- no_error_log
-[error]
 
 
 
@@ -346,67 +339,18 @@ Content-type: application/json
 --- yaml_config eval: $::yaml_config
 --- request
 GET /queries
-["ns-a/ep:p1","ns-a/ep:p2","ns-b/ep:p1","ns-b/ep:p2","ns-c/ep:5001","ns-c/ep:5002"]
---- more_headers
-Content-type: application/json
---- response_body eval
-qr{ 2 2 2 2 2 2 }
---- no_error_log
-[error]
-
-
-
-=== TEST 3: use specify parameters
---- yaml_config
-apisix:
-  node_listen: 1984
-  config_center: yaml
-  enable_admin: false
-discovery:
-  kubernetes:
-    service:
-      host: "127.0.0.1"
-      port: "6443"
-    client:
-      token: "${KUBERNETES_CLIENT_TOKEN}"
---- request
-GET /queries
-["ns-a/ep:p1","ns-a/ep:p2","ns-b/ep:p1","ns-b/ep:p2","ns-c/ep:5001","ns-c/ep:5002"]
---- more_headers
-Content-type: application/json
---- response_body eval
-qr{ 2 2 2 2 2 2 }
---- no_error_log
-[error]
-
-
-
-=== TEST 4: use specify environment parameters
---- yaml_config
-apisix:
-  node_listen: 1984
-  config_center: yaml
-  enable_admin: false
-discovery:
-  kubernetes:
-    service:
-      host: ${KUBERNETES_SERVICE_HOST}
-      port: ${KUBERNETES_SERVICE_PORT}
-    client:
-      token: ${KUBERNETES_CLIENT_TOKEN}
---- request
-GET /queries
-["ns-a/ep:p1","ns-a/ep:p2","ns-b/ep:p1","ns-b/ep:p2","ns-c/ep:5001","ns-c/ep:5002"]
+[
+  
"first/ns-a/ep:p1","first/ns-a/ep:p2","first/ns-b/ep:p1","first/ns-b/ep:p2","first/ns-c/ep:5001","first/ns-c/ep:5002",
+  
"second/ns-a/ep:p1","second/ns-a/ep:p2","second/ns-b/ep:p1","second/ns-b/ep:p2","second/ns-c/ep:5001","second/ns-c/ep:5002"
+]
 --- more_headers
 Content-type: application/json
 --- response_body eval
-qr{ 2 2 2 2 2 2 }
---- no_error_log
-[error]
+qr{ 2 2 2 2 2 2 2 2 2 2 2 2 }
 
 
 
-=== TEST 5: use token_file
+=== TEST 3: use specify environment parameters
 --- yaml_config
 apisix:
   node_listen: 1984
@@ -414,47 +358,34 @@ apisix:
   enable_admin: false
 discovery:
   kubernetes:
-    client:
-      token_file: ${KUBERNETES_CLIENT_TOKEN_FILE}
---- request
-GET /queries
-["ns-a/ep:p1","ns-a/ep:p2","ns-b/ep:p1","ns-b/ep:p2","ns-c/ep:5001","ns-c/ep:5002"]
---- more_headers
-Content-type: application/json
---- response_body eval
-qr{ 2 2 2 2 2 2 }
---- no_error_log
-[error]
-
+    - id: first
+      service:
+        host: ${KUBERNETES_SERVICE_HOST}
+        port: ${KUBERNETES_SERVICE_PORT}
+      client:
+        token: ${KUBERNETES_CLIENT_TOKEN}
+    - id: second
+      service:
+        schema: "http",
+        host: "127.0.0.1",
+        port: "6445"
+      client:
+        token: ${KUBERNETES_CLIENT_TOKEN}
 
-
-=== TEST 6: use http
---- yaml_config
-apisix:
-  node_listen: 1984
-  config_center: yaml
-  enable_admin: false
-discovery:
-  kubernetes:
-    service:
-      schema: http
-      host: "127.0.0.1"
-      port: "6445"
-    client:
-      token: ""
 --- request
 GET /queries
-["ns-a/ep:p1","ns-a/ep:p2","ns-b/ep:p1","ns-b/ep:p2","ns-c/ep:5001","ns-c/ep:5002"]
+[
+  
"first/ns-a/ep:p1","first/ns-a/ep:p2","first/ns-b/ep:p1","first/ns-b/ep:p2","first/ns-c/ep:5001","first/ns-c/ep:5002",
+  
"second/ns-a/ep:p1","second/ns-a/ep:p2","second/ns-b/ep:p1","second/ns-b/ep:p2","second/ns-c/ep:5001","second/ns-c/ep:5002"
+]
 --- more_headers
 Content-type: application/json
 --- response_body eval
-qr{ 2 2 2 2 2 2 }
---- no_error_log
-[error]
+qr{ 2 2 2 2 2 2 2 2 2 2 2 2 }
 
 
 
-=== TEST 7: use namespace selector equal
+=== TEST 4: use namespace selector equal
 --- yaml_config
 apisix:
   node_listen: 1984
@@ -462,23 +393,35 @@ apisix:
   enable_admin: false
 discovery:
   kubernetes:
-    client:
-      token_file: ${KUBERNETES_CLIENT_TOKEN_FILE}
-    namespace_selector:
-      equal: ns-a
+    - id: first
+      service:
+        host: ${KUBERNETES_SERVICE_HOST}
+        port: ${KUBERNETES_SERVICE_PORT}
+      client:
+        token_file: ${KUBERNETES_CLIENT_TOKEN_FILE}
+      namespace_selector:
+        equal: ns-a
+    - id: second
+      service:
+        schema: "http",
+        host: "127.0.0.1",
+        port: "6445"
+      client:
+        token: ${KUBERNETES_CLIENT_TOKEN}
 --- request
 GET /queries
-["ns-a/ep:p1","ns-a/ep:p2","ns-b/ep:p1","ns-b/ep:p2","ns-c/ep:5001","ns-c/ep:5002"]
+[
+  
"first/ns-a/ep:p1","first/ns-a/ep:p2","first/ns-b/ep:p1","first/ns-b/ep:p2","first/ns-c/ep:5001","first/ns-c/ep:5002",
+  
"second/ns-a/ep:p1","second/ns-a/ep:p2","second/ns-b/ep:p1","second/ns-b/ep:p2","second/ns-c/ep:5001","second/ns-c/ep:5002"
+]
 --- more_headers
 Content-type: application/json
 --- response_body eval
-qr{ 2 2 0 0 0 0 }
---- no_error_log
-[error]
+qr{ 2 2 0 0 0 0 2 2 2 2 2 2 }
 
 
 
-=== TEST 8: use namespace selector not_equal
+=== TEST 5: use namespace selector not_equal
 --- yaml_config
 apisix:
   node_listen: 1984
@@ -486,23 +429,35 @@ apisix:
   enable_admin: false
 discovery:
   kubernetes:
-    client:
-      token_file: ${KUBERNETES_CLIENT_TOKEN_FILE}
-    namespace_selector:
-      not_equal: ns-a
+    - id: first
+      service:
+        host: ${KUBERNETES_SERVICE_HOST}
+        port: ${KUBERNETES_SERVICE_PORT}
+      client:
+        token_file: ${KUBERNETES_CLIENT_TOKEN_FILE}
+      namespace_selector:
+        not_equal: ns-a
+    - id: second
+      service:
+        schema: "http",
+        host: "127.0.0.1",
+        port: "6445"
+      client:
+        token: ${KUBERNETES_CLIENT_TOKEN}
 --- request
 GET /queries
-["ns-a/ep:p1","ns-a/ep:p2","ns-b/ep:p1","ns-b/ep:p2","ns-c/ep:5001","ns-c/ep:5002"]
+[
+  
"first/ns-a/ep:p1","first/ns-a/ep:p2","first/ns-b/ep:p1","first/ns-b/ep:p2","first/ns-c/ep:5001","first/ns-c/ep:5002",
+  
"second/ns-a/ep:p1","second/ns-a/ep:p2","second/ns-b/ep:p1","second/ns-b/ep:p2","second/ns-c/ep:5001","second/ns-c/ep:5002"
+]
 --- more_headers
 Content-type: application/json
 --- response_body eval
-qr{ 0 0 2 2 2 2 }
---- no_error_log
-[error]
+qr{ 0 0 2 2 2 2 2 2 2 2 2 2 }
 
 
 
-=== TEST 9: use namespace selector match
+=== TEST 6: use namespace selector match
 --- yaml_config
 apisix:
   node_listen: 1984
@@ -510,23 +465,35 @@ apisix:
   enable_admin: false
 discovery:
   kubernetes:
-    client:
-      token_file: ${KUBERNETES_CLIENT_TOKEN_FILE}
-    namespace_selector:
-      match: [ns-a,ns-b]
+    - id: first
+      service:
+        host: ${KUBERNETES_SERVICE_HOST}
+        port: ${KUBERNETES_SERVICE_PORT}
+      client:
+        token_file: ${KUBERNETES_CLIENT_TOKEN_FILE}
+      namespace_selector:
+        match: [ns-a,ns-b]
+    - id: second
+      service:
+        schema: "http",
+        host: "127.0.0.1",
+        port: "6445"
+      client:
+        token: ${KUBERNETES_CLIENT_TOKEN}
 --- request
 GET /queries
-["ns-a/ep:p1","ns-a/ep:p2","ns-b/ep:p1","ns-b/ep:p2","ns-c/ep:5001","ns-c/ep:5002"]
+[
+  
"first/ns-a/ep:p1","first/ns-a/ep:p2","first/ns-b/ep:p1","first/ns-b/ep:p2","first/ns-c/ep:5001","first/ns-c/ep:5002",
+  
"second/ns-a/ep:p1","second/ns-a/ep:p2","second/ns-b/ep:p1","second/ns-b/ep:p2","second/ns-c/ep:5001","second/ns-c/ep:5002"
+]
 --- more_headers
 Content-type: application/json
 --- response_body eval
-qr{ 2 2 2 2 0 0 }
---- no_error_log
-[error]
+qr{ 2 2 2 2 0 0 2 2 2 2 2 2 }
 
 
 
-=== TEST 10: use namespace selector match with regex
+=== TEST 7: use namespace selector match with regex
 --- yaml_config
 apisix:
   node_listen: 1984
@@ -534,23 +501,35 @@ apisix:
   enable_admin: false
 discovery:
   kubernetes:
-    client:
-      token_file: ${KUBERNETES_CLIENT_TOKEN_FILE}
-    namespace_selector:
-      match: ["ns-[ab]"]
+    - id: first
+      service:
+        host: ${KUBERNETES_SERVICE_HOST}
+        port: ${KUBERNETES_SERVICE_PORT}
+      client:
+        token_file: ${KUBERNETES_CLIENT_TOKEN_FILE}
+      namespace_selector:
+        match: ["ns-[ab]"]
+    - id: second
+      service:
+        schema: "http",
+        host: "127.0.0.1",
+        port: "6445"
+      client:
+        token: ${KUBERNETES_CLIENT_TOKEN}
 --- request
 GET /queries
-["ns-a/ep:p1","ns-a/ep:p2","ns-b/ep:p1","ns-b/ep:p2","ns-c/ep:5001","ns-c/ep:5002"]
+[
+  
"first/ns-a/ep:p1","first/ns-a/ep:p2","first/ns-b/ep:p1","first/ns-b/ep:p2","first/ns-c/ep:5001","first/ns-c/ep:5002",
+  
"second/ns-a/ep:p1","second/ns-a/ep:p2","second/ns-b/ep:p1","second/ns-b/ep:p2","second/ns-c/ep:5001","second/ns-c/ep:5002"
+]
 --- more_headers
 Content-type: application/json
 --- response_body eval
-qr{ 2 2 2 2 0 0 }
---- no_error_log
-[error]
+qr{ 2 2 2 2 0 0 2 2 2 2 2 2 }
 
 
 
-=== TEST 11: use namespace selector not_match
+=== TEST 8: use namespace selector not_match
 --- yaml_config
 apisix:
   node_listen: 1984
@@ -558,23 +537,35 @@ apisix:
   enable_admin: false
 discovery:
   kubernetes:
-    client:
-      token_file: ${KUBERNETES_CLIENT_TOKEN_FILE}
-    namespace_selector:
-      not_match: ["ns-a"]
+    - id: first
+      service:
+        host: ${KUBERNETES_SERVICE_HOST}
+        port: ${KUBERNETES_SERVICE_PORT}
+      client:
+        token_file: ${KUBERNETES_CLIENT_TOKEN_FILE}
+      namespace_selector:
+        not_match: ["ns-a"]
+    - id: second
+      service:
+        schema: "http",
+        host: "127.0.0.1",
+        port: "6445"
+      client:
+        token: ${KUBERNETES_CLIENT_TOKEN}
 --- request
 GET /queries
-["ns-a/ep:p1","ns-a/ep:p2","ns-b/ep:p1","ns-b/ep:p2","ns-c/ep:5001","ns-c/ep:5002"]
+[
+  
"first/ns-a/ep:p1","first/ns-a/ep:p2","first/ns-b/ep:p1","first/ns-b/ep:p2","first/ns-c/ep:5001","first/ns-c/ep:5002",
+  
"second/ns-a/ep:p1","second/ns-a/ep:p2","second/ns-b/ep:p1","second/ns-b/ep:p2","second/ns-c/ep:5001","second/ns-c/ep:5002"
+]
 --- more_headers
 Content-type: application/json
 --- response_body eval
-qr{ 0 0 2 2 2 2 }
---- no_error_log
-[error]
+qr{ 0 0 2 2 2 2 2 2 2 2 2 2 }
 
 
 
-=== TEST 12: use namespace selector not_match with regex
+=== TEST 9: use namespace selector not_match with regex
 --- yaml_config
 apisix:
   node_listen: 1984
@@ -582,23 +573,35 @@ apisix:
   enable_admin: false
 discovery:
   kubernetes:
-    client:
-      token_file: ${KUBERNETES_CLIENT_TOKEN_FILE}
-    namespace_selector:
-      not_match: ["ns-[ab]"]
+    - id: first
+      service:
+        host: ${KUBERNETES_SERVICE_HOST}
+        port: ${KUBERNETES_SERVICE_PORT}
+      client:
+        token_file: ${KUBERNETES_CLIENT_TOKEN_FILE}
+      namespace_selector:
+        not_match: ["ns-[ab]"]
+    - id: second
+      service:
+        schema: "http",
+        host: "127.0.0.1",
+        port: "6445"
+      client:
+        token: ${KUBERNETES_CLIENT_TOKEN}
 --- request
 GET /queries
-["ns-a/ep:p1","ns-a/ep:p2","ns-b/ep:p1","ns-b/ep:p2","ns-c/ep:5001","ns-c/ep:5002"]
+[
+  
"first/ns-a/ep:p1","first/ns-a/ep:p2","first/ns-b/ep:p1","first/ns-b/ep:p2","first/ns-c/ep:5001","first/ns-c/ep:5002",
+  
"second/ns-a/ep:p1","second/ns-a/ep:p2","second/ns-b/ep:p1","second/ns-b/ep:p2","second/ns-c/ep:5001","second/ns-c/ep:5002"
+]
 --- more_headers
 Content-type: application/json
 --- response_body eval
-qr{ 0 0 0 0 2 2 }
---- no_error_log
-[error]
+qr{ 0 0 0 0 2 2 2 2 2 2 2 2 }
 
 
 
-=== TEST 13: use label selector
+=== TEST 10: use label selector
 --- yaml_config
 apisix:
   node_listen: 1984
@@ -606,10 +609,21 @@ apisix:
   enable_admin: false
 discovery:
   kubernetes:
-    client:
-      token_file: ${KUBERNETES_CLIENT_TOKEN_FILE}
-    label_selector: |-
-       first=1,second
+    - id: first
+      service:
+        host: ${KUBERNETES_SERVICE_HOST}
+        port: ${KUBERNETES_SERVICE_PORT}
+      client:
+        token_file: ${KUBERNETES_CLIENT_TOKEN_FILE}
+      label_selector: |-
+        first=1,second
+    - id: second
+      service:
+        schema: "http",
+        host: "127.0.0.1",
+        port: "6445"
+      client:
+        token: ${KUBERNETES_CLIENT_TOKEN}
 --- request eval
 [
 
@@ -623,37 +637,37 @@ discovery:
 
[{\"op\":\"replace_labels\",\"name\":\"ep\",\"namespace\":\"ns-c\",\"labels\":{}}]",
 
 "GET /queries
-[\"ns-a/ep:p1\",\"ns-b/ep:p1\",\"ns-c/ep:5001\"]",
+[\"first/ns-a/ep:p1\",\"first/ns-b/ep:p1\",\"first/ns-c/ep:5001\"]",
 
 "POST /operators
 
[{\"op\":\"replace_labels\",\"name\":\"ep\",\"namespace\":\"ns-a\",\"labels\":{\"first\":\"1\"
 }}]",
 
 "GET /queries
-[\"ns-a/ep:p1\",\"ns-b/ep:p1\",\"ns-c/ep:5001\"]",
+[\"first/ns-a/ep:p1\",\"first/ns-b/ep:p1\",\"first/ns-c/ep:5001\"]",
 
 "POST /operators
 
[{\"op\":\"replace_labels\",\"name\":\"ep\",\"namespace\":\"ns-b\",\"labels\":{\"first\":\"1\",\"second\":\"o\"
 }}]",
 
 "GET /queries
-[\"ns-a/ep:p1\",\"ns-b/ep:p1\",\"ns-c/ep:5001\"]",
+[\"first/ns-a/ep:p1\",\"first/ns-b/ep:p1\",\"first/ns-c/ep:5001\"]",
 
 "POST /operators
 
[{\"op\":\"replace_labels\",\"name\":\"ep\",\"namespace\":\"ns-c\",\"labels\":{\"first\":\"2\",\"second\":\"o\"
 }}]",
 
 "GET /queries
-[\"ns-a/ep:p1\",\"ns-b/ep:p1\",\"ns-c/ep:5001\"]",
+[\"first/ns-a/ep:p1\",\"first/ns-b/ep:p1\",\"first/ns-c/ep:5001\"]",
 
 "POST /operators
 
[{\"op\":\"replace_labels\",\"name\":\"ep\",\"namespace\":\"ns-c\",\"labels\":{\"first\":\"1\"
 }}]",
 
 "GET /queries
-[\"ns-a/ep:p1\",\"ns-b/ep:p1\",\"ns-c/ep:5001\"]",
+[\"first/ns-a/ep:p1\",\"first/ns-b/ep:p1\",\"first/ns-c/ep:5001\"]",
 
 "POST /operators
 
[{\"op\":\"replace_labels\",\"name\":\"ep\",\"namespace\":\"ns-c\",\"labels\":{\"first\":\"1\",\"second\":\"o\"
 }}]",
 
 "GET /queries
-[\"ns-a/ep:p1\",\"ns-b/ep:p1\",\"ns-c/ep:5001\"]",
+[\"first/ns-a/ep:p1\",\"first/ns-b/ep:p1\",\"first/ns-c/ep:5001\"]",
 
 ]
 --- response_body eval
@@ -673,42 +687,51 @@ discovery:
     "DONE\n",
     "{ 0 2 2 }\n",
 ]
---- no_error_log
-[error]
 
 
 
-=== TEST 14: scale endpoints
+=== TEST 11: scale endpoints
 --- yaml_config eval: $::yaml_config
 --- request eval
 [
+
 "GET /queries
-[\"ns-a/ep:p1\",\"ns-a/ep:p2\"]",
+[
+  \"first/ns-a/ep:p1\",\"first/ns-a/ep:p2\",
+  \"second/ns-a/ep:p1\",\"second/ns-a/ep:p2\"
+]",
 
 "POST /operators
 
[{\"op\":\"replace_subsets\",\"name\":\"ep\",\"namespace\":\"ns-a\",\"subsets\":[]}]",
 
 "GET /queries
-[\"ns-a/ep:p1\",\"ns-a/ep:p2\"]",
+[
+  \"first/ns-a/ep:p1\",\"first/ns-a/ep:p2\",
+  \"second/ns-a/ep:p1\",\"second/ns-a/ep:p2\"
+]",
 
 "GET /queries
-[\"ns-c/ep:5001\",\"ns-c/ep:5002\",\"ns-c/ep:p1\"]",
+[
+  \"first/ns-c/ep:5001\",\"first/ns-c/ep:5002\",\"first/ns-c/ep:p1\",
+  \"second/ns-c/ep:5001\",\"second/ns-c/ep:5002\",\"second/ns-c/ep:p1\"
+]",
 
 "POST /operators
 $::scale_ns_c",
 
 "GET /queries
-[\"ns-c/ep:5001\",\"ns-c/ep:5002\",\"ns-c/ep:p1\"]",
+[
+  \"first/ns-c/ep:5001\",\"first/ns-c/ep:5002\",\"first/ns-c/ep:p1\",
+  \"second/ns-c/ep:5001\",\"second/ns-c/ep:5002\",\"second/ns-c/ep:p1\"
+]"
 
 ]
 --- response_body eval
 [
-    "{ 2 2 }\n",
+    "{ 2 2 2 2 }\n",
     "DONE\n",
-    "{ 0 0 }\n",
-    "{ 2 2 0 }\n",
+    "{ 0 0 0 0 }\n",
+    "{ 2 2 0 2 2 0 }\n",
     "DONE\n",
-    "{ 0 0 1 }\n",
+    "{ 0 0 1 0 0 1 }\n",
 ]
---- no_error_log
-[error]
diff --git a/t/kubernetes/discovery/kubernetes.t 
b/t/kubernetes/discovery/kubernetes3.t
similarity index 60%
copy from t/kubernetes/discovery/kubernetes.t
copy to t/kubernetes/discovery/kubernetes3.t
index 4a7b8573b..8a71cd8cd 100644
--- a/t/kubernetes/discovery/kubernetes.t
+++ b/t/kubernetes/discovery/kubernetes3.t
@@ -16,40 +16,31 @@
 #
 
 BEGIN {
-    my $token_var_file = "/var/run/secrets/kubernetes.io/serviceaccount/token";
-    my $token_from_var = eval {`cat $token_var_file 2>/dev/null`};
-    if ($token_from_var) {
+    our $token_file = 
"/tmp/var/run/secrets/kubernetes.io/serviceaccount/token";
+    our $token_value = eval {`cat $token_file 2>/dev/null`};
 
-        our $yaml_config = <<_EOC_;
-apisix:
-  node_listen: 1984
-  config_center: yaml
-  enable_admin: false
-discovery:
-  kubernetes: {}
-_EOC_
-        our $token_file = $token_var_file;
-        our $token_value = $token_from_var;
-
-    }
-
-    my $token_tmp_file = 
"/tmp/var/run/secrets/kubernetes.io/serviceaccount/token";
-    my $token_from_tmp = eval {`cat $token_tmp_file 2>/dev/null`};
-    if ($token_from_tmp) {
-
-        our $yaml_config = <<_EOC_;
+    our $yaml_config = <<_EOC_;
 apisix:
   node_listen: 1984
   config_center: yaml
   enable_admin: false
 discovery:
   kubernetes:
-    client:
-      token_file: /tmp/var/run/secrets/kubernetes.io/serviceaccount/token
+    - id: first
+      service:
+        host: "127.0.0.1"
+        port: "6443"
+      client:
+        token_file: "/tmp/var/run/secrets/kubernetes.io/serviceaccount/token"
+    - id: second
+      service:
+        schema: "http",
+        host: "127.0.0.1",
+        port: "6445"
+      client:
+        token_file: "/tmp/var/run/secrets/kubernetes.io/serviceaccount/token"
+
 _EOC_
-        our $token_file = $token_tmp_file;
-        our $token_value = $token_from_tmp;
-    }
 
     our $scale_ns_c = <<_EOC_;
 [
@@ -81,7 +72,7 @@ _EOC_
 use t::APISIX 'no_plan';
 
 repeat_each(1);
-log_level('debug');
+log_level('warn');
 no_root_location();
 no_shuffle();
 workers(4);
@@ -199,6 +190,11 @@ _EOC_
 _EOC_
 
     $block->set_value("config", $config);
+
+    if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
+        $block->set_value("no_error_log", "[error]");
+    }
+
 });
 
 run_tests();
@@ -336,9 +332,6 @@ POST /operators
 ]
 --- more_headers
 Content-type: application/json
---- error_code: 200
---- no_error_log
-[error]
 
 
 
@@ -346,67 +339,18 @@ Content-type: application/json
 --- yaml_config eval: $::yaml_config
 --- request
 GET /queries
-["ns-a/ep:p1","ns-a/ep:p2","ns-b/ep:p1","ns-b/ep:p2","ns-c/ep:5001","ns-c/ep:5002"]
---- more_headers
-Content-type: application/json
---- response_body eval
-qr{ 2 2 2 2 2 2 }
---- no_error_log
-[error]
-
-
-
-=== TEST 3: use specify parameters
---- yaml_config
-apisix:
-  node_listen: 1984
-  config_center: yaml
-  enable_admin: false
-discovery:
-  kubernetes:
-    service:
-      host: "127.0.0.1"
-      port: "6443"
-    client:
-      token: "${KUBERNETES_CLIENT_TOKEN}"
---- request
-GET /queries
-["ns-a/ep:p1","ns-a/ep:p2","ns-b/ep:p1","ns-b/ep:p2","ns-c/ep:5001","ns-c/ep:5002"]
---- more_headers
-Content-type: application/json
---- response_body eval
-qr{ 2 2 2 2 2 2 }
---- no_error_log
-[error]
-
-
-
-=== TEST 4: use specify environment parameters
---- yaml_config
-apisix:
-  node_listen: 1984
-  config_center: yaml
-  enable_admin: false
-discovery:
-  kubernetes:
-    service:
-      host: ${KUBERNETES_SERVICE_HOST}
-      port: ${KUBERNETES_SERVICE_PORT}
-    client:
-      token: ${KUBERNETES_CLIENT_TOKEN}
---- request
-GET /queries
-["ns-a/ep:p1","ns-a/ep:p2","ns-b/ep:p1","ns-b/ep:p2","ns-c/ep:5001","ns-c/ep:5002"]
+[
+  
"first/ns-a/ep:p1","first/ns-a/ep:p2","first/ns-b/ep:p1","first/ns-b/ep:p2","first/ns-c/ep:5001","first/ns-c/ep:5002",
+  
"second/ns-a/ep:p1","second/ns-a/ep:p2","second/ns-b/ep:p1","second/ns-b/ep:p2","second/ns-c/ep:5001","second/ns-c/ep:5002"
+]
 --- more_headers
 Content-type: application/json
 --- response_body eval
-qr{ 2 2 2 2 2 2 }
---- no_error_log
-[error]
+qr{ 2 2 2 2 2 2 2 2 2 2 2 2 }
 
 
 
-=== TEST 5: use token_file
+=== TEST 3: use specify environment parameters
 --- yaml_config
 apisix:
   node_listen: 1984
@@ -414,47 +358,34 @@ apisix:
   enable_admin: false
 discovery:
   kubernetes:
-    client:
-      token_file: ${KUBERNETES_CLIENT_TOKEN_FILE}
---- request
-GET /queries
-["ns-a/ep:p1","ns-a/ep:p2","ns-b/ep:p1","ns-b/ep:p2","ns-c/ep:5001","ns-c/ep:5002"]
---- more_headers
-Content-type: application/json
---- response_body eval
-qr{ 2 2 2 2 2 2 }
---- no_error_log
-[error]
-
+    - id: first
+      service:
+        host: ${KUBERNETES_SERVICE_HOST}
+        port: ${KUBERNETES_SERVICE_PORT}
+      client:
+        token: ${KUBERNETES_CLIENT_TOKEN}
+    - id: second
+      service:
+        schema: "http",
+        host: "127.0.0.1",
+        port: "6445"
+      client:
+        token: ${KUBERNETES_CLIENT_TOKEN}
 
-
-=== TEST 6: use http
---- yaml_config
-apisix:
-  node_listen: 1984
-  config_center: yaml
-  enable_admin: false
-discovery:
-  kubernetes:
-    service:
-      schema: http
-      host: "127.0.0.1"
-      port: "6445"
-    client:
-      token: ""
 --- request
 GET /queries
-["ns-a/ep:p1","ns-a/ep:p2","ns-b/ep:p1","ns-b/ep:p2","ns-c/ep:5001","ns-c/ep:5002"]
+[
+  
"first/ns-a/ep:p1","first/ns-a/ep:p2","first/ns-b/ep:p1","first/ns-b/ep:p2","first/ns-c/ep:5001","first/ns-c/ep:5002",
+  
"second/ns-a/ep:p1","second/ns-a/ep:p2","second/ns-b/ep:p1","second/ns-b/ep:p2","second/ns-c/ep:5001","second/ns-c/ep:5002"
+]
 --- more_headers
 Content-type: application/json
 --- response_body eval
-qr{ 2 2 2 2 2 2 }
---- no_error_log
-[error]
+qr{ 2 2 2 2 2 2 2 2 2 2 2 2 }
 
 
 
-=== TEST 7: use namespace selector equal
+=== TEST 4: use namespace selector equal
 --- yaml_config
 apisix:
   node_listen: 1984
@@ -462,23 +393,35 @@ apisix:
   enable_admin: false
 discovery:
   kubernetes:
-    client:
-      token_file: ${KUBERNETES_CLIENT_TOKEN_FILE}
-    namespace_selector:
-      equal: ns-a
+    - id: first
+      service:
+        host: ${KUBERNETES_SERVICE_HOST}
+        port: ${KUBERNETES_SERVICE_PORT}
+      client:
+        token_file: ${KUBERNETES_CLIENT_TOKEN_FILE}
+      namespace_selector:
+        equal: ns-a
+    - id: second
+      service:
+        schema: "http",
+        host: "127.0.0.1",
+        port: "6445"
+      client:
+        token: ${KUBERNETES_CLIENT_TOKEN}
 --- request
 GET /queries
-["ns-a/ep:p1","ns-a/ep:p2","ns-b/ep:p1","ns-b/ep:p2","ns-c/ep:5001","ns-c/ep:5002"]
+[
+  
"first/ns-a/ep:p1","first/ns-a/ep:p2","first/ns-b/ep:p1","first/ns-b/ep:p2","first/ns-c/ep:5001","first/ns-c/ep:5002",
+  
"second/ns-a/ep:p1","second/ns-a/ep:p2","second/ns-b/ep:p1","second/ns-b/ep:p2","second/ns-c/ep:5001","second/ns-c/ep:5002"
+]
 --- more_headers
 Content-type: application/json
 --- response_body eval
-qr{ 2 2 0 0 0 0 }
---- no_error_log
-[error]
+qr{ 2 2 0 0 0 0 2 2 2 2 2 2 }
 
 
 
-=== TEST 8: use namespace selector not_equal
+=== TEST 5: use namespace selector not_equal
 --- yaml_config
 apisix:
   node_listen: 1984
@@ -486,23 +429,35 @@ apisix:
   enable_admin: false
 discovery:
   kubernetes:
-    client:
-      token_file: ${KUBERNETES_CLIENT_TOKEN_FILE}
-    namespace_selector:
-      not_equal: ns-a
+    - id: first
+      service:
+        host: ${KUBERNETES_SERVICE_HOST}
+        port: ${KUBERNETES_SERVICE_PORT}
+      client:
+        token_file: ${KUBERNETES_CLIENT_TOKEN_FILE}
+      namespace_selector:
+        not_equal: ns-a
+    - id: second
+      service:
+        schema: "http",
+        host: "127.0.0.1",
+        port: "6445"
+      client:
+        token: ${KUBERNETES_CLIENT_TOKEN}
 --- request
 GET /queries
-["ns-a/ep:p1","ns-a/ep:p2","ns-b/ep:p1","ns-b/ep:p2","ns-c/ep:5001","ns-c/ep:5002"]
+[
+  
"first/ns-a/ep:p1","first/ns-a/ep:p2","first/ns-b/ep:p1","first/ns-b/ep:p2","first/ns-c/ep:5001","first/ns-c/ep:5002",
+  
"second/ns-a/ep:p1","second/ns-a/ep:p2","second/ns-b/ep:p1","second/ns-b/ep:p2","second/ns-c/ep:5001","second/ns-c/ep:5002"
+]
 --- more_headers
 Content-type: application/json
 --- response_body eval
-qr{ 0 0 2 2 2 2 }
---- no_error_log
-[error]
+qr{ 0 0 2 2 2 2 2 2 2 2 2 2 }
 
 
 
-=== TEST 9: use namespace selector match
+=== TEST 6: use namespace selector match
 --- yaml_config
 apisix:
   node_listen: 1984
@@ -510,23 +465,35 @@ apisix:
   enable_admin: false
 discovery:
   kubernetes:
-    client:
-      token_file: ${KUBERNETES_CLIENT_TOKEN_FILE}
-    namespace_selector:
-      match: [ns-a,ns-b]
+    - id: first
+      service:
+        host: ${KUBERNETES_SERVICE_HOST}
+        port: ${KUBERNETES_SERVICE_PORT}
+      client:
+        token_file: ${KUBERNETES_CLIENT_TOKEN_FILE}
+      namespace_selector:
+        match: [ns-a,ns-b]
+    - id: second
+      service:
+        schema: "http",
+        host: "127.0.0.1",
+        port: "6445"
+      client:
+        token: ${KUBERNETES_CLIENT_TOKEN}
 --- request
 GET /queries
-["ns-a/ep:p1","ns-a/ep:p2","ns-b/ep:p1","ns-b/ep:p2","ns-c/ep:5001","ns-c/ep:5002"]
+[
+  
"first/ns-a/ep:p1","first/ns-a/ep:p2","first/ns-b/ep:p1","first/ns-b/ep:p2","first/ns-c/ep:5001","first/ns-c/ep:5002",
+  
"second/ns-a/ep:p1","second/ns-a/ep:p2","second/ns-b/ep:p1","second/ns-b/ep:p2","second/ns-c/ep:5001","second/ns-c/ep:5002"
+]
 --- more_headers
 Content-type: application/json
 --- response_body eval
-qr{ 2 2 2 2 0 0 }
---- no_error_log
-[error]
+qr{ 2 2 2 2 0 0 2 2 2 2 2 2 }
 
 
 
-=== TEST 10: use namespace selector match with regex
+=== TEST 7: use namespace selector match with regex
 --- yaml_config
 apisix:
   node_listen: 1984
@@ -534,23 +501,35 @@ apisix:
   enable_admin: false
 discovery:
   kubernetes:
-    client:
-      token_file: ${KUBERNETES_CLIENT_TOKEN_FILE}
-    namespace_selector:
-      match: ["ns-[ab]"]
+    - id: first
+      service:
+        host: ${KUBERNETES_SERVICE_HOST}
+        port: ${KUBERNETES_SERVICE_PORT}
+      client:
+        token_file: ${KUBERNETES_CLIENT_TOKEN_FILE}
+      namespace_selector:
+        match: ["ns-[ab]"]
+    - id: second
+      service:
+        schema: "http",
+        host: "127.0.0.1",
+        port: "6445"
+      client:
+        token: ${KUBERNETES_CLIENT_TOKEN}
 --- request
 GET /queries
-["ns-a/ep:p1","ns-a/ep:p2","ns-b/ep:p1","ns-b/ep:p2","ns-c/ep:5001","ns-c/ep:5002"]
+[
+  
"first/ns-a/ep:p1","first/ns-a/ep:p2","first/ns-b/ep:p1","first/ns-b/ep:p2","first/ns-c/ep:5001","first/ns-c/ep:5002",
+  
"second/ns-a/ep:p1","second/ns-a/ep:p2","second/ns-b/ep:p1","second/ns-b/ep:p2","second/ns-c/ep:5001","second/ns-c/ep:5002"
+]
 --- more_headers
 Content-type: application/json
 --- response_body eval
-qr{ 2 2 2 2 0 0 }
---- no_error_log
-[error]
+qr{ 2 2 2 2 0 0 2 2 2 2 2 2 }
 
 
 
-=== TEST 11: use namespace selector not_match
+=== TEST 8: use namespace selector not_match
 --- yaml_config
 apisix:
   node_listen: 1984
@@ -558,23 +537,35 @@ apisix:
   enable_admin: false
 discovery:
   kubernetes:
-    client:
-      token_file: ${KUBERNETES_CLIENT_TOKEN_FILE}
-    namespace_selector:
-      not_match: ["ns-a"]
+    - id: first
+      service:
+        host: ${KUBERNETES_SERVICE_HOST}
+        port: ${KUBERNETES_SERVICE_PORT}
+      client:
+        token_file: ${KUBERNETES_CLIENT_TOKEN_FILE}
+      namespace_selector:
+        not_match: ["ns-a"]
+    - id: second
+      service:
+        schema: "http",
+        host: "127.0.0.1",
+        port: "6445"
+      client:
+        token: ${KUBERNETES_CLIENT_TOKEN}
 --- request
 GET /queries
-["ns-a/ep:p1","ns-a/ep:p2","ns-b/ep:p1","ns-b/ep:p2","ns-c/ep:5001","ns-c/ep:5002"]
+[
+  
"first/ns-a/ep:p1","first/ns-a/ep:p2","first/ns-b/ep:p1","first/ns-b/ep:p2","first/ns-c/ep:5001","first/ns-c/ep:5002",
+  
"second/ns-a/ep:p1","second/ns-a/ep:p2","second/ns-b/ep:p1","second/ns-b/ep:p2","second/ns-c/ep:5001","second/ns-c/ep:5002"
+]
 --- more_headers
 Content-type: application/json
 --- response_body eval
-qr{ 0 0 2 2 2 2 }
---- no_error_log
-[error]
+qr{ 0 0 2 2 2 2 2 2 2 2 2 2 }
 
 
 
-=== TEST 12: use namespace selector not_match with regex
+=== TEST 9: use namespace selector not_match with regex
 --- yaml_config
 apisix:
   node_listen: 1984
@@ -582,23 +573,35 @@ apisix:
   enable_admin: false
 discovery:
   kubernetes:
-    client:
-      token_file: ${KUBERNETES_CLIENT_TOKEN_FILE}
-    namespace_selector:
-      not_match: ["ns-[ab]"]
+    - id: first
+      service:
+        host: ${KUBERNETES_SERVICE_HOST}
+        port: ${KUBERNETES_SERVICE_PORT}
+      client:
+        token_file: ${KUBERNETES_CLIENT_TOKEN_FILE}
+      namespace_selector:
+        not_match: ["ns-[ab]"]
+    - id: second
+      service:
+        schema: "http",
+        host: "127.0.0.1",
+        port: "6445"
+      client:
+        token: ${KUBERNETES_CLIENT_TOKEN}
 --- request
 GET /queries
-["ns-a/ep:p1","ns-a/ep:p2","ns-b/ep:p1","ns-b/ep:p2","ns-c/ep:5001","ns-c/ep:5002"]
+[
+  
"first/ns-a/ep:p1","first/ns-a/ep:p2","first/ns-b/ep:p1","first/ns-b/ep:p2","first/ns-c/ep:5001","first/ns-c/ep:5002",
+  
"second/ns-a/ep:p1","second/ns-a/ep:p2","second/ns-b/ep:p1","second/ns-b/ep:p2","second/ns-c/ep:5001","second/ns-c/ep:5002"
+]
 --- more_headers
 Content-type: application/json
 --- response_body eval
-qr{ 0 0 0 0 2 2 }
---- no_error_log
-[error]
+qr{ 0 0 0 0 2 2 2 2 2 2 2 2 }
 
 
 
-=== TEST 13: use label selector
+=== TEST 10: use label selector
 --- yaml_config
 apisix:
   node_listen: 1984
@@ -606,10 +609,21 @@ apisix:
   enable_admin: false
 discovery:
   kubernetes:
-    client:
-      token_file: ${KUBERNETES_CLIENT_TOKEN_FILE}
-    label_selector: |-
-       first=1,second
+    - id: first
+      service:
+        host: ${KUBERNETES_SERVICE_HOST}
+        port: ${KUBERNETES_SERVICE_PORT}
+      client:
+        token_file: ${KUBERNETES_CLIENT_TOKEN_FILE}
+      label_selector: |-
+        first=1,second
+    - id: second
+      service:
+        schema: "http",
+        host: "127.0.0.1",
+        port: "6445"
+      client:
+        token: ${KUBERNETES_CLIENT_TOKEN}
 --- request eval
 [
 
@@ -623,37 +637,37 @@ discovery:
 
[{\"op\":\"replace_labels\",\"name\":\"ep\",\"namespace\":\"ns-c\",\"labels\":{}}]",
 
 "GET /queries
-[\"ns-a/ep:p1\",\"ns-b/ep:p1\",\"ns-c/ep:5001\"]",
+[\"first/ns-a/ep:p1\",\"first/ns-b/ep:p1\",\"first/ns-c/ep:5001\"]",
 
 "POST /operators
 
[{\"op\":\"replace_labels\",\"name\":\"ep\",\"namespace\":\"ns-a\",\"labels\":{\"first\":\"1\"
 }}]",
 
 "GET /queries
-[\"ns-a/ep:p1\",\"ns-b/ep:p1\",\"ns-c/ep:5001\"]",
+[\"first/ns-a/ep:p1\",\"first/ns-b/ep:p1\",\"first/ns-c/ep:5001\"]",
 
 "POST /operators
 
[{\"op\":\"replace_labels\",\"name\":\"ep\",\"namespace\":\"ns-b\",\"labels\":{\"first\":\"1\",\"second\":\"o\"
 }}]",
 
 "GET /queries
-[\"ns-a/ep:p1\",\"ns-b/ep:p1\",\"ns-c/ep:5001\"]",
+[\"first/ns-a/ep:p1\",\"first/ns-b/ep:p1\",\"first/ns-c/ep:5001\"]",
 
 "POST /operators
 
[{\"op\":\"replace_labels\",\"name\":\"ep\",\"namespace\":\"ns-c\",\"labels\":{\"first\":\"2\",\"second\":\"o\"
 }}]",
 
 "GET /queries
-[\"ns-a/ep:p1\",\"ns-b/ep:p1\",\"ns-c/ep:5001\"]",
+[\"first/ns-a/ep:p1\",\"first/ns-b/ep:p1\",\"first/ns-c/ep:5001\"]",
 
 "POST /operators
 
[{\"op\":\"replace_labels\",\"name\":\"ep\",\"namespace\":\"ns-c\",\"labels\":{\"first\":\"1\"
 }}]",
 
 "GET /queries
-[\"ns-a/ep:p1\",\"ns-b/ep:p1\",\"ns-c/ep:5001\"]",
+[\"first/ns-a/ep:p1\",\"first/ns-b/ep:p1\",\"first/ns-c/ep:5001\"]",
 
 "POST /operators
 
[{\"op\":\"replace_labels\",\"name\":\"ep\",\"namespace\":\"ns-c\",\"labels\":{\"first\":\"1\",\"second\":\"o\"
 }}]",
 
 "GET /queries
-[\"ns-a/ep:p1\",\"ns-b/ep:p1\",\"ns-c/ep:5001\"]",
+[\"first/ns-a/ep:p1\",\"first/ns-b/ep:p1\",\"first/ns-c/ep:5001\"]",
 
 ]
 --- response_body eval
@@ -673,42 +687,51 @@ discovery:
     "DONE\n",
     "{ 0 2 2 }\n",
 ]
---- no_error_log
-[error]
 
 
 
-=== TEST 14: scale endpoints
+=== TEST 11: scale endpoints
 --- yaml_config eval: $::yaml_config
 --- request eval
 [
+
 "GET /queries
-[\"ns-a/ep:p1\",\"ns-a/ep:p2\"]",
+[
+  \"first/ns-a/ep:p1\",\"first/ns-a/ep:p2\",
+  \"second/ns-a/ep:p1\",\"second/ns-a/ep:p2\"
+]",
 
 "POST /operators
 
[{\"op\":\"replace_subsets\",\"name\":\"ep\",\"namespace\":\"ns-a\",\"subsets\":[]}]",
 
 "GET /queries
-[\"ns-a/ep:p1\",\"ns-a/ep:p2\"]",
+[
+  \"first/ns-a/ep:p1\",\"first/ns-a/ep:p2\",
+  \"second/ns-a/ep:p1\",\"second/ns-a/ep:p2\"
+]",
 
 "GET /queries
-[\"ns-c/ep:5001\",\"ns-c/ep:5002\",\"ns-c/ep:p1\"]",
+[
+  \"first/ns-c/ep:5001\",\"first/ns-c/ep:5002\",\"first/ns-c/ep:p1\",
+  \"second/ns-c/ep:5001\",\"second/ns-c/ep:5002\",\"second/ns-c/ep:p1\"
+]",
 
 "POST /operators
 $::scale_ns_c",
 
 "GET /queries
-[\"ns-c/ep:5001\",\"ns-c/ep:5002\",\"ns-c/ep:p1\"]",
+[
+  \"first/ns-c/ep:5001\",\"first/ns-c/ep:5002\",\"first/ns-c/ep:p1\",
+  \"second/ns-c/ep:5001\",\"second/ns-c/ep:5002\",\"second/ns-c/ep:p1\"
+]"
 
 ]
 --- response_body eval
 [
-    "{ 2 2 }\n",
+    "{ 2 2 2 2 }\n",
     "DONE\n",
-    "{ 0 0 }\n",
-    "{ 2 2 0 }\n",
+    "{ 0 0 0 0 }\n",
+    "{ 2 2 0 2 2 0 }\n",
     "DONE\n",
-    "{ 0 0 1 }\n",
+    "{ 0 0 1 0 0 1 }\n",
 ]
---- no_error_log
-[error]

Reply via email to