Skip to content

Commit 3300db0

Browse files
zhixiongdu027spacewander
authored andcommitted
feat: improve kubernetes discovery (#6663)
1 parent 724ea22 commit 3300db0

File tree

9 files changed

+296
-32
lines changed

9 files changed

+296
-32
lines changed

apisix/cli/ngx_tpl.lua

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,10 @@ http {
190190
lua_shared_dict balancer-ewma-last-touched-at {* http.lua_shared_dict["balancer-ewma-last-touched-at"] *};
191191
lua_shared_dict etcd-cluster-health-check {* http.lua_shared_dict["etcd-cluster-health-check"] *}; # etcd health check
192192
193+
{% if enabled_discoveries["kubernetes"] then %}
194+
lua_shared_dict kubernetes {* http.lua_shared_dict["kubernetes"] *};
195+
{% end %}
196+
193197
{% if enabled_plugins["limit-conn"] then %}
194198
lua_shared_dict plugin-limit-conn {* http.lua_shared_dict["plugin-limit-conn"] *};
195199
{% end %}

apisix/cli/ops.lua

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,11 @@ Please modify "admin_key" in conf/config.yaml .
251251
use_apisix_openresty = false
252252
end
253253

254+
local enabled_discoveries = {}
255+
for name in pairs(yaml_conf.discovery or {}) do
256+
enabled_discoveries[name] = true
257+
end
258+
254259
local enabled_plugins = {}
255260
for i, name in ipairs(yaml_conf.plugins or {}) do
256261
enabled_plugins[name] = true
@@ -528,6 +533,7 @@ Please modify "admin_key" in conf/config.yaml .
528533
with_module_status = with_module_status,
529534
use_apisix_openresty = use_apisix_openresty,
530535
error_log = {level = "warn"},
536+
enabled_discoveries = enabled_discoveries,
531537
enabled_plugins = enabled_plugins,
532538
enabled_stream_plugins = enabled_stream_plugins,
533539
dubbo_upstream_multiplex_count = dubbo_upstream_multiplex_count,

apisix/discovery/kubernetes/informer_factory.lua

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,6 @@ local type = type
2323
local core = require("apisix.core")
2424
local http = require("resty.http")
2525

26-
local empty_table = {}
27-
2826
local function list_query(informer)
2927
local arguments = {
3028
limit = informer.limit,
@@ -81,7 +79,7 @@ local function list(httpc, apiserver, informer)
8179
informer.version = data.metadata.resourceVersion
8280

8381
if informer.on_added then
84-
for _, item in ipairs(data.items or empty_table) do
82+
for _, item in ipairs(data.items or {}) do
8583
informer:on_added(item, "list")
8684
end
8785
end

apisix/discovery/kubernetes/init.lua

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ local local_conf = require("apisix.core.config_local").local_conf()
3131
local informer_factory = require("apisix.discovery.kubernetes.informer_factory")
3232

3333
local endpoint_dict
34+
3435
local default_weight
3536

3637
local endpoint_lrucache = core.lrucache.new({
@@ -39,7 +40,6 @@ local endpoint_lrucache = core.lrucache.new({
3940
})
4041

4142
local endpoint_buffer = {}
42-
local empty_table = {}
4343

4444
local function sort_nodes_cmp(left, right)
4545
if left.host ~= right.host then
@@ -60,10 +60,10 @@ local function on_endpoint_modified(informer, endpoint)
6060
core.table.clear(endpoint_buffer)
6161

6262
local subsets = endpoint.subsets
63-
for _, subset in ipairs(subsets or empty_table) do
63+
for _, subset in ipairs(subsets or {}) do
6464
if subset.addresses then
6565
local addresses = subset.addresses
66-
for _, port in ipairs(subset.ports or empty_table) do
66+
for _, port in ipairs(subset.ports or {}) do
6767
local port_name
6868
if port.name then
6969
port_name = port.name
@@ -166,7 +166,7 @@ local function setup_namespace_selector(conf, informer)
166166
local match = conf.namespace_selector.match
167167
local m, err
168168
for _, v in ipairs(match) do
169-
m, err = ngx.re.match(namespace, v, "j")
169+
m, err = ngx.re.match(namespace, v, "jo")
170170
if m and m[0] == namespace then
171171
return true
172172
end
@@ -324,10 +324,9 @@ end
324324

325325

326326
function _M.init_worker()
327-
-- TODO: maybe we can read dict name from discovery config
328-
endpoint_dict = ngx.shared.discovery
327+
endpoint_dict = ngx.shared.kubernetes
329328
if not endpoint_dict then
330-
error("failed to get nginx shared dict: discovery, please check your APISIX version")
329+
error("failed to get lua_shared_dict: kubernetes, please check your APISIX version")
331330
end
332331

333332
if process.type() ~= "privileged agent" then
@@ -336,7 +335,7 @@ function _M.init_worker()
336335

337336
local discovery_conf = local_conf.discovery.kubernetes
338337

339-
default_weight = discovery_conf.default_weight or 50
338+
default_weight = discovery_conf.default_weight
340339

341340
local apiserver, err = get_apiserver(discovery_conf)
342341
if err then

conf/config-default.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,7 @@ nginx_config: # config for render the template to generate n
264264
introspection: 10m
265265
access-tokens: 1m
266266
ext-plugin: 1m
267+
kubernetes: 1m
267268

268269
etcd:
269270
host: # it's possible to define multiple etcd hosts addresses of the same etcd cluster.

docs/en/latest/config.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,8 @@
199199
"discovery/dns",
200200
"discovery/consul_kv",
201201
"discovery/nacos",
202-
"discovery/eureka"
202+
"discovery/eureka",
203+
"discovery/kubernetes"
203204
]
204205
},
205206
{
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
---
2+
title: Kubernetes
3+
---
4+
5+
<!--
6+
#
7+
# Licensed to the Apache Software Foundation (ASF) under one or more
8+
# contributor license agreements. See the NOTICE file distributed with
9+
# this work for additional information regarding copyright ownership.
10+
# The ASF licenses this file to You under the Apache License, Version 2.0
11+
# (the "License"); you may not use this file except in compliance with
12+
# the License. You may obtain a copy of the License at
13+
#
14+
# http://www.apache.org/licenses/LICENSE-2.0
15+
#
16+
# Unless required by applicable law or agreed to in writing, software
17+
# distributed under the License is distributed on an "AS IS" BASIS,
18+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19+
# See the License for the specific language governing permissions and
20+
# limitations under the License.
21+
#
22+
-->
23+
24+
## Summary
25+
26+
The [_Kubernetes_](https://kubernetes.io/) service discovery [_List-Watch_](https://kubernetes.io/docs/reference/using-api/api-concepts/) real-time changes of [_Endpoints_](https://kubernetes.io/docs/concepts/services-networking/service/) resources,
27+
then store theirs value into ngx.shared.kubernetes \
28+
Discovery also provides a query interface in accordance with the [_APISIX Discovery Specification_](https://github.com/apache/apisix/blob/master/docs/en/latest/discovery.md)
29+
30+
## Configuration
31+
32+
A detailed configuration for the kubernetes service discovery is as follows:
33+
34+
```yaml
35+
discovery:
36+
kubernetes:
37+
service:
38+
# apiserver schema, options [http, https]
39+
schema: https #default https
40+
41+
# apiserver host, options [ipv4, ipv6, domain, environment variable]
42+
host: ${KUBERNETES_SERVICE_HOST} #default ${KUBERNETES_SERVICE_HOST}
43+
44+
# apiserver port, options [port number, environment variable]
45+
port: ${KUBERNETES_SERVICE_PORT} #default ${KUBERNETES_SERVICE_PORT}
46+
47+
client:
48+
# serviceaccount token or token_file
49+
token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
50+
51+
#token: |-
52+
# eyJhbGciOiJSUzI1NiIsImtpZCI6Ikx5ME1DNWdnbmhQNkZCNlZYMXBsT3pYU3BBS2swYzBPSkN3ZnBESGpkUEEif
53+
# 6Ikx5ME1DNWdnbmhQNkZCNlZYMXBsT3pYU3BBS2swYzBPSkN3ZnBESGpkUEEifeyJhbGciOiJSUzI1NiIsImtpZCI
54+
55+
# kubernetes discovery plugin support use namespace_selector
56+
# you can use one of [equal, not_equal, match, not_match] filter namespace
57+
namespace_selector:
58+
# only save endpoints with namespace equal default
59+
equal: default
60+
61+
# only save endpoints with namespace not equal default
62+
#not_equal: default
63+
64+
# only save endpoints with namespace match one of [default, ^my-[a-z]+$]
65+
#match:
66+
#- default
67+
#- ^my-[a-z]+$
68+
69+
# only save endpoints with namespace not match one of [default, ^my-[a-z]+$ ]
70+
#not_match:
71+
#- default
72+
#- ^my-[a-z]+$
73+
74+
# kubernetes discovery plugin support use label_selector
75+
# for the expression of label_selector, please refer to https://kubernetes.io/docs/concepts/overview/working-with-objects/labels
76+
label_selector: |-
77+
first="a",second="b"
78+
```
79+
80+
If the kubernetes service discovery runs inside a pod, you can use minimal configuration:
81+
82+
```yaml
83+
discovery:
84+
kubernetes: { }
85+
```
86+
87+
If the kubernetes service discovery runs outside a pod, you need to create or select a specified [_ServiceAccount_](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/),
88+
then get its token value, and use following configuration:
89+
90+
```yaml
91+
discovery:
92+
kubernetes:
93+
service:
94+
schema: https
95+
host: # enter apiserver host value here
96+
port: # enter apiserver port value here
97+
client:
98+
token: # enter serviceaccount token value here
99+
#token_file: # enter file path here
100+
```
101+
102+
## Interface
103+
104+
the kubernetes service discovery provides a query interface in accordance with the [_APISIX Discovery Specification_](https://github.com/apache/apisix/blob/master/docs/en/latest/discovery.md)
105+
106+
**function:** \
107+
nodes(service_name)
108+
109+
**description:** \
110+
nodes() function attempts to look up the ngx.shared.kubernetes for nodes corresponding to service_name, \
111+
service_name should match pattern: _[namespace]/[name]:[portName]_
112+
113+
+ namespace: The namespace where the kubernetes endpoints is located
114+
115+
+ name: The name of the kubernetes endpoints
116+
117+
+ portName: The portName of the kubernetes endpoints, if there is no portName, use targetPort, port instead
118+
119+
**return value:** \
120+
if the kubernetes endpoints value is as follows:
121+
122+
```yaml
123+
apiVersion: v1
124+
kind: Endpoints
125+
metadata:
126+
name: plat-dev
127+
namespace: default
128+
subsets:
129+
- addresses:
130+
- ip: "10.5.10.109"
131+
- ip: "10.5.10.110"
132+
ports:
133+
- port: 3306
134+
```
135+
136+
a nodes("default/plat-dev:3306") call will get follow result:
137+
138+
```
139+
{
140+
{
141+
host="10.5.10.109",
142+
port= 3306,
143+
weight= 50,
144+
},
145+
{
146+
host="10.5.10.110",
147+
port= 3306,
148+
weight= 50,
149+
},
150+
}
151+
```
152+
153+
## Q&A
154+
155+
> Q: Why only support configuration token to access _Kubernetes APIServer_ \
156+
> A: Usually, we will use three ways to complete the authentication of _Kubernetes APIServer_:
157+
>
158+
>+ mTLS
159+
>+ token
160+
>+ basic authentication
161+
>
162+
> Because lua-resty-http does not currently support mTLS, and basic authentication is not recommended,\
163+
> So currently only the token authentication method is implemented
164+
165+
---
166+
167+
> Q: APISIX inherits Nginx's multiple process model, does it mean that each nginx worker process will [_List-Watch_](https://kubernetes.io/docs/reference/using-api/api-concepts/) kubernetes endpoints resources \
168+
> A: The kubernetes service discovery only uses privileged processes to [_List-Watch_](https://kubernetes.io/docs/reference/using-api/api-concepts/) kubernetes endpoints resources, then store theirs value \
169+
> into ngx.shared.kubernetes, worker processes get results by querying ngx.shared.kubernetes
170+
171+
---
172+
173+
> Q: How to get [_ServiceAccount_](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/) token value \
174+
> A: Assume your [_ServiceAccount_](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/) located in namespace apisix and name is kubernetes-discovery, you can use the following steps to get token value
175+
>
176+
> 1. Get secret name: \
177+
> you can execute the following command, the output of the first column is the secret name we want
178+
>
179+
> ```shell
180+
> kubectl -n apisix get secrets | grep kubernetes-discovery
181+
> ```
182+
>
183+
> 2. Get token value: \
184+
> assume secret resources name is kubernetes-discovery-token-c64cv, you can execute the following command, the output is the service account token value we want
185+
>
186+
> ```shell
187+
> kubectl -n apisix get secret kubernetes-discovery-token-c64cv -o jsonpath={.data.token} | base64 -d
188+
> ```

0 commit comments

Comments
 (0)