feat: support configuring admin specific IP (#4967)

Signed-off-by: wayne-cheng <zhengwei@tiduyun.com>
diff --git a/apisix/cli/ngx_tpl.lua b/apisix/cli/ngx_tpl.lua
index cecc623..3b9b841 100644
--- a/apisix/cli/ngx_tpl.lua
+++ b/apisix/cli/ngx_tpl.lua
@@ -396,10 +396,10 @@
     }
     {% end %}
 
-    {% if enable_admin and port_admin then %}
+    {% if enable_admin and admin_server_addr then %}
     server {
         {%if https_admin then%}
-        listen {* port_admin *} ssl;
+        listen {* admin_server_addr *} ssl;
 
         ssl_certificate      {* admin_api_mtls.admin_ssl_cert *};
         ssl_certificate_key  {* admin_api_mtls.admin_ssl_cert_key *};
@@ -419,7 +419,7 @@
         {% end %}
 
         {% else %}
-        listen {* port_admin *};
+        listen {* admin_server_addr *};
         {%end%}
         log_not_found off;
 
@@ -506,7 +506,7 @@
         }
         {% end %}
 
-        {% if enable_admin and not port_admin then %}
+        {% if enable_admin and not admin_server_addr then %}
         location /apisix/admin {
             set $upstream_scheme             'http';
             set $upstream_host               $http_host;
diff --git a/apisix/cli/ops.lua b/apisix/cli/ops.lua
index e5fbe20..0fcfa13 100644
--- a/apisix/cli/ops.lua
+++ b/apisix/cli/ops.lua
@@ -408,6 +408,27 @@
 
     local ports_to_check = {}
 
+    -- listen in admin use a separate port, support specific IP, compatible with the original style
+    local admin_server_addr
+    if yaml_conf.apisix.enable_admin then
+        if yaml_conf.apisix.admin_listen or yaml_conf.apisix.port_admin then
+            local ip = "0.0.0.0"
+            local port = yaml_conf.apisix.port_admin or 9180
+
+            if yaml_conf.apisix.admin_listen then
+                ip = yaml_conf.apisix.admin_listen.ip or ip
+                port = tonumber(yaml_conf.apisix.admin_listen.port) or port
+            end
+
+            if ports_to_check[port] ~= nil then
+                util.die("admin port ", port, " conflicts with ", ports_to_check[port], "\n")
+            end
+
+            admin_server_addr = ip .. ":" .. port
+            ports_to_check[port] = "admin"
+        end
+    end
+
     local control_server_addr
     if yaml_conf.apisix.enable_control then
         if not yaml_conf.apisix.control then
@@ -654,6 +675,7 @@
         enabled_plugins = enabled_plugins,
         dubbo_upstream_multiplex_count = dubbo_upstream_multiplex_count,
         tcp_enable_ssl = tcp_enable_ssl,
+        admin_server_addr = admin_server_addr,
         control_server_addr = control_server_addr,
         prometheus_server_addr = prometheus_server_addr,
     }
diff --git a/conf/config-default.yaml b/conf/config-default.yaml
index 48ac994..ab1f3d4 100644
--- a/conf/config-default.yaml
+++ b/conf/config-default.yaml
@@ -70,7 +70,10 @@
   allow_admin:                  # http://nginx.org/en/docs/http/ngx_http_access_module.html#allow
     - 127.0.0.0/24              # If we don't set any IP list, then any IP access is allowed by default.
     #- "::/64"
-  #port_admin: 9180             # use a separate port
+  #admin_listen:                # use a separate port
+  #  ip: 127.0.0.1              # Specific IP, if not set, the default value is `0.0.0.0`.
+  #  port: 9180
+  #port_admin: 9180             # Not recommend: This parameter should be set via the `admin_listen`.
   #https_admin: true            # enable HTTPS when use a separate port for Admin API.
                                 # Admin API will use conf/apisix_admin_api.crt and conf/apisix_admin_api.key as certificate.
   admin_api_mtls:               # Depends on `port_admin` and `https_admin`.
diff --git a/t/cli/test_admin.sh b/t/cli/test_admin.sh
index caa4f00..2ec2f7c 100755
--- a/t/cli/test_admin.sh
+++ b/t/cli/test_admin.sh
@@ -34,7 +34,7 @@
 
 make init
 
-grep "listen 9180 ssl" conf/nginx.conf > /dev/null
+grep "listen 0.0.0.0:9180 ssl" conf/nginx.conf > /dev/null
 if [ ! $? -eq 0 ]; then
     echo "failed: failed to enable https for admin"
     exit 1
@@ -52,6 +52,32 @@
 
 echo "passed: admin https enabled"
 
+echo '
+apisix:
+  enable_admin: true
+  admin_listen:
+    ip: 127.0.0.2
+    port: 9181
+' > conf/config.yaml
+
+make init
+
+if ! grep "listen 127.0.0.2:9181;" conf/nginx.conf > /dev/null; then
+    echo "failed: customize address for admin server"
+    exit 1
+fi
+
+make run
+
+code=$(curl -v -k -i -m 20 -o /dev/null -s -w %{http_code} http://127.0.0.2:9181/apisix/admin/routes -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1')
+
+if [ ! $code -eq 200 ]; then
+    echo "failed: failed to access admin"
+    exit 1
+fi
+
+make stop
+
 # rollback to the default
 
 git checkout conf/config.yaml
@@ -60,7 +86,7 @@
 
 set +ex
 
-grep "listen 9080 ssl" conf/nginx.conf > /dev/null
+grep "listen 0.0.0.0:9080 ssl" conf/nginx.conf > /dev/null
 if [ ! $? -eq 1 ]; then
     echo "failed: failed to rollback to the default admin config"
     exit 1