You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@apisix.apache.org by zh...@apache.org on 2021/12/02 08:53:17 UTC

[apisix-ingress-controller] branch master updated: feat: update Ingress LB status (#740)

This is an automated email from the ASF dual-hosted git repository.

zhangjintao pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix-ingress-controller.git


The following commit(s) were added to refs/heads/master by this push:
     new 0c6de2d  feat: update Ingress LB status (#740)
0c6de2d is described below

commit 0c6de2deb92c72b8d609490283c930a068df7d23
Author: Jintao Zhang <zh...@gmail.com>
AuthorDate: Thu Dec 2 16:53:12 2021 +0800

    feat: update Ingress LB status (#740)
---
 cmd/ingress/ingress.go        |   8 +++
 conf/config-default.yaml      |  10 +++
 pkg/config/config.go          |  36 +++++-----
 pkg/config/config_test.go     |  38 +++++++----
 pkg/ingress/apisix_route.go   |   2 +-
 pkg/ingress/ingress.go        |  51 +++++++++++++++
 pkg/ingress/status.go         | 148 +++++++++++++++++++++++++++++++++++++++++-
 test/e2e/ingress/status.go    |  50 ++++++++++++++
 test/e2e/scaffold/ingress.go  |  10 +--
 test/e2e/scaffold/scaffold.go |   3 +
 10 files changed, 319 insertions(+), 37 deletions(-)

diff --git a/cmd/ingress/ingress.go b/cmd/ingress/ingress.go
index e36fbcc..2729738 100644
--- a/cmd/ingress/ingress.go
+++ b/cmd/ingress/ingress.go
@@ -140,6 +140,14 @@ the apisix cluster and others are created`,
 	cmd.PersistentFlags().StringVar(&cfg.LogOutput, "log-output", "stderr", "error log output file")
 	cmd.PersistentFlags().StringVar(&cfg.HTTPListen, "http-listen", ":8080", "the HTTP Server listen address")
 	cmd.PersistentFlags().StringVar(&cfg.HTTPSListen, "https-listen", ":8443", "the HTTPS Server listen address")
+	cmd.PersistentFlags().StringVar(&cfg.IngressPublishService, "ingress-publish-service", "",
+		`the controller will use the Endpoint of this Service to update the status information of the Ingress resource. 
+The format is "namespace/svc-name" to solve the situation that the data plane and the controller are not deployed in the same namespace.`)
+	cmd.PersistentFlags().StringSliceVar(&cfg.IngressStatusAddress, "ingress-status-address", []string{},
+		`when there is no available information on the Service used for publishing on the data plane,
+the static address provided here will be used to update the status information of Ingress.
+When ingress-publish-service is specified at the same time, ingress-status-address is preferred.
+For example, no available LB exists in the bare metal environment.`)
 	cmd.PersistentFlags().BoolVar(&cfg.EnableProfiling, "enable-profiling", true, "enable profiling via web interface host:port/debug/pprof")
 	cmd.PersistentFlags().StringVar(&cfg.Kubernetes.Kubeconfig, "kubeconfig", "", "Kubernetes configuration file (by default in-cluster configuration will be used)")
 	cmd.PersistentFlags().DurationVar(&cfg.Kubernetes.ResyncInterval.Duration, "resync-interval", time.Minute, "the controller resync (with Kubernetes) interval, the minimum resync interval is 30s")
diff --git a/conf/config-default.yaml b/conf/config-default.yaml
index c724212..afc521f 100644
--- a/conf/config-default.yaml
+++ b/conf/config-default.yaml
@@ -32,6 +32,16 @@ key_file: "/etc/webhook/certs/key.pem"   # the TLS key file path.
 
 http_listen: ":8080"   # the HTTP Server listen address, default is ":8080"
 https_listen: ":8443"   # the HTTPS Server listen address, default is ":8443"
+ingress_publish_service: ""  # the controller will use the Endpoint of this Service to
+                             # update the status information of the Ingress resource.
+                             # The format is "namespace/svc-name" to solve the situation that
+                             # the data plane and the controller are not deployed in the same namespace.
+ingress_status_address: []   # when there is no available information on the Service
+                             # used for publishing on the data plane,
+                             # the static address provided here will be
+                             # used to update the status information of Ingress.
+                             # When ingress-publish-service is specified at the same time, ingress-status-address is preferred.
+                             # For example, no available LB exists in the bare metal environment.
 enable_profiling: true # enable profiling via web interfaces
                        # host:port/debug/pprof, default is true.
 
diff --git a/pkg/config/config.go b/pkg/config/config.go
index 1f8a3dd..15f7bd3 100644
--- a/pkg/config/config.go
+++ b/pkg/config/config.go
@@ -66,15 +66,17 @@ const (
 // Config contains all config items which are necessary for
 // apisix-ingress-controller's running.
 type Config struct {
-	CertFilePath    string           `json:"cert_file" yaml:"cert_file"`
-	KeyFilePath     string           `json:"key_file" yaml:"key_file"`
-	LogLevel        string           `json:"log_level" yaml:"log_level"`
-	LogOutput       string           `json:"log_output" yaml:"log_output"`
-	HTTPListen      string           `json:"http_listen" yaml:"http_listen"`
-	HTTPSListen     string           `json:"https_listen" yaml:"https_listen"`
-	EnableProfiling bool             `json:"enable_profiling" yaml:"enable_profiling"`
-	Kubernetes      KubernetesConfig `json:"kubernetes" yaml:"kubernetes"`
-	APISIX          APISIXConfig     `json:"apisix" yaml:"apisix"`
+	CertFilePath          string           `json:"cert_file" yaml:"cert_file"`
+	KeyFilePath           string           `json:"key_file" yaml:"key_file"`
+	LogLevel              string           `json:"log_level" yaml:"log_level"`
+	LogOutput             string           `json:"log_output" yaml:"log_output"`
+	HTTPListen            string           `json:"http_listen" yaml:"http_listen"`
+	HTTPSListen           string           `json:"https_listen" yaml:"https_listen"`
+	IngressPublishService string           `json:"ingress_publish_service" yaml:"ingress_publish_service"`
+	IngressStatusAddress  []string         `json:"ingress_status_address" yaml:"ingress_status_address"`
+	EnableProfiling       bool             `json:"enable_profiling" yaml:"enable_profiling"`
+	Kubernetes            KubernetesConfig `json:"kubernetes" yaml:"kubernetes"`
+	APISIX                APISIXConfig     `json:"apisix" yaml:"apisix"`
 }
 
 // KubernetesConfig contains all Kubernetes related config items.
@@ -113,13 +115,15 @@ type APISIXConfig struct {
 // default value.
 func NewDefaultConfig() *Config {
 	return &Config{
-		LogLevel:        "warn",
-		LogOutput:       "stderr",
-		HTTPListen:      ":8080",
-		HTTPSListen:     ":8443",
-		CertFilePath:    "/etc/webhook/certs/cert.pem",
-		KeyFilePath:     "/etc/webhook/certs/key.pem",
-		EnableProfiling: true,
+		LogLevel:              "warn",
+		LogOutput:             "stderr",
+		HTTPListen:            ":8080",
+		HTTPSListen:           ":8443",
+		IngressPublishService: "",
+		IngressStatusAddress:  []string{},
+		CertFilePath:          "/etc/webhook/certs/cert.pem",
+		KeyFilePath:           "/etc/webhook/certs/key.pem",
+		EnableProfiling:       true,
 		Kubernetes: KubernetesConfig{
 			Kubeconfig:          "", // Use in-cluster configurations.
 			ResyncInterval:      types.TimeDuration{Duration: 6 * time.Hour},
diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go
index 8877c7a..e7e3862 100644
--- a/pkg/config/config_test.go
+++ b/pkg/config/config_test.go
@@ -28,13 +28,15 @@ import (
 
 func TestNewConfigFromFile(t *testing.T) {
 	cfg := &Config{
-		LogLevel:        "warn",
-		LogOutput:       "stdout",
-		HTTPListen:      ":9090",
-		HTTPSListen:     ":9443",
-		CertFilePath:    "/etc/webhook/certs/cert.pem",
-		KeyFilePath:     "/etc/webhook/certs/key.pem",
-		EnableProfiling: true,
+		LogLevel:              "warn",
+		LogOutput:             "stdout",
+		HTTPListen:            ":9090",
+		HTTPSListen:           ":9443",
+		IngressPublishService: "",
+		IngressStatusAddress:  []string{},
+		CertFilePath:          "/etc/webhook/certs/cert.pem",
+		KeyFilePath:           "/etc/webhook/certs/key.pem",
+		EnableProfiling:       true,
 		Kubernetes: KubernetesConfig{
 			ResyncInterval:     types.TimeDuration{Duration: time.Hour},
 			Kubeconfig:         "/path/to/foo/baz",
@@ -77,6 +79,8 @@ log_level: warn
 log_output: stdout
 http_listen: :9090
 https_listen: :9443
+ingress_publish_service: ""
+ingress_status_address: []
 enable_profiling: true
 kubernetes:
   kubeconfig: /path/to/foo/baz
@@ -105,13 +109,15 @@ apisix:
 
 func TestConfigWithEnvVar(t *testing.T) {
 	cfg := &Config{
-		LogLevel:        "warn",
-		LogOutput:       "stdout",
-		HTTPListen:      ":9090",
-		HTTPSListen:     ":9443",
-		CertFilePath:    "/etc/webhook/certs/cert.pem",
-		KeyFilePath:     "/etc/webhook/certs/key.pem",
-		EnableProfiling: true,
+		LogLevel:              "warn",
+		LogOutput:             "stdout",
+		HTTPListen:            ":9090",
+		HTTPSListen:           ":9443",
+		IngressPublishService: "",
+		IngressStatusAddress:  []string{},
+		CertFilePath:          "/etc/webhook/certs/cert.pem",
+		KeyFilePath:           "/etc/webhook/certs/key.pem",
+		EnableProfiling:       true,
 		Kubernetes: KubernetesConfig{
 			ResyncInterval:     types.TimeDuration{Duration: time.Hour},
 			Kubeconfig:         "",
@@ -143,6 +149,8 @@ func TestConfigWithEnvVar(t *testing.T) {
     "log_output": "stdout",
     "http_listen": ":9090",
     "https_listen": ":9443",
+	"ingress_publish_service": "",
+	"ingress_status_address": [],
     "enable_profiling": true,
     "kubernetes": {
         "kubeconfig": "{{.KUBECONFIG}}",
@@ -176,6 +184,8 @@ log_level: warn
 log_output: stdout
 http_listen: :9090
 https_listen: :9443
+ingress_publish_service: ""
+ingress_status_address: []
 enable_profiling: true
 kubernetes:
   resync_interval: 1h0m0s
diff --git a/pkg/ingress/apisix_route.go b/pkg/ingress/apisix_route.go
index 50c5df0..a4b8e9d 100644
--- a/pkg/ingress/apisix_route.go
+++ b/pkg/ingress/apisix_route.go
@@ -133,7 +133,7 @@ func (c *apisixRouteController) sync(ctx context.Context, ev *types.Event) error
 		}
 		ar = ev.Tombstone.(kube.ApisixRoute)
 	}
-	//
+
 	switch obj.GroupVersion {
 	case kube.ApisixRouteV1:
 		tctx, err = c.controller.translator.TranslateRouteV1(ar.V1())
diff --git a/pkg/ingress/ingress.go b/pkg/ingress/ingress.go
index cd42bf3..da136c2 100644
--- a/pkg/ingress/ingress.go
+++ b/pkg/ingress/ingress.go
@@ -21,6 +21,7 @@ import (
 
 	"go.uber.org/zap"
 	k8serrors "k8s.io/apimachinery/pkg/api/errors"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/client-go/tools/cache"
 	"k8s.io/client-go/util/workqueue"
 
@@ -184,7 +185,42 @@ func (c *ingressController) sync(ctx context.Context, ev *types.Event) error {
 }
 
 func (c *ingressController) handleSyncErr(obj interface{}, err error) {
+	ev := obj.(*types.Event)
+	event := ev.Object.(kube.IngressEvent)
+	namespace, name, errLocal := cache.SplitMetaNamespaceKey(event.Key)
+	if errLocal != nil {
+		log.Errorf("invalid resource key: %s", event.Key)
+		return
+	}
+
+	var ing kube.Ingress
+	switch event.GroupVersion {
+	case kube.IngressV1:
+		ing, err = c.controller.ingressLister.V1(namespace, name)
+	case kube.IngressV1beta1:
+		ing, err = c.controller.ingressLister.V1beta1(namespace, name)
+	case kube.IngressExtensionsV1beta1:
+		ing, err = c.controller.ingressLister.ExtensionsV1beta1(namespace, name)
+	}
+
 	if err == nil {
+		// add status
+		if ev.Type != types.EventDelete {
+			if errLocal == nil {
+				switch ing.GroupVersion() {
+				case kube.IngressV1:
+					c.controller.recordStatus(ing.V1(), _resourceSynced, nil, metav1.ConditionTrue, ing.V1().GetGeneration())
+				case kube.IngressV1beta1:
+					c.controller.recordStatus(ing.V1beta1(), _resourceSynced, nil, metav1.ConditionTrue, ing.V1beta1().GetGeneration())
+				case kube.IngressExtensionsV1beta1:
+					c.controller.recordStatus(ing.ExtensionsV1beta1(), _resourceSynced, nil, metav1.ConditionTrue, ing.ExtensionsV1beta1().GetGeneration())
+				}
+			} else {
+				log.Errorw("failed split namespace/name",
+					zap.Error(errLocal),
+				)
+			}
+		}
 		c.workqueue.Forget(obj)
 		c.controller.MetricsCollector.IncrSyncOperation("ingress", "success")
 		return
@@ -193,6 +229,21 @@ func (c *ingressController) handleSyncErr(obj interface{}, err error) {
 		zap.Any("object", obj),
 		zap.Error(err),
 	)
+
+	if errLocal == nil {
+		switch ing.GroupVersion() {
+		case kube.IngressV1:
+			c.controller.recordStatus(ing.V1(), _resourceSyncAborted, err, metav1.ConditionTrue, ing.V1().GetGeneration())
+		case kube.IngressV1beta1:
+			c.controller.recordStatus(ing.V1beta1(), _resourceSyncAborted, err, metav1.ConditionTrue, ing.V1beta1().GetGeneration())
+		case kube.IngressExtensionsV1beta1:
+			c.controller.recordStatus(ing.ExtensionsV1beta1(), _resourceSyncAborted, err, metav1.ConditionTrue, ing.ExtensionsV1beta1().GetGeneration())
+		}
+	} else {
+		log.Errorw("failed split namespace/name",
+			zap.Error(errLocal),
+		)
+	}
 	c.workqueue.AddRateLimited(obj)
 	c.controller.MetricsCollector.IncrSyncOperation("ingress", "failure")
 }
diff --git a/pkg/ingress/status.go b/pkg/ingress/status.go
index f19873e..882595e 100644
--- a/pkg/ingress/status.go
+++ b/pkg/ingress/status.go
@@ -17,11 +17,19 @@ package ingress
 
 import (
 	"context"
+	"fmt"
+	"net"
+	"time"
 
 	"go.uber.org/zap"
+	apiv1 "k8s.io/api/core/v1"
+	extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
+	networkingv1 "k8s.io/api/networking/v1"
+	networkingv1beta1 "k8s.io/api/networking/v1beta1"
 	"k8s.io/apimachinery/pkg/api/meta"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/client-go/tools/cache"
 
 	configv1 "github.com/apache/apisix-ingress-controller/pkg/kube/apisix/apis/config/v1"
 	configv2alpha1 "github.com/apache/apisix-ingress-controller/pkg/kube/apisix/apis/config/v2alpha1"
@@ -31,8 +39,9 @@ import (
 )
 
 const (
-	_conditionType        = "ResourcesAvailable"
-	_commonSuccessMessage = "Sync Successfully"
+	_conditionType            = "ResourcesAvailable"
+	_commonSuccessMessage     = "Sync Successfully"
+	_gatewayLBNotReadyMessage = "The LoadBalancer used by the APISIX gateway is not yet ready"
 )
 
 // verifyGeneration verify generation to decide whether to update status
@@ -59,6 +68,7 @@ func (c *Controller) recordStatus(at interface{}, reason string, err error, stat
 		ObservedGeneration: generation,
 	}
 	client := c.kubeClient.APISIXClient
+	kubeClient := c.kubeClient.Client
 
 	switch v := at.(type) {
 	case *configv1.ApisixTls:
@@ -163,8 +173,142 @@ func (c *Controller) recordStatus(at interface{}, reason string, err error, stat
 				)
 			}
 		}
+	case *networkingv1.Ingress:
+		// set to status
+		lbips, err := c.ingressLBStatusIPs()
+		if err != nil {
+			log.Errorw("failed to get APISIX gateway external IPs",
+				zap.Error(err),
+			)
+
+		}
+
+		v.Status.LoadBalancer.Ingress = lbips
+		if _, errRecord := kubeClient.NetworkingV1().Ingresses(v.Namespace).UpdateStatus(context.TODO(), v, metav1.UpdateOptions{}); errRecord != nil {
+			log.Errorw("failed to record status change for IngressV1",
+				zap.Error(errRecord),
+				zap.String("name", v.Name),
+				zap.String("namespace", v.Namespace),
+			)
+		}
+
+	case *networkingv1beta1.Ingress:
+		// set to status
+		lbips, err := c.ingressLBStatusIPs()
+		if err != nil {
+			log.Errorw("failed to get APISIX gateway external IPs",
+				zap.Error(err),
+			)
+
+		}
+
+		v.Status.LoadBalancer.Ingress = lbips
+		if _, errRecord := kubeClient.NetworkingV1beta1().Ingresses(v.Namespace).UpdateStatus(context.TODO(), v, metav1.UpdateOptions{}); errRecord != nil {
+			log.Errorw("failed to record status change for IngressV1",
+				zap.Error(errRecord),
+				zap.String("name", v.Name),
+				zap.String("namespace", v.Namespace),
+			)
+		}
+	case *extensionsv1beta1.Ingress:
+		// set to status
+		lbips, err := c.ingressLBStatusIPs()
+		if err != nil {
+			log.Errorw("failed to get APISIX gateway external IPs",
+				zap.Error(err),
+			)
+
+		}
+
+		v.Status.LoadBalancer.Ingress = lbips
+		if _, errRecord := kubeClient.ExtensionsV1beta1().Ingresses(v.Namespace).UpdateStatus(context.TODO(), v, metav1.UpdateOptions{}); errRecord != nil {
+			log.Errorw("failed to record status change for IngressV1",
+				zap.Error(errRecord),
+				zap.String("name", v.Name),
+				zap.String("namespace", v.Namespace),
+			)
+		}
 	default:
 		// This should not be executed
 		log.Errorf("unsupported resource record: %s", v)
 	}
 }
+
+// ingressPublishAddresses get addressed used to expose Ingress
+func (c *Controller) ingressPublishAddresses() ([]string, error) {
+	ingressPublishService := c.cfg.IngressPublishService
+	ingressStatusAddress := c.cfg.IngressStatusAddress
+	addrs := []string{}
+
+	// if ingressStatusAddress is specified, it will be used first
+	if len(ingressStatusAddress) > 0 {
+		addrs = append(addrs, ingressStatusAddress...)
+		return addrs, nil
+	}
+
+	namespace, name, err := cache.SplitMetaNamespaceKey(ingressPublishService)
+	if err != nil {
+		log.Errorf("invalid ingressPublishService %s: %s", ingressPublishService, err)
+		return nil, err
+	}
+
+	kubeClient := c.kubeClient.Client
+	svc, err := kubeClient.CoreV1().Services(namespace).Get(context.TODO(), name, metav1.GetOptions{})
+	if err != nil {
+		return nil, err
+	}
+
+	switch svc.Spec.Type {
+	case apiv1.ServiceTypeLoadBalancer:
+		if len(svc.Status.LoadBalancer.Ingress) < 1 {
+			return addrs, fmt.Errorf(_gatewayLBNotReadyMessage)
+		}
+
+		for _, ip := range svc.Status.LoadBalancer.Ingress {
+			if ip.IP == "" {
+				// typically AWS load-balancers
+				addrs = append(addrs, ip.Hostname)
+			} else {
+				addrs = append(addrs, ip.IP)
+			}
+		}
+
+		addrs = append(addrs, svc.Spec.ExternalIPs...)
+		return addrs, nil
+	default:
+		return addrs, nil
+	}
+
+}
+
+// ingressLBStatusIPs organizes the available addresses
+func (c *Controller) ingressLBStatusIPs() ([]apiv1.LoadBalancerIngress, error) {
+	lbips := []apiv1.LoadBalancerIngress{}
+	var ips []string
+
+	for {
+		var err error
+		ips, err = c.ingressPublishAddresses()
+		if err != nil {
+			if err.Error() == _gatewayLBNotReadyMessage {
+				log.Warnf("%s. Provided service: %s", _gatewayLBNotReadyMessage, c.cfg.IngressPublishService)
+				time.Sleep(time.Second)
+				continue
+			}
+
+			return nil, err
+		}
+		break
+	}
+
+	for _, ip := range ips {
+		if net.ParseIP(ip) == nil {
+			lbips = append(lbips, apiv1.LoadBalancerIngress{Hostname: ip})
+		} else {
+			lbips = append(lbips, apiv1.LoadBalancerIngress{IP: ip})
+		}
+
+	}
+
+	return lbips, nil
+}
diff --git a/test/e2e/ingress/status.go b/test/e2e/ingress/status.go
index fcf9060..8643266 100644
--- a/test/e2e/ingress/status.go
+++ b/test/e2e/ingress/status.go
@@ -17,7 +17,9 @@ package ingress
 
 import (
 	"fmt"
+	"net/http"
 	"strings"
+	"time"
 
 	"github.com/onsi/ginkgo"
 	"github.com/stretchr/testify/assert"
@@ -69,3 +71,51 @@ spec:
 		assert.True(ginkgo.GinkgoT(), hasMsg, "Status is recorded")
 	})
 })
+
+var _ = ginkgo.Describe("Ingress LB Status Testing", func() {
+	opts := &scaffold.Options{
+		Name:                  "default",
+		Kubeconfig:            scaffold.GetKubeconfig(),
+		APISIXConfigPath:      "testdata/apisix-gw-config.yaml",
+		IngressAPISIXReplicas: 1,
+		HTTPBinServicePort:    80,
+		APISIXRouteVersion:    "apisix.apache.org/v2beta2",
+		APISIXPublishAddress:  "10.6.6.6",
+		EnableWebhooks:        false,
+	}
+	s := scaffold.NewScaffold(opts)
+	ginkgo.It("check the ingress lb status is updated", func() {
+		backendSvc, backendPort := s.DefaultHTTPBackend()
+		ing := fmt.Sprintf(`
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+  annotations:
+    kubernetes.io/ingress.class: apisix
+  name: ingress-v1-lb
+spec:
+  rules:
+  - host: httpbin.org
+    http:
+      paths:
+      - path: /ip
+        pathType: Exact
+        backend:
+          service:
+            name: %s
+            port:
+              number: %d
+`, backendSvc, backendPort[0])
+		err := s.CreateResourceFromString(ing)
+		assert.Nil(ginkgo.GinkgoT(), err, "creating ingress")
+		time.Sleep(5 * time.Second)
+
+		_ = s.NewAPISIXClient().GET("/ip").WithHeader("Host", "httpbin.org").Expect().Status(http.StatusOK)
+
+		output, err := s.GetOutputFromString("ingress", "ingress-v1-lb", "-o", "jsonpath='{ .status.loadBalancer.ingress[0].ip }'")
+		assert.Nil(ginkgo.GinkgoT(), err, "Get output of ingress status")
+
+		hasIP := strings.Contains(output, "10.6.6.6")
+		assert.True(ginkgo.GinkgoT(), hasIP, "LB Status is recorded")
+	})
+})
diff --git a/test/e2e/scaffold/ingress.go b/test/e2e/scaffold/ingress.go
index bd6ddbd..99b9ec3 100644
--- a/test/e2e/scaffold/ingress.go
+++ b/test/e2e/scaffold/ingress.go
@@ -283,6 +283,8 @@ spec:
             - %s
             - --apisix-route-version
             - %s
+            - --ingress-status-address
+            - "%s"
             - --watch-endpointslices
           %s
       volumes:
@@ -405,9 +407,9 @@ func (s *Scaffold) newIngressAPISIXController() error {
 	var ingressAPISIXDeployment string
 	label := fmt.Sprintf("apisix.ingress.watch=%s", s.namespace)
 	if s.opts.EnableWebhooks {
-		ingressAPISIXDeployment = fmt.Sprintf(_ingressAPISIXDeploymentTemplate, s.opts.IngressAPISIXReplicas, s.namespace, label, s.opts.APISIXRouteVersion, _volumeMounts, _webhookCertSecret)
+		ingressAPISIXDeployment = fmt.Sprintf(_ingressAPISIXDeploymentTemplate, s.opts.IngressAPISIXReplicas, s.namespace, label, s.opts.APISIXRouteVersion, s.opts.APISIXPublishAddress, _volumeMounts, _webhookCertSecret)
 	} else {
-		ingressAPISIXDeployment = fmt.Sprintf(_ingressAPISIXDeploymentTemplate, s.opts.IngressAPISIXReplicas, s.namespace, label, s.opts.APISIXRouteVersion, "", _webhookCertSecret)
+		ingressAPISIXDeployment = fmt.Sprintf(_ingressAPISIXDeploymentTemplate, s.opts.IngressAPISIXReplicas, s.namespace, label, s.opts.APISIXRouteVersion, s.opts.APISIXPublishAddress, "", _webhookCertSecret)
 	}
 
 	err = k8s.KubectlApplyFromStringE(s.t, s.kubectlOptions, ingressAPISIXDeployment)
@@ -513,9 +515,9 @@ func (s *Scaffold) ScaleIngressController(desired int) error {
 	var ingressDeployment string
 	label := fmt.Sprintf("apisix.ingress.watch=%s", s.namespace)
 	if s.opts.EnableWebhooks {
-		ingressDeployment = fmt.Sprintf(_ingressAPISIXDeploymentTemplate, desired, s.namespace, label, s.opts.APISIXRouteVersion, _volumeMounts, _webhookCertSecret)
+		ingressDeployment = fmt.Sprintf(_ingressAPISIXDeploymentTemplate, desired, s.namespace, label, s.opts.APISIXRouteVersion, s.opts.APISIXPublishAddress, _volumeMounts, _webhookCertSecret)
 	} else {
-		ingressDeployment = fmt.Sprintf(_ingressAPISIXDeploymentTemplate, desired, s.namespace, label, s.opts.APISIXRouteVersion, "", _webhookCertSecret)
+		ingressDeployment = fmt.Sprintf(_ingressAPISIXDeploymentTemplate, desired, s.namespace, label, s.opts.APISIXRouteVersion, s.opts.APISIXPublishAddress, "", _webhookCertSecret)
 	}
 	if err := k8s.KubectlApplyFromStringE(s.t, s.kubectlOptions, ingressDeployment); err != nil {
 		return err
diff --git a/test/e2e/scaffold/scaffold.go b/test/e2e/scaffold/scaffold.go
index 73173f6..b19d806 100644
--- a/test/e2e/scaffold/scaffold.go
+++ b/test/e2e/scaffold/scaffold.go
@@ -53,6 +53,7 @@ type Options struct {
 	APISIXRouteVersion    string
 	APISIXAdminAPIKey     string
 	EnableWebhooks        bool
+	APISIXPublishAddress  string
 }
 
 type Scaffold struct {
@@ -130,6 +131,7 @@ func NewDefaultScaffold() *Scaffold {
 		HTTPBinServicePort:    80,
 		APISIXRouteVersion:    kube.ApisixRouteV1,
 		EnableWebhooks:        false,
+		APISIXPublishAddress:  "",
 	}
 	return NewScaffold(opts)
 }
@@ -144,6 +146,7 @@ func NewDefaultV2Scaffold() *Scaffold {
 		HTTPBinServicePort:    80,
 		APISIXRouteVersion:    kube.ApisixRouteV2alpha1,
 		EnableWebhooks:        false,
+		APISIXPublishAddress:  "",
 	}
 	return NewScaffold(opts)
 }