Skip to content

Commit 46176f2

Browse files
authored
fix: correct handling of endpointSlices in Kubernetes service discovery (#12634)
1 parent 0b959f5 commit 46176f2

4 files changed

Lines changed: 742 additions & 81 deletions

File tree

apisix/discovery/kubernetes/init.lua

Lines changed: 163 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
--
1717

1818
local ngx = ngx
19+
local unpack = unpack
1920
local ipairs = ipairs
2021
local pairs = pairs
2122
local string = string
@@ -25,7 +26,6 @@ local os = os
2526
local error = error
2627
local pcall = pcall
2728
local setmetatable = setmetatable
28-
local type = type
2929
local is_http = ngx.config.subsystem == "http"
3030
local process = require("ngx.process")
3131
local core = require("apisix.core")
@@ -42,6 +42,7 @@ local endpoint_lrucache = core.lrucache.new({
4242
})
4343

4444
local endpoint_buffer = {}
45+
local kubernetes_service_name_label = "kubernetes.io/service-name"
4546

4647
local function sort_nodes_cmp(left, right)
4748
if left.host ~= right.host then
@@ -51,69 +52,133 @@ local function sort_nodes_cmp(left, right)
5152
return left.port < right.port
5253
end
5354

54-
local function on_endpoint_slices_modified(handle, endpoint, operate)
55-
if handle.namespace_selector and
56-
not handle:namespace_selector(endpoint.metadata.namespace) then
57-
return
55+
local function update_endpoint_slices_cache(handle, endpoint_key, slice, slice_name)
56+
if not handle.endpoint_slices_cache[endpoint_key] then
57+
handle.endpoint_slices_cache[endpoint_key] = {}
5858
end
59+
local endpoint_slices = handle.endpoint_slices_cache[endpoint_key]
60+
endpoint_slices[slice_name] = slice
61+
end
5962

60-
core.log.debug(core.json.delay_encode(endpoint))
61-
core.table.clear(endpoint_buffer)
62-
63-
local endpointslices = endpoint.endpoints
64-
if type(endpointslices) == "table" then
65-
for _, endpointslice in ipairs(endpointslices) do
66-
if endpointslice.addresses then
67-
local addresses = endpointslice.addresses
68-
for _, port in ipairs(endpoint.ports or {}) do
69-
local port_name
70-
if port.name then
71-
port_name = port.name
72-
elseif port.targetPort then
73-
port_name = tostring(port.targetPort)
74-
else
75-
port_name = tostring(port.port)
76-
end
77-
78-
if endpointslice.conditions and endpointslice.conditions.ready then
79-
local nodes = endpoint_buffer[port_name]
80-
if nodes == nil then
81-
nodes = core.table.new(0, #endpointslices * #addresses)
82-
endpoint_buffer[port_name] = nodes
83-
end
84-
85-
for _, address in ipairs(addresses) do
86-
core.table.insert(nodes, {
87-
host = address.ip,
88-
port = port.port,
89-
weight = handle.default_weight
90-
})
91-
end
92-
end
93-
end
63+
local function get_endpoints_from_cache(handle, endpoint_key)
64+
local endpoint_slices = handle.endpoint_slices_cache[endpoint_key] or {}
65+
local endpoints = {}
66+
for _, endpoint_slice in pairs(endpoint_slices) do
67+
for port, targets in pairs(endpoint_slice) do
68+
if not endpoints[port] then
69+
endpoints[port] = core.table.new(0, #targets)
9470
end
71+
core.table.insert_tail(endpoints[port], unpack(targets))
9572
end
9673
end
9774

98-
for _, ports in pairs(endpoint_buffer) do
99-
for _, nodes in pairs(ports) do
100-
core.table.sort(nodes, sort_nodes_cmp)
101-
end
102-
end
103-
local endpoint_key = endpoint.metadata.namespace .. "/" .. endpoint.metadata.name
104-
local endpoint_content = core.json.encode(endpoint_buffer, true)
105-
local endpoint_version = ngx.crc32_long(endpoint_content)
75+
return endpoints
76+
end
10677

78+
local function update_endpoint_dict(handle, endpoints, endpoint_key)
79+
local endpoint_content = core.json.encode(endpoints, true)
80+
local endpoint_version = ngx.crc32_long(endpoint_content)
10781
local _, err
10882
_, err = handle.endpoint_dict:safe_set(endpoint_key .. "#version", endpoint_version)
10983
if err then
110-
core.log.error("set endpoint version into discovery DICT failed, ", err)
111-
return
84+
return false, "set endpoint version into discovery DICT failed, " .. err
11285
end
11386
_, err = handle.endpoint_dict:safe_set(endpoint_key, endpoint_content)
11487
if err then
115-
core.log.error("set endpoint into discovery DICT failed, ", err)
11688
handle.endpoint_dict:delete(endpoint_key .. "#version")
89+
return false, "set endpoint into discovery DICT failed, " .. err
90+
end
91+
92+
return true
93+
end
94+
95+
local function validate_endpoint_slice(endpoint_slice)
96+
if not endpoint_slice.metadata then
97+
return false, "endpoint_slice has no metadata, endpointSlice: "
98+
.. core.json.encode(endpoint_slice)
99+
end
100+
if not endpoint_slice.metadata.name then
101+
return false, "endpoint_slice has no metadata.name, endpointSlice: "
102+
.. core.json.encode(endpoint_slice)
103+
end
104+
if not endpoint_slice.metadata.namespace then
105+
return false, "endpoint_slice has no metadata.namespace, endpointSlice: "
106+
.. core.json.encode(endpoint_slice)
107+
end
108+
if not endpoint_slice.metadata.labels
109+
or not endpoint_slice.metadata.labels[kubernetes_service_name_label] then
110+
return false, "endpoint_slice has no service-name, endpointSlice: "
111+
.. core.json.encode(endpoint_slice)
112+
end
113+
114+
return true
115+
end
116+
117+
local function on_endpoint_slices_modified(handle, endpoint_slice, operate)
118+
local ok, err = validate_endpoint_slice(endpoint_slice)
119+
if not ok then
120+
core.log.error("endpoint_slice validation fail: ", err)
121+
return
122+
end
123+
if handle.namespace_selector and
124+
not handle:namespace_selector(endpoint_slice.metadata.namespace) then
125+
return
126+
end
127+
128+
core.log.debug("get endpoint_slice: ", core.json.delay_encode(endpoint_slice))
129+
--record nodes to every port in service
130+
local port_to_nodes = {}
131+
132+
local slice_endpoints = endpoint_slice.endpoints
133+
if not slice_endpoints or slice_endpoints == ngx.null then
134+
slice_endpoints = {}
135+
end
136+
137+
for _, endpoint in ipairs(slice_endpoints) do
138+
if endpoint.addresses
139+
and endpoint.conditions
140+
and endpoint.conditions.ready then
141+
local addresses = endpoint.addresses
142+
for _, port in ipairs(endpoint_slice.ports or {}) do
143+
local port_name
144+
if port.name then
145+
port_name = port.name
146+
elseif port.targetPort then
147+
port_name = tostring(port.targetPort)
148+
else
149+
port_name = tostring(port.port)
150+
end
151+
152+
local nodes = port_to_nodes[port_name]
153+
if nodes == nil then
154+
nodes = core.table.new(0, #slice_endpoints * #addresses)
155+
port_to_nodes[port_name] = nodes
156+
end
157+
158+
for _, ip in ipairs(addresses) do
159+
core.table.insert(nodes, {
160+
host = ip,
161+
port = port.port,
162+
weight = handle.default_weight
163+
})
164+
end
165+
end
166+
end
167+
end
168+
169+
local endpoint_key = endpoint_slice.metadata.namespace
170+
.. "/" .. endpoint_slice.metadata.labels[kubernetes_service_name_label]
171+
update_endpoint_slices_cache(handle, endpoint_key, port_to_nodes, endpoint_slice.metadata.name)
172+
173+
local cached_endpoints = get_endpoints_from_cache(handle, endpoint_key)
174+
for _, nodes in pairs(cached_endpoints) do
175+
core.table.sort(nodes, sort_nodes_cmp)
176+
end
177+
178+
local ok, err = update_endpoint_dict(handle, cached_endpoints, endpoint_key)
179+
if not ok then
180+
core.log.error("failed to update endpoint dict for endpoint: ", endpoint_key,
181+
", err: ", err)
117182
return
118183
end
119184
if operate == "list" then
@@ -122,6 +187,36 @@ local function on_endpoint_slices_modified(handle, endpoint, operate)
122187
end
123188
end
124189

190+
local function on_endpoint_slices_deleted(handle, endpoint_slice)
191+
local ok, err = validate_endpoint_slice(endpoint_slice)
192+
if not ok then
193+
core.log.error("endpoint_slice validation fail: ", err)
194+
return
195+
end
196+
197+
if handle.namespace_selector and
198+
not handle:namespace_selector(endpoint_slice.metadata.namespace) then
199+
return
200+
end
201+
202+
core.log.debug("delete endpoint_slice: ", core.json.delay_encode(endpoint_slice))
203+
204+
local endpoint_key = endpoint_slice.metadata.namespace
205+
.. "/" .. endpoint_slice.metadata.labels[kubernetes_service_name_label]
206+
update_endpoint_slices_cache(handle, endpoint_key, nil, endpoint_slice.metadata.name)
207+
208+
local cached_endpoints = get_endpoints_from_cache(handle, endpoint_key)
209+
for _, nodes in pairs(cached_endpoints) do
210+
core.table.sort(nodes, sort_nodes_cmp)
211+
end
212+
213+
ok, err = update_endpoint_dict(handle, cached_endpoints, endpoint_key)
214+
if not ok then
215+
core.log.error("failed to update endpoint dict for endpoint: ", endpoint_key,
216+
", err: ", err)
217+
end
218+
end
219+
125220
local function on_endpoint_modified(handle, endpoint, operate)
126221
if handle.namespace_selector and
127222
not handle:namespace_selector(endpoint.metadata.namespace) then
@@ -162,26 +257,16 @@ local function on_endpoint_modified(handle, endpoint, operate)
162257
end
163258
end
164259

165-
for _, ports in pairs(endpoint_buffer) do
166-
for _, nodes in pairs(ports) do
167-
core.table.sort(nodes, sort_nodes_cmp)
168-
end
260+
261+
for _, nodes in pairs(endpoint_buffer) do
262+
core.table.sort(nodes, sort_nodes_cmp)
169263
end
170264

171265
local endpoint_key = endpoint.metadata.namespace .. "/" .. endpoint.metadata.name
172-
local endpoint_content = core.json.encode(endpoint_buffer, true)
173-
local endpoint_version = ngx.crc32_long(endpoint_content)
174-
175-
local _, err
176-
_, err = handle.endpoint_dict:safe_set(endpoint_key .. "#version", endpoint_version)
177-
if err then
178-
core.log.error("set endpoint version into discovery DICT failed, ", err)
179-
return
180-
end
181-
_, err = handle.endpoint_dict:safe_set(endpoint_key, endpoint_content)
182-
if err then
183-
core.log.error("set endpoint into discovery DICT failed, ", err)
184-
handle.endpoint_dict:delete(endpoint_key .. "#version")
266+
local ok, err = update_endpoint_dict(handle, endpoint_buffer, endpoint_key)
267+
if not ok then
268+
core.log.error("failed to update endpoint dict for endpoint: ", endpoint_key,
269+
", err: ", err)
185270
return
186271
end
187272
if operate == "list" then
@@ -207,6 +292,9 @@ end
207292
local function pre_list(handle)
208293
handle.current_keys_hash = {}
209294
handle.existing_keys = handle.endpoint_dict:get_keys(0)
295+
if handle.endpoint_slices_cache then
296+
handle.endpoint_slices_cache = {}
297+
end
210298
end
211299

212300

@@ -499,11 +587,14 @@ local function single_mode_init(conf)
499587
if conf.watch_endpoint_slices then
500588
endpoints_informer.on_added = on_endpoint_slices_modified
501589
endpoints_informer.on_modified = on_endpoint_slices_modified
590+
endpoints_informer.on_deleted = on_endpoint_slices_deleted
591+
endpoints_informer.endpoint_slices_cache = {}
502592
else
503593
endpoints_informer.on_added = on_endpoint_modified
504594
endpoints_informer.on_modified = on_endpoint_modified
595+
endpoints_informer.on_deleted = on_endpoint_deleted
505596
end
506-
endpoints_informer.on_deleted = on_endpoint_deleted
597+
507598
endpoints_informer.pre_list = pre_list
508599
endpoints_informer.post_list = post_list
509600

@@ -605,11 +696,14 @@ local function multiple_mode_init(confs)
605696
if conf.watch_endpoint_slices then
606697
endpoints_informer.on_added = on_endpoint_slices_modified
607698
endpoints_informer.on_modified = on_endpoint_slices_modified
699+
endpoints_informer.on_deleted = on_endpoint_slices_deleted
700+
endpoints_informer.endpoint_slices_cache = {}
608701
else
609702
endpoints_informer.on_added = on_endpoint_modified
610703
endpoints_informer.on_modified = on_endpoint_modified
704+
endpoints_informer.on_deleted = on_endpoint_deleted
611705
end
612-
endpoints_informer.on_deleted = on_endpoint_deleted
706+
613707
endpoints_informer.pre_list = pre_list
614708
endpoints_informer.post_list = post_list
615709

t/kubernetes/configs/endpointslices.yaml

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,10 @@ metadata:
2424
kind: EndpointSlice
2525
apiVersion: discovery.k8s.io/v1
2626
metadata:
27-
name: epslice
27+
name: service-a-epslice1
2828
namespace: ns-a
29+
labels:
30+
"kubernetes.io/service-name": service-a
2931
addressType: IPv4
3032
endpoints: [ ]
3133
---
@@ -39,8 +41,10 @@ metadata:
3941
kind: EndpointSlice
4042
apiVersion: discovery.k8s.io/v1
4143
metadata:
42-
name: epslice
44+
name: service-a-epslice1
4345
namespace: ns-b
46+
labels:
47+
"kubernetes.io/service-name": service-a
4448
addressType: IPv4
4549
endpoints: [ ]
4650
---
@@ -54,8 +58,10 @@ metadata:
5458
kind: EndpointSlice
5559
apiVersion: discovery.k8s.io/v1
5660
metadata:
57-
name: epslice
61+
name: service-a-epslice1
5862
namespace: ns-c
63+
labels:
64+
"kubernetes.io/service-name": service-a
5965
addressType: IPv4
6066
endpoints: [ ]
6167
---

0 commit comments

Comments
 (0)