diff --git a/apisix/cli/ngx_tpl.lua b/apisix/cli/ngx_tpl.lua index 4fa56d57a4cc..5b5b57f2efc2 100644 --- a/apisix/cli/ngx_tpl.lua +++ b/apisix/cli/ngx_tpl.lua @@ -616,7 +616,9 @@ http { {% if enable_admin then %} server { {%if https_admin then%} + {% for _, admin_server_addr in ipairs(admin_server_addrs) do %} listen {* admin_server_addr *} ssl; + {% end %} ssl_certificate {* admin_api_mtls.admin_ssl_cert *}; ssl_certificate_key {* admin_api_mtls.admin_ssl_cert_key *}; @@ -636,7 +638,9 @@ http { {% end %} {% else %} + {% for _, admin_server_addr in ipairs(admin_server_addrs) do %} listen {* admin_server_addr *}; + {% end %} {%end%} log_not_found off; diff --git a/apisix/cli/ops.lua b/apisix/cli/ops.lua index 0f6a4fae6a00..87eed977bf8c 100644 --- a/apisix/cli/ops.lua +++ b/apisix/cli/ops.lua @@ -426,7 +426,9 @@ Please modify "admin_key" in conf/config.yaml . default_port, configured_port) local ip = configured_ip or default_ip local port = tonumber(configured_port) or default_port - if ports_to_check[port] ~= nil then + -- allow the same service (e.g. admin) to reuse one port across multiple + -- listen addresses, which is required by IPv4 + IPv6 dual-stack listening + if ports_to_check[port] ~= nil and ports_to_check[port] ~= port_name then util.die(port_name .. " ", port, " conflicts with ", ports_to_check[port], "\n") end ports_to_check[port] = port_name @@ -434,12 +436,20 @@ Please modify "admin_key" in conf/config.yaml . end -- listen in admin use a separate port, support specific IP, compatible with the original style - local admin_server_addr + -- admin_listen supports both a single {ip=, port=} object and an array of them. + -- The array form allows listening on multiple addresses, e.g. IPv4 + IPv6 dual-stack. + local admin_server_addrs = {} if yaml_conf.apisix.enable_admin then - local ip = yaml_conf.deployment.admin.admin_listen.ip - local port = yaml_conf.deployment.admin.admin_listen.port - admin_server_addr = validate_and_get_listen_addr("admin port", "0.0.0.0", ip, - 9180, port) + local admin_listen = yaml_conf.deployment.admin.admin_listen + if admin_listen[1] ~= nil then + for _, listen in ipairs(admin_listen) do + table_insert(admin_server_addrs, validate_and_get_listen_addr( + "admin port", "0.0.0.0", listen.ip, 9180, listen.port)) + end + else + table_insert(admin_server_addrs, validate_and_get_listen_addr( + "admin port", "0.0.0.0", admin_listen.ip, 9180, admin_listen.port)) + end end local status_server_addr @@ -701,9 +711,9 @@ Please modify "admin_key" in conf/config.yaml . local role = yaml_conf.deployment.role env.deployment_role = role - if role == "control_plane" and not admin_server_addr then + if role == "control_plane" and #admin_server_addrs == 0 then local listen = node_listen[1] - admin_server_addr = str_format("%s:%s", listen.ip, listen.port) + table_insert(admin_server_addrs, str_format("%s:%s", listen.ip, listen.port)) end end @@ -728,7 +738,7 @@ Please modify "admin_key" in conf/config.yaml . enabled_stream_plugins = enabled_stream_plugins, dubbo_upstream_multiplex_count = dubbo_upstream_multiplex_count, status_server_addr = status_server_addr, - admin_server_addr = admin_server_addr, + admin_server_addrs = admin_server_addrs, control_server_addr = control_server_addr, prometheus_server_addr = prometheus_server_addr, proxy_mirror_timeouts = proxy_mirror_timeouts, diff --git a/apisix/cli/schema.lua b/apisix/cli/schema.lua index 3e3e855106c8..90673435feea 100644 --- a/apisix/cli/schema.lua +++ b/apisix/cli/schema.lua @@ -381,12 +381,30 @@ local admin_schema = { } }, admin_listen = { - properties = { - listen = { type = "string" }, - port = { type = "integer" }, + -- compatible with both the single-address object form and the + -- multi-address array form (IPv4 + IPv6 dual-stack) + oneOf = { + { + type = "object", + properties = { + ip = { type = "string" }, + port = { type = "integer" }, + }, + }, + { + type = "array", + minItems = 1, + items = { + type = "object", + properties = { + ip = { type = "string" }, + port = { type = "integer" }, + }, + }, + }, }, default = { - listen = "0.0.0.0", + ip = "0.0.0.0", port = 9180, } }, diff --git a/conf/config.yaml.example b/conf/config.yaml.example index d6714427ad4f..97828fc228fc 100644 --- a/conf/config.yaml.example +++ b/conf/config.yaml.example @@ -766,6 +766,13 @@ deployment: # Deployment configurations admin_listen: # Set the Admin API listening addresses. ip: 0.0.0.0 # Set listening IP. port: 9180 # Set listening port. Beware of port conflict with node_listen. + # admin_listen also accepts an array to listen on multiple addresses at once, + # for example IPv4 + IPv6 dual-stack (the same port may be reused across them): + # admin_listen: + # - ip: 0.0.0.0 + # port: 9180 + # - ip: "[::]" + # port: 9180 # https_admin: true # Enable SSL for Admin API on IP and port specified in admin_listen. # Use admin_api_mtls.admin_ssl_cert and admin_api_mtls.admin_ssl_cert_key. diff --git a/t/cli/test_admin.sh b/t/cli/test_admin.sh index 1298cc1dd406..613f5a990a8f 100755 --- a/t/cli/test_admin.sh +++ b/t/cli/test_admin.sh @@ -56,6 +56,36 @@ make stop echo "passed: admin https enabled" +# check admin_listen supports an array of addresses (IPv4 + IPv6 dual-stack) + +git checkout conf/config.yaml + +echo ' +apisix: + enable_admin: true +deployment: + admin: + admin_listen: + - ip: 0.0.0.0 + port: 9180 + - ip: "[::]" + port: 9180 +' > conf/config.yaml + +make init + +if ! grep "listen 0.0.0.0:9180;" conf/nginx.conf > /dev/null; then + echo "failed: admin_listen array should generate the IPv4 listen directive" + exit 1 +fi + +if ! grep "listen \[::\]:9180;" conf/nginx.conf > /dev/null; then + echo "failed: admin_listen array should generate the IPv6 listen directive" + exit 1 +fi + +echo "passed: admin_listen supports IPv4 + IPv6 dual-stack" + echo ' apisix: enable_admin: true