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

baoyuan 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 a9f184c97 fix: enable issue of endpointslices for k8s discovery 
(#11654)
a9f184c97 is described below

commit a9f184c9779f38f601346cefb5e830198c0dddba
Author: Sachin Maurya <57769917+slayer...@users.noreply.github.com>
AuthorDate: Fri Aug 15 15:01:01 2025 +0530

    fix: enable issue of endpointslices for k8s discovery (#11654)
---
 .github/workflows/kubernetes-ci.yml                |  2 +
 apisix/discovery/kubernetes/init.lua               | 59 +++++++++--------
 docs/en/latest/discovery/kubernetes.md             | 13 ++--
 docs/zh/latest/discovery/kubernetes.md             | 13 ++--
 t/kubernetes/configs/account.yaml                  |  5 +-
 .../configs/{account.yaml => endpointslices.yaml}  | 59 +++++++++++------
 t/kubernetes/discovery/kubernetes3.t               | 76 +++++++++++++++++-----
 7 files changed, 149 insertions(+), 78 deletions(-)

diff --git a/.github/workflows/kubernetes-ci.yml 
b/.github/workflows/kubernetes-ci.yml
index 16f334372..06554db58 100644
--- a/.github/workflows/kubernetes-ci.yml
+++ b/.github/workflows/kubernetes-ci.yml
@@ -53,6 +53,8 @@ jobs:
 
           kubectl apply -f ./t/kubernetes/configs/endpoint.yaml
 
+          kubectl apply -f ./t/kubernetes/configs/endpointslices.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_DIR="/tmp/var/run/secrets/kubernetes.io/serviceaccount"
diff --git a/apisix/discovery/kubernetes/init.lua 
b/apisix/discovery/kubernetes/init.lua
index 39fa69e58..695a9dd7f 100644
--- a/apisix/discovery/kubernetes/init.lua
+++ b/apisix/discovery/kubernetes/init.lua
@@ -25,6 +25,7 @@ local os = os
 local error = error
 local pcall = pcall
 local setmetatable = setmetatable
+local type = type
 local is_http = ngx.config.subsystem == "http"
 local process = require("ngx.process")
 local core = require("apisix.core")
@@ -60,32 +61,34 @@ local function on_endpoint_slices_modified(handle, endpoint)
     core.table.clear(endpoint_buffer)
 
     local endpointslices = endpoint.endpoints
-    for _, endpointslice in ipairs(endpointslices or {}) do
-        if endpointslice.addresses then
-            local addresses = endpointslices.addresses
-            for _, port in ipairs(endpoint.ports or {}) do
-                local port_name
-                if port.name then
-                    port_name = port.name
-                elseif port.targetPort then
-                    port_name = tostring(port.targetPort)
-                else
-                    port_name = tostring(port.port)
-                end
-
-                if endpointslice.conditions and endpointslice.condition.ready 
then
-                    local nodes = endpoint_buffer[port_name]
-                    if nodes == nil then
-                        nodes = core.table.new(0, #endpointslices * #addresses)
-                        endpoint_buffer[port_name] = nodes
+    if type(endpointslices) == "table" then
+        for _, endpointslice in ipairs(endpointslices) do
+            if endpointslice.addresses then
+                local addresses = endpointslice.addresses
+                for _, port in ipairs(endpoint.ports or {}) do
+                    local port_name
+                    if port.name then
+                        port_name = port.name
+                    elseif port.targetPort then
+                        port_name = tostring(port.targetPort)
+                    else
+                        port_name = tostring(port.port)
                     end
 
-                    for _, address in ipairs(endpointslices.addresses) do
-                        core.table.insert(nodes, {
-                            host = address.ip,
-                            port = port.port,
-                            weight = handle.default_weight
-                        })
+                    if endpointslice.conditions and 
endpointslice.conditions.ready then
+                        local nodes = endpoint_buffer[port_name]
+                        if nodes == nil then
+                            nodes = core.table.new(0, #endpointslices * 
#addresses)
+                            endpoint_buffer[port_name] = nodes
+                        end
+
+                        for _, address in ipairs(addresses) do
+                            core.table.insert(nodes, {
+                                host = address.ip,
+                                port = port.port,
+                                weight = handle.default_weight
+                            })
+                        end
                     end
                 end
             end
@@ -448,7 +451,7 @@ local function single_mode_init(conf)
 
     local default_weight = conf.default_weight
     local endpoints_informer, err
-    if conf.watch_endpoint_slices_schema then
+    if conf.watch_endpoint_slices then
         endpoints_informer, err = informer_factory.new("discovery.k8s.io", 
"v1",
                                                        "EndpointSlice", 
"endpointslices", "")
     else
@@ -462,7 +465,7 @@ local function single_mode_init(conf)
     setup_namespace_selector(conf, endpoints_informer)
     setup_label_selector(conf, endpoints_informer)
 
-    if conf.watch_endpoint_slices_schema then
+    if conf.watch_endpoint_slices then
         endpoints_informer.on_added = on_endpoint_slices_modified
         endpoints_informer.on_modified = on_endpoint_slices_modified
     else
@@ -554,7 +557,7 @@ local function multiple_mode_init(confs)
         local default_weight = conf.default_weight
 
         local endpoints_informer, err
-        if conf.watch_endpoint_slices_schema then
+        if conf.watch_endpoint_slices then
             endpoints_informer, err = informer_factory.new("discovery.k8s.io", 
"v1",
                                                            "EndpointSlice", 
"endpointslices", "")
         else
@@ -568,7 +571,7 @@ local function multiple_mode_init(confs)
         setup_namespace_selector(conf, endpoints_informer)
         setup_label_selector(conf, endpoints_informer)
 
-        if conf.watch_endpoint_slices_schema then
+        if conf.watch_endpoint_slices then
             endpoints_informer.on_added = on_endpoint_slices_modified
             endpoints_informer.on_modified = on_endpoint_slices_modified
         else
diff --git a/docs/en/latest/discovery/kubernetes.md 
b/docs/en/latest/discovery/kubernetes.md
index f2000a144..728e2e332 100644
--- a/docs/en/latest/discovery/kubernetes.md
+++ b/docs/en/latest/discovery/kubernetes.md
@@ -302,7 +302,7 @@ A: The Kubernetes service discovery only uses privileged 
processes to [_List-Wat
 
 **Q: What permissions do 
[_ServiceAccount_](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/)
 require?**
 
-A: ServiceAccount requires the permissions of cluster-level [ get, list, watch 
] endpoints resources, the declarative definition is as follows:
+A: ServiceAccount requires the permissions of cluster-level [ get, list, watch 
] endpoints and endpointslices resources, the declarative definition is as 
follows:
 
 ```yaml
 kind: ServiceAccount
@@ -315,11 +315,14 @@ metadata:
 kind: ClusterRole
 apiVersion: rbac.authorization.k8s.io/v1
 metadata:
- name: apisix-test
+  name: apisix-test
 rules:
-- apiGroups: [ "" ]
-  resources: [ endpoints,endpointslices ]
-  verbs: [ get,list,watch ]
+  - apiGroups: [ "" ]
+    resources: [ endpoints]
+    verbs: [ get,list,watch ]
+  - apiGroups: [ "discovery.k8s.io" ]
+    resources: [ endpointslices ]
+    verbs: [ get,list,watch ]
 ---
 
 apiVersion: rbac.authorization.k8s.io/v1
diff --git a/docs/zh/latest/discovery/kubernetes.md 
b/docs/zh/latest/discovery/kubernetes.md
index e10a53972..9e0b721cc 100644
--- a/docs/zh/latest/discovery/kubernetes.md
+++ b/docs/zh/latest/discovery/kubernetes.md
@@ -300,7 +300,7 @@ A: Kubernetes 服务发现只使用特权进程监听 Kubernetes Endpoints,然
 
 **Q: 
[_ServiceAccount_](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/)
 需要的权限有哪些?**
 
-A: 
[_ServiceAccount_](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/)
 需要集群级 [ get,list,watch ] endpoints 资源的的权限,其声明式定义如下:
+A: 
[_ServiceAccount_](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/)
 需要集群级 [ get,list,watch ] endpoints 和 endpointslices 资源的的权限,其声明式定义如下:
 
 ```yaml
 kind: ServiceAccount
@@ -313,11 +313,14 @@ metadata:
 kind: ClusterRole
 apiVersion: rbac.authorization.k8s.io/v1
 metadata:
- name: apisix-test
+  name: apisix-test
 rules:
-- apiGroups: [ "" ]
-  resources: [ endpoints,endpointslices ]
-  verbs: [ get,list,watch ]
+  - apiGroups: [ "" ]
+    resources: [ endpoints]
+    verbs: [ get,list,watch ]
+  - apiGroups: [ "discovery.k8s.io" ]
+    resources: [ endpointslices ]
+    verbs: [ get,list,watch ]
 ---
 
 apiVersion: rbac.authorization.k8s.io/v1
diff --git a/t/kubernetes/configs/account.yaml 
b/t/kubernetes/configs/account.yaml
index da7cf01f5..d461183b7 100644
--- a/t/kubernetes/configs/account.yaml
+++ b/t/kubernetes/configs/account.yaml
@@ -27,7 +27,10 @@ metadata:
   name: apisix-test
 rules:
   - apiGroups: [ "" ]
-    resources: [ endpoints ]
+    resources: [ endpoints]
+    verbs: [ get,list,watch ]
+  - apiGroups: [ "discovery.k8s.io" ]
+    resources: [ endpointslices ]
     verbs: [ get,list,watch ]
 ---
 apiVersion: rbac.authorization.k8s.io/v1
diff --git a/t/kubernetes/configs/account.yaml 
b/t/kubernetes/configs/endpointslices.yaml
similarity index 61%
copy from t/kubernetes/configs/account.yaml
copy to t/kubernetes/configs/endpointslices.yaml
index da7cf01f5..d22851233 100644
--- a/t/kubernetes/configs/account.yaml
+++ b/t/kubernetes/configs/endpointslices.yaml
@@ -15,30 +15,47 @@
 # limitations under the License.
 #
 
-kind: ServiceAccount
+kind: Namespace
 apiVersion: v1
 metadata:
-  name: apisix-test
-  namespace: default
+  name: ns-a
 ---
-kind: ClusterRole
-apiVersion: rbac.authorization.k8s.io/v1
+
+kind: EndpointSlice
+apiVersion: discovery.k8s.io/v1
+metadata:
+  name: epslice
+  namespace: ns-a
+addressType: IPv4
+endpoints: [ ]
+---
+
+kind: Namespace
+apiVersion: v1
+metadata:
+  name: ns-b
+---
+
+kind: EndpointSlice
+apiVersion: discovery.k8s.io/v1
 metadata:
-  name: apisix-test
-rules:
-  - apiGroups: [ "" ]
-    resources: [ endpoints ]
-    verbs: [ get,list,watch ]
+  name: epslice
+  namespace: ns-b
+addressType: IPv4
+endpoints: [ ]
 ---
-apiVersion: rbac.authorization.k8s.io/v1
-kind: ClusterRoleBinding
+
+kind: Namespace
+apiVersion: v1
 metadata:
-  name: apisix-test
-roleRef:
-  apiGroup: rbac.authorization.k8s.io
-  kind: ClusterRole
-  name: apisix-test
-subjects:
-  - kind: ServiceAccount
-    name: apisix-test
-    namespace: default
+  name: ns-c
+---
+
+kind: EndpointSlice
+apiVersion: discovery.k8s.io/v1
+metadata:
+  name: epslice
+  namespace: ns-c
+addressType: IPv4
+endpoints: [ ]
+---
diff --git a/t/kubernetes/discovery/kubernetes3.t 
b/t/kubernetes/discovery/kubernetes3.t
index 37a06b00f..60b224820 100644
--- a/t/kubernetes/discovery/kubernetes3.t
+++ b/t/kubernetes/discovery/kubernetes3.t
@@ -165,11 +165,11 @@ _EOC_
 
                     if op.op == "replace_endpointslices" then
                         method = "PATCH"
-                        path = "/apis/discovery.k8s.io/namespaces/" .. 
op.namespace .. "/endpointslices/" .. op.name
+                        path = "/apis/discovery.k8s.io/v1/namespaces/" .. 
op.namespace .. "/endpointslices/" .. op.name
                         if #op.endpoints == 0 then
                             body = 
'[{"path":"/endpoints","op":"replace","value":[]}]'
                         else
-                            local t = { { op = "replace", path = "/endpoints", 
value = op.endpoints } }
+                            local t = { { op = "replace", path = "/endpoints", 
value = op.endpoints }, { op = "replace", path = "/ports", value = op.ports } }
                             body = core.json.encode(t, true)
                         end
                         headers["Content-Type"] = "application/json-patch+json"
@@ -177,7 +177,7 @@ _EOC_
 
                     if op.op == "replace_labels" then
                         method = "PATCH"
-                        path = "/apis/discovery.k8s.io/namespaces/" .. 
op.namespace .. "/endpointslices/" .. op.name
+                        path = "/apis/discovery.k8s.io/v1/namespaces/" .. 
op.namespace .. "/endpointslices/" .. 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"
@@ -260,7 +260,7 @@ POST /operators
     {
         "op": "replace_endpointslices",
         "namespace": "ns-a",
-        "name": "ep",
+        "name": "epslice",
         "endpoints": [
             {
                 "addresses": [
@@ -289,7 +289,7 @@ POST /operators
         ],
         "ports": [
             {
-                "name": "p",
+                "name": "p1",
                 "port": 5001
             }
         ]
@@ -301,7 +301,7 @@ POST /operators
     {
         "op": "replace_endpointslices",
         "namespace": "ns-b",
-        "name": "ep",
+        "name": "epslice",
         "endpoints": [
             {
                 "addresses": [
@@ -330,7 +330,7 @@ POST /operators
         ],
         "ports": [
             {
-                "name": "p",
+                "name": "p2",
                 "port": 5002
             }
         ]
@@ -342,7 +342,7 @@ POST /operators
     {
         "op": "replace_endpointslices",
         "namespace": "ns-c",
-        "name": "ep",
+        "name": "epslice",
         "endpoints": [
             {
                 "addresses": [
@@ -362,7 +362,7 @@ POST /operators
                     "20.0.0.2"
                 ],
                 "conditions": {
-                    "ready": true,
+                    "ready": false,
                     "serving": true,
                     "terminating": false
                 },
@@ -371,7 +371,7 @@ POST /operators
         ],
         "ports": [
             {
-                "name": "p",
+                "name": "p3",
                 "port": 5003
             }
         ]
@@ -389,13 +389,13 @@ Content-type: application/json
 --- request
 GET /queries
 [
-  
"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"
+  
"first/ns-a/epslice:p1","first/ns-a/epslice:p1","first/ns-b/epslice:p2","first/ns-b/epslice:p2","first/ns-c/epslice:p3","first/ns-c/epslice:p3",
+  
"second/ns-a/epslice:p1","second/ns-a/epslice:p1","second/ns-b/epslice:p2","second/ns-b/epslice:p2","second/ns-c/epslice:p3","second/ns-c/epslice:p3"
 ]
 --- more_headers
 Content-type: application/json
 --- response_body eval
-qr{ 0 0 2 2 0 0 0 0 2 2 0 0 }
+qr{ 2 2 2 2 2 2 2 2 2 2 2 2 }
 
 
 
@@ -428,17 +428,57 @@ discovery:
 --- request
 GET /queries
 [
-  
"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"
+  
"first/ns-a/epslice:p1","first/ns-a/epslice:p1","first/ns-b/epslice:p2","first/ns-b/epslice:p2","first/ns-c/epslice:p3","first/ns-c/epslice:p3",
+  
"second/ns-a/epslice:p1","second/ns-a/epslice:p1","second/ns-b/epslice:p2","second/ns-b/epslice:p2","second/ns-c/epslice:p3","second/ns-c/epslice:p3"
+]
+--- more_headers
+Content-type: application/json
+--- response_body eval
+qr{ 2 2 2 2 2 2 2 2 2 2 2 2 }
+
+
+
+=== TEST 4: use namespace selector equal
+--- yaml_config
+apisix:
+  node_listen: 1984
+deployment:
+  role: data_plane
+  role_data_plane:
+    config_provider: yaml
+discovery:
+  kubernetes:
+    - id: first
+      service:
+        host: ${KUBERNETES_SERVICE_HOST}
+        port: ${KUBERNETES_SERVICE_PORT}
+      client:
+        token_file: ${KUBERNETES_CLIENT_TOKEN_FILE}
+      watch_endpoint_slices: true
+      namespace_selector:
+        equal: ns-a
+    - id: second
+      service:
+        schema: "http"
+        host: "127.0.0.1"
+        port: "6445"
+      watch_endpoint_slices: true
+      client:
+        token: ${KUBERNETES_CLIENT_TOKEN}
+--- request
+GET /queries
+[
+  
"first/ns-a/epslice:p1","first/ns-a/epslice:p1","first/ns-b/epslice:p2","first/ns-b/epslice:p2","first/ns-c/epslice:p3","first/ns-c/epslice:p3",
+  
"second/ns-a/epslice:p1","second/ns-a/epslice:p1","second/ns-b/epslice:p2","second/ns-b/epslice:p2","second/ns-c/epslice:p3","second/ns-c/epslice:p3"
 ]
 --- more_headers
 Content-type: application/json
 --- response_body eval
-qr{ 0 0 2 2 0 0 0 0 2 2 0 0 }
+qr{ 2 2 0 0 0 0 2 2 2 2 2 2 }
 
 
 
-=== TEST 4: test dump
+=== TEST 5: test dump
 --- yaml_config eval: $::yaml_config
 --- request
 GET /dump
@@ -447,7 +487,7 @@ GET /dump
 
 
 
-=== TEST 5: test single mode dump
+=== TEST 6: test single mode dump
 --- yaml_config eval: $::single_yaml_config
 --- request
 GET /dump

Reply via email to