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