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 2022/08/10 13:42:18 UTC

[apisix-ingress-controller] branch master updated: feat: Restruct pkg/ingress (#1204)

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 d32c7281 feat: Restruct pkg/ingress (#1204)
d32c7281 is described below

commit d32c728139a706bbad155a5e58514a3e59a841e4
Author: Sarasa Kisaragi <li...@gmail.com>
AuthorDate: Wed Aug 10 21:42:13 2022 +0800

    feat: Restruct pkg/ingress (#1204)
---
 cmd/ingress/ingress.go                             |    4 +-
 pkg/config/config.go                               |    2 -
 pkg/config/config_test.go                          |   30 +-
 pkg/ingress/controller.go                          |  813 ------------
 pkg/ingress/status.go                              |  343 -----
 pkg/kube/translation/ingress_test.go               | 1376 --------------------
 pkg/kube/translation/translator.go                 |  426 ------
 pkg/kube/translation/util.go                       |  263 ----
 .../apisix}/apisix_cluster_config.go               |  175 ++-
 .../apisix}/apisix_consumer.go                     |  157 ++-
 .../apisix/apisix_plugin_config.go}                |  166 ++-
 pkg/{ingress => providers/apisix}/apisix_route.go  |  235 +++-
 pkg/{ingress => providers/apisix}/apisix_tls.go    |  178 ++-
 .../apisix}/apisix_upstream.go                     |  192 ++-
 pkg/providers/apisix/provider.go                   |  202 +++
 .../apisix/provider_init.go}                       |   76 +-
 .../apisix/translation/apisix_cluster_config.go}   |    0
 .../translation/apisix_cluster_config_test.go}     |    0
 .../apisix}/translation/apisix_consumer.go         |    0
 .../apisix}/translation/apisix_consumer_test.go    |    0
 .../apisix/translation/apisix_plugin.go}           |    9 +-
 .../apisix/translation/apisix_plugin_test.go}      |   72 +-
 .../apisix}/translation/apisix_pluginconfig.go     |   17 +-
 .../translation/apisix_pluginconfig_test.go        |    0
 .../apisix}/translation/apisix_route.go            |  271 +++-
 .../apisix}/translation/apisix_route_test.go       |   11 +-
 .../apisix}/translation/apisix_ssl.go              |   64 +-
 .../apisix/translation/apisix_upstream.go          |   48 +
 pkg/providers/apisix/translation/translator.go     |  106 ++
 pkg/providers/controller.go                        |  500 +++++++
 pkg/{ingress => providers}/gateway/gateway.go      |    2 +-
 .../gateway/gateway_class.go                       |    0
 .../gateway/gateway_httproute.go                   |    4 +-
 .../gateway/gateway_tlsroute.go                    |    4 +-
 pkg/{ingress => providers}/gateway/provider.go     |   18 +-
 .../gateway/translation/gateway.go                 |    2 +-
 .../gateway/translation/gateway_httproute.go       |    6 +-
 .../gateway/translation/gateway_httproute_test.go  |    2 +-
 .../gateway/translation/gateway_tlsroute.go        |    6 +-
 .../gateway/translation/translator.go              |    4 +-
 pkg/{ingress => providers}/gateway/types/types.go  |    0
 pkg/{ => providers}/ingress/ingress.go             |  173 ++-
 pkg/{ => providers}/ingress/ingress_test.go        |    7 +-
 pkg/providers/ingress/provider.go                  |  118 ++
 .../ingress}/translation/annotations.go            |    4 +-
 .../translation/annotations/authorization.go       |    0
 .../ingress}/translation/annotations/cors.go       |    0
 .../ingress}/translation/annotations/cors_test.go  |    0
 .../ingress}/translation/annotations/csrf.go       |    0
 .../translation/annotations/forward_auth.go        |    0
 .../translation/annotations/forward_auth_test.go   |    0
 .../translation/annotations/iprestriction.go       |    0
 .../translation/annotations/iprestriction_test.go  |    0
 .../ingress}/translation/annotations/redirect.go   |    0
 .../ingress}/translation/annotations/rewrite.go    |    0
 .../ingress}/translation/annotations/types.go      |    0
 .../ingress/translation/translator.go}             |  160 ++-
 pkg/providers/k8s/endpoint/base.go                 |  133 ++
 .../k8s/endpoint}/endpoint.go                      |   57 +-
 .../k8s/endpoint}/endpointslice.go                 |   71 +-
 pkg/providers/k8s/endpoint/provider.go             |   77 ++
 .../k8s}/namespace/namespace.go                    |    0
 .../k8s/namespace/namespace_provider.go}           |   24 +-
 .../k8s/namespace/namespace_provider_mock.go}      |    6 +-
 pkg/{ingress => providers/k8s/pod}/pod.go          |   45 +-
 pkg/{ingress => providers/k8s/pod}/pod_test.go     |   57 +-
 pkg/providers/k8s/pod/provider.go                  |   75 ++
 pkg/providers/k8s/provider.go                      |   80 ++
 pkg/{ingress => providers/k8s}/secret.go           |  196 ++-
 .../translation/apisix_upstream.go                 |  340 ++---
 .../translation/apisix_upstream_test.go            |  240 ++--
 pkg/{kube => providers}/translation/context.go     |   10 +-
 .../translation/context_test.go                    |    0
 pkg/providers/translation/service.go               |  249 ++++
 pkg/providers/translation/translator.go            |   89 ++
 .../translation/translator_test.go                 |   60 +-
 pkg/providers/translation/util.go                  |   88 ++
 pkg/{kube => providers}/translation/util_test.go   |    4 +-
 pkg/providers/types/types.go                       |  183 +++
 pkg/{ingress => providers}/utils/executor.go       |    0
 pkg/{ingress => providers}/utils/ingress_status.go |    0
 pkg/{ingress => providers}/utils/manifest.go       |    0
 pkg/{ingress => providers}/utils/manifest_test.go  |    0
 pkg/providers/utils/status.go                      |   66 +
 pkg/{ingress => providers}/utils/string.go         |    0
 pkg/types/pod.go                                   |    2 +-
 test/e2e/scaffold/ingress.go                       |  189 +--
 test/e2e/scaffold/scaffold.go                      |    2 +-
 88 files changed, 4178 insertions(+), 4411 deletions(-)

diff --git a/cmd/ingress/ingress.go b/cmd/ingress/ingress.go
index dcbb7264..cf732566 100644
--- a/cmd/ingress/ingress.go
+++ b/cmd/ingress/ingress.go
@@ -27,8 +27,8 @@ import (
 	"github.com/spf13/cobra"
 
 	"github.com/apache/apisix-ingress-controller/pkg/config"
-	controller "github.com/apache/apisix-ingress-controller/pkg/ingress"
 	"github.com/apache/apisix-ingress-controller/pkg/log"
+	controller "github.com/apache/apisix-ingress-controller/pkg/providers"
 	"github.com/apache/apisix-ingress-controller/pkg/version"
 )
 
@@ -161,8 +161,6 @@ For example, no available LB exists in the bare metal environment.`)
 	cmd.PersistentFlags().StringVar(&cfg.Kubernetes.IngressClass, "ingress-class", config.IngressClass, "the class of an Ingress object is set using the field IngressClassName in Kubernetes clusters version v1.18.0 or higher or the annotation \"kubernetes.io/ingress.class\" (deprecated)")
 	cmd.PersistentFlags().StringVar(&cfg.Kubernetes.ElectionID, "election-id", config.IngressAPISIXLeader, "election id used for campaign the controller leader")
 	cmd.PersistentFlags().StringVar(&cfg.Kubernetes.IngressVersion, "ingress-version", config.IngressNetworkingV1, "the supported ingress api group version, can be \"networking/v1beta1\", \"networking/v1\" (for Kubernetes version v1.19.0 or higher) and \"extensions/v1beta1\"")
-	cmd.PersistentFlags().StringVar(&cfg.Kubernetes.ApisixRouteVersion, "apisix-route-version", config.DefaultAPIVersion, "the supported apisixroute api group version, can be \"apisix.apache.org/v2beta2\" or \"apisix.apache.org/v2beta3\" or \"apisix.apache.org/v2\"")
-	_ = cmd.PersistentFlags().MarkDeprecated("apisix-route-version", "will be removed in the next version, please use --api-version instead")
 	cmd.PersistentFlags().StringVar(&cfg.Kubernetes.APIVersion, "api-version", config.DefaultAPIVersion, config.APIVersionDescribe)
 	cmd.PersistentFlags().BoolVar(&cfg.Kubernetes.WatchEndpointSlices, "watch-endpointslices", false, "whether to watch endpointslices rather than endpoints")
 	cmd.PersistentFlags().BoolVar(&cfg.Kubernetes.EnableGatewayAPI, "enable-gateway-api", false, "whether to enable support for Gateway API")
diff --git a/pkg/config/config.go b/pkg/config/config.go
index 04bb64bf..4e061b6c 100644
--- a/pkg/config/config.go
+++ b/pkg/config/config.go
@@ -99,7 +99,6 @@ type KubernetesConfig struct {
 	IngressClass        string             `json:"ingress_class" yaml:"ingress_class"`
 	IngressVersion      string             `json:"ingress_version" yaml:"ingress_version"`
 	WatchEndpointSlices bool               `json:"watch_endpoint_slices" yaml:"watch_endpoint_slices"`
-	ApisixRouteVersion  string             `json:"apisix_route_version" yaml:"apisix_route_version"`
 	APIVersion          string             `json:"api_version" yaml:"api_version"`
 	EnableGatewayAPI    bool               `json:"enable_gateway_api" yaml:"enable_gateway_api"`
 }
@@ -136,7 +135,6 @@ func NewDefaultConfig() *Config {
 			ElectionID:          IngressAPISIXLeader,
 			IngressClass:        IngressClass,
 			IngressVersion:      IngressNetworkingV1,
-			ApisixRouteVersion:  DefaultAPIVersion,
 			APIVersion:          DefaultAPIVersion,
 			WatchEndpointSlices: false,
 			EnableGatewayAPI:    false,
diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go
index 9c305c7c..c448d440 100644
--- a/pkg/config/config_test.go
+++ b/pkg/config/config_test.go
@@ -39,14 +39,13 @@ func TestNewConfigFromFile(t *testing.T) {
 		EnableProfiling:            true,
 		ApisixResourceSyncInterval: types.TimeDuration{Duration: 200 * time.Second},
 		Kubernetes: KubernetesConfig{
-			ResyncInterval:     types.TimeDuration{Duration: time.Hour},
-			Kubeconfig:         "/path/to/foo/baz",
-			AppNamespaces:      []string{""},
-			ElectionID:         "my-election-id",
-			IngressClass:       IngressClass,
-			IngressVersion:     IngressNetworkingV1,
-			ApisixRouteVersion: DefaultAPIVersion,
-			APIVersion:         DefaultAPIVersion,
+			ResyncInterval: types.TimeDuration{Duration: time.Hour},
+			Kubeconfig:     "/path/to/foo/baz",
+			AppNamespaces:  []string{""},
+			ElectionID:     "my-election-id",
+			IngressClass:   IngressClass,
+			IngressVersion: IngressNetworkingV1,
+			APIVersion:     DefaultAPIVersion,
 		},
 		APISIX: APISIXConfig{
 			DefaultClusterName:     "default",
@@ -124,14 +123,13 @@ func TestConfigWithEnvVar(t *testing.T) {
 		EnableProfiling:            true,
 		ApisixResourceSyncInterval: types.TimeDuration{Duration: 200 * time.Second},
 		Kubernetes: KubernetesConfig{
-			ResyncInterval:     types.TimeDuration{Duration: time.Hour},
-			Kubeconfig:         "",
-			AppNamespaces:      []string{""},
-			ElectionID:         "my-election-id",
-			IngressClass:       IngressClass,
-			IngressVersion:     IngressNetworkingV1,
-			ApisixRouteVersion: DefaultAPIVersion,
-			APIVersion:         DefaultAPIVersion,
+			ResyncInterval: types.TimeDuration{Duration: time.Hour},
+			Kubeconfig:     "",
+			AppNamespaces:  []string{""},
+			ElectionID:     "my-election-id",
+			IngressClass:   IngressClass,
+			IngressVersion: IngressNetworkingV1,
+			APIVersion:     DefaultAPIVersion,
 		},
 		APISIX: APISIXConfig{
 			DefaultClusterName:     "default",
diff --git a/pkg/ingress/controller.go b/pkg/ingress/controller.go
deleted file mode 100644
index 5a1a41d1..00000000
--- a/pkg/ingress/controller.go
+++ /dev/null
@@ -1,813 +0,0 @@
-// Licensed to the Apache Software Foundation (ASF) under one or more
-// contributor license agreements.  See the NOTICE file distributed with
-// this work for additional information regarding copyright ownership.
-// The ASF licenses this file to You under the Apache License, Version 2.0
-// (the "License"); you may not use this file except in compliance with
-// the License.  You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-package ingress
-
-import (
-	"context"
-	"fmt"
-	"os"
-	"sync"
-	"time"
-
-	"go.uber.org/zap"
-	v1 "k8s.io/api/core/v1"
-	k8serrors "k8s.io/apimachinery/pkg/api/errors"
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"k8s.io/apimachinery/pkg/runtime"
-	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
-	"k8s.io/client-go/kubernetes/scheme"
-	typedcorev1 "k8s.io/client-go/kubernetes/typed/core/v1"
-	listerscorev1 "k8s.io/client-go/listers/core/v1"
-	"k8s.io/client-go/tools/cache"
-	"k8s.io/client-go/tools/leaderelection"
-	"k8s.io/client-go/tools/leaderelection/resourcelock"
-	"k8s.io/client-go/tools/record"
-
-	"github.com/apache/apisix-ingress-controller/pkg/api"
-	"github.com/apache/apisix-ingress-controller/pkg/apisix"
-	apisixcache "github.com/apache/apisix-ingress-controller/pkg/apisix/cache"
-	"github.com/apache/apisix-ingress-controller/pkg/config"
-	"github.com/apache/apisix-ingress-controller/pkg/ingress/gateway"
-	"github.com/apache/apisix-ingress-controller/pkg/ingress/namespace"
-	"github.com/apache/apisix-ingress-controller/pkg/ingress/utils"
-	"github.com/apache/apisix-ingress-controller/pkg/kube"
-	configv2 "github.com/apache/apisix-ingress-controller/pkg/kube/apisix/apis/config/v2"
-	configv2beta3 "github.com/apache/apisix-ingress-controller/pkg/kube/apisix/apis/config/v2beta3"
-	apisixscheme "github.com/apache/apisix-ingress-controller/pkg/kube/apisix/client/clientset/versioned/scheme"
-	"github.com/apache/apisix-ingress-controller/pkg/kube/translation"
-	"github.com/apache/apisix-ingress-controller/pkg/log"
-	"github.com/apache/apisix-ingress-controller/pkg/metrics"
-	"github.com/apache/apisix-ingress-controller/pkg/types"
-	apisixv1 "github.com/apache/apisix-ingress-controller/pkg/types/apisix/v1"
-)
-
-const (
-	// _component is used for event component
-	_component = "ApisixIngress"
-	// _resourceSynced is used when a resource is synced successfully
-	_resourceSynced = "ResourcesSynced"
-	// _messageResourceSynced is used to specify controller
-	_messageResourceSynced = "%s synced successfully"
-	// _resourceSyncAborted is used when a resource synced failed
-	_resourceSyncAborted = "ResourceSyncAborted"
-	// _messageResourceFailed is used to report error
-	_messageResourceFailed = "%s synced failed, with error: %s"
-	// minimum interval for ingress sync to APISIX
-	_mininumApisixResourceSyncInterval = 60 * time.Second
-)
-
-// Controller is the ingress apisix controller object.
-type Controller struct {
-	name             string
-	namespace        string
-	cfg              *config.Config
-	apisix           apisix.APISIX
-	podCache         types.PodCache
-	translator       translation.Translator
-	apiServer        *api.Server
-	MetricsCollector metrics.Collector
-	kubeClient       *kube.KubeClient
-	// recorder event
-	recorder record.EventRecorder
-	// this map enrolls which ApisixTls objects refer to a Kubernetes
-	// Secret object.
-	// type: Map<SecretKey, Map<ApisixTlsKey, ApisixTls>>
-	// SecretKey is `namespace_name`, ApisixTlsKey is kube style meta key: `namespace/name`
-	secretSSLMap *sync.Map
-
-	// leaderContextCancelFunc will be called when apisix-ingress-controller
-	// decides to give up its leader role.
-	leaderContextCancelFunc context.CancelFunc
-
-	// common informers and listers
-	podInformer                 cache.SharedIndexInformer
-	podLister                   listerscorev1.PodLister
-	epInformer                  cache.SharedIndexInformer
-	epLister                    kube.EndpointLister
-	svcInformer                 cache.SharedIndexInformer
-	svcLister                   listerscorev1.ServiceLister
-	ingressLister               kube.IngressLister
-	ingressInformer             cache.SharedIndexInformer
-	secretInformer              cache.SharedIndexInformer
-	secretLister                listerscorev1.SecretLister
-	apisixUpstreamInformer      cache.SharedIndexInformer
-	apisixUpstreamLister        kube.ApisixUpstreamLister
-	apisixRouteLister           kube.ApisixRouteLister
-	apisixRouteInformer         cache.SharedIndexInformer
-	apisixTlsLister             kube.ApisixTlsLister
-	apisixTlsInformer           cache.SharedIndexInformer
-	apisixClusterConfigLister   kube.ApisixClusterConfigLister
-	apisixClusterConfigInformer cache.SharedIndexInformer
-	apisixConsumerInformer      cache.SharedIndexInformer
-	apisixConsumerLister        kube.ApisixConsumerLister
-	apisixPluginConfigInformer  cache.SharedIndexInformer
-	apisixPluginConfigLister    kube.ApisixPluginConfigLister
-
-	// resource controllers
-	podController           *podController
-	endpointsController     *endpointsController
-	endpointSliceController *endpointSliceController
-	ingressController       *ingressController
-	secretController        *secretController
-
-	namespaceProvider namespace.WatchingProvider
-	gatewayProvider   *gateway.Provider
-
-	apisixUpstreamController      *apisixUpstreamController
-	apisixRouteController         *apisixRouteController
-	apisixTlsController           *apisixTlsController
-	apisixClusterConfigController *apisixClusterConfigController
-	apisixConsumerController      *apisixConsumerController
-	apisixPluginConfigController  *apisixPluginConfigController
-}
-
-// NewController creates an ingress apisix controller object.
-func NewController(cfg *config.Config) (*Controller, error) {
-	podName := os.Getenv("POD_NAME")
-	podNamespace := os.Getenv("POD_NAMESPACE")
-	if podNamespace == "" {
-		podNamespace = "default"
-	}
-	client, err := apisix.NewClient()
-	if err != nil {
-		return nil, err
-	}
-
-	kubeClient, err := kube.NewKubeClient(cfg)
-	if err != nil {
-		return nil, err
-	}
-
-	apiSrv, err := api.NewServer(cfg)
-	if err != nil {
-		return nil, err
-	}
-
-	// recorder
-	utilruntime.Must(apisixscheme.AddToScheme(scheme.Scheme))
-	eventBroadcaster := record.NewBroadcaster()
-	eventBroadcaster.StartRecordingToSink(&typedcorev1.EventSinkImpl{Interface: kubeClient.Client.CoreV1().Events("")})
-
-	c := &Controller{
-		name:             podName,
-		namespace:        podNamespace,
-		cfg:              cfg,
-		apiServer:        apiSrv,
-		apisix:           client,
-		MetricsCollector: metrics.NewPrometheusCollector(),
-		kubeClient:       kubeClient,
-		secretSSLMap:     new(sync.Map),
-		recorder:         eventBroadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: _component}),
-
-		podCache: types.NewPodCache(),
-	}
-	return c, nil
-}
-
-func (c *Controller) initWhenStartLeading() {
-	var (
-		ingressInformer             cache.SharedIndexInformer
-		apisixRouteInformer         cache.SharedIndexInformer
-		apisixPluginConfigInformer  cache.SharedIndexInformer
-		apisixTlsInformer           cache.SharedIndexInformer
-		apisixClusterConfigInformer cache.SharedIndexInformer
-		apisixConsumerInformer      cache.SharedIndexInformer
-		apisixUpstreamInformer      cache.SharedIndexInformer
-	)
-
-	kubeFactory := c.kubeClient.NewSharedIndexInformerFactory()
-	apisixFactory := c.kubeClient.NewAPISIXSharedIndexInformerFactory()
-
-	c.podLister = kubeFactory.Core().V1().Pods().Lister()
-	c.epLister, c.epInformer = kube.NewEndpointListerAndInformer(kubeFactory, c.cfg.Kubernetes.WatchEndpointSlices)
-	c.svcLister = kubeFactory.Core().V1().Services().Lister()
-	c.ingressLister = kube.NewIngressLister(
-		kubeFactory.Networking().V1().Ingresses().Lister(),
-		kubeFactory.Networking().V1beta1().Ingresses().Lister(),
-		kubeFactory.Extensions().V1beta1().Ingresses().Lister(),
-	)
-	c.secretLister = kubeFactory.Core().V1().Secrets().Lister()
-	c.apisixRouteLister = kube.NewApisixRouteLister(
-		apisixFactory.Apisix().V2beta2().ApisixRoutes().Lister(),
-		apisixFactory.Apisix().V2beta3().ApisixRoutes().Lister(),
-		apisixFactory.Apisix().V2().ApisixRoutes().Lister(),
-	)
-	c.apisixUpstreamLister = kube.NewApisixUpstreamLister(
-		apisixFactory.Apisix().V2beta3().ApisixUpstreams().Lister(),
-		apisixFactory.Apisix().V2().ApisixUpstreams().Lister(),
-	)
-	c.apisixTlsLister = kube.NewApisixTlsLister(
-		apisixFactory.Apisix().V2beta3().ApisixTlses().Lister(),
-		apisixFactory.Apisix().V2().ApisixTlses().Lister(),
-	)
-	c.apisixClusterConfigLister = kube.NewApisixClusterConfigLister(
-		apisixFactory.Apisix().V2beta3().ApisixClusterConfigs().Lister(),
-		apisixFactory.Apisix().V2().ApisixClusterConfigs().Lister(),
-	)
-	c.apisixConsumerLister = kube.NewApisixConsumerLister(
-		apisixFactory.Apisix().V2beta3().ApisixConsumers().Lister(),
-		apisixFactory.Apisix().V2().ApisixConsumers().Lister(),
-	)
-	c.apisixPluginConfigLister = kube.NewApisixPluginConfigLister(
-		apisixFactory.Apisix().V2beta3().ApisixPluginConfigs().Lister(),
-		apisixFactory.Apisix().V2().ApisixPluginConfigs().Lister(),
-	)
-
-	c.translator = translation.NewTranslator(&translation.TranslatorOptions{
-		PodCache:             c.podCache,
-		PodLister:            c.podLister,
-		EndpointLister:       c.epLister,
-		ServiceLister:        c.svcLister,
-		ApisixUpstreamLister: c.apisixUpstreamLister,
-		SecretLister:         c.secretLister,
-		UseEndpointSlices:    c.cfg.Kubernetes.WatchEndpointSlices,
-		APIVersion:           c.cfg.Kubernetes.APIVersion,
-		Apisix:               c.apisix,
-		ClusterName:          c.cfg.APISIX.DefaultClusterName,
-	})
-
-	switch c.cfg.Kubernetes.APIVersion {
-	case config.ApisixV2beta3:
-		apisixUpstreamInformer = apisixFactory.Apisix().V2beta3().ApisixUpstreams().Informer()
-		// to do ApisixRoute
-		apisixPluginConfigInformer = apisixFactory.Apisix().V2beta3().ApisixPluginConfigs().Informer()
-		apisixTlsInformer = apisixFactory.Apisix().V2beta3().ApisixTlses().Informer()
-		apisixConsumerInformer = apisixFactory.Apisix().V2beta3().ApisixConsumers().Informer()
-		apisixClusterConfigInformer = apisixFactory.Apisix().V2beta3().ApisixClusterConfigs().Informer()
-	case config.ApisixV2:
-		apisixUpstreamInformer = apisixFactory.Apisix().V2().ApisixUpstreams().Informer()
-		// to do ApisixRoute
-		apisixPluginConfigInformer = apisixFactory.Apisix().V2().ApisixPluginConfigs().Informer()
-		apisixTlsInformer = apisixFactory.Apisix().V2().ApisixTlses().Informer()
-		apisixConsumerInformer = apisixFactory.Apisix().V2().ApisixConsumers().Informer()
-		apisixClusterConfigInformer = apisixFactory.Apisix().V2().ApisixClusterConfigs().Informer()
-	default:
-		panic(fmt.Errorf("unsupported API version %v", c.cfg.Kubernetes.APIVersion))
-	}
-
-	if c.cfg.Kubernetes.IngressVersion == config.IngressNetworkingV1 {
-		ingressInformer = kubeFactory.Networking().V1().Ingresses().Informer()
-	} else if c.cfg.Kubernetes.IngressVersion == config.IngressNetworkingV1beta1 {
-		ingressInformer = kubeFactory.Networking().V1beta1().Ingresses().Informer()
-	} else {
-		ingressInformer = kubeFactory.Extensions().V1beta1().Ingresses().Informer()
-	}
-
-	switch c.cfg.Kubernetes.ApisixRouteVersion {
-	case config.ApisixV2beta2:
-		apisixRouteInformer = apisixFactory.Apisix().V2beta2().ApisixRoutes().Informer()
-	case config.ApisixV2beta3:
-		apisixRouteInformer = apisixFactory.Apisix().V2beta3().ApisixRoutes().Informer()
-	case config.ApisixV2:
-		apisixRouteInformer = apisixFactory.Apisix().V2().ApisixRoutes().Informer()
-	default:
-		panic(fmt.Errorf("unsupported ApisixRoute version %s", c.cfg.Kubernetes.ApisixRouteVersion))
-	}
-
-	c.podInformer = kubeFactory.Core().V1().Pods().Informer()
-	c.svcInformer = kubeFactory.Core().V1().Services().Informer()
-	c.ingressInformer = ingressInformer
-	c.apisixRouteInformer = apisixRouteInformer
-	c.apisixUpstreamInformer = apisixUpstreamInformer
-	c.apisixClusterConfigInformer = apisixClusterConfigInformer
-	c.secretInformer = kubeFactory.Core().V1().Secrets().Informer()
-	c.apisixTlsInformer = apisixTlsInformer
-	c.apisixConsumerInformer = apisixConsumerInformer
-	c.apisixPluginConfigInformer = apisixPluginConfigInformer
-
-	if c.cfg.Kubernetes.WatchEndpointSlices {
-		c.endpointSliceController = c.newEndpointSliceController()
-	} else {
-		c.endpointsController = c.newEndpointsController()
-	}
-	c.podController = c.newPodController()
-	c.apisixUpstreamController = c.newApisixUpstreamController()
-	c.ingressController = c.newIngressController()
-	c.apisixRouteController = c.newApisixRouteController()
-	c.apisixClusterConfigController = c.newApisixClusterConfigController()
-	c.apisixTlsController = c.newApisixTlsController()
-	c.secretController = c.newSecretController()
-	c.apisixConsumerController = c.newApisixConsumerController()
-	c.apisixPluginConfigController = c.newApisixPluginConfigController()
-}
-
-func (c *Controller) syncManifests(ctx context.Context, added, updated, deleted *utils.Manifest) error {
-	return utils.SyncManifests(ctx, c.apisix, c.cfg.APISIX.DefaultClusterName, added, updated, deleted)
-}
-
-// recorderEvent recorder events for resources
-func (c *Controller) recorderEvent(object runtime.Object, eventtype, reason string, err error) {
-	if err != nil {
-		message := fmt.Sprintf(_messageResourceFailed, _component, err.Error())
-		c.recorder.Event(object, eventtype, reason, message)
-	} else {
-		message := fmt.Sprintf(_messageResourceSynced, _component)
-		c.recorder.Event(object, eventtype, reason, message)
-	}
-}
-
-// recorderEvent recorder events for resources
-func (c *Controller) recorderEventS(object runtime.Object, eventtype, reason string, msg string) {
-	c.recorder.Event(object, eventtype, reason, msg)
-}
-
-// Eventf implements the resourcelock.EventRecorder interface.
-func (c *Controller) Eventf(_ runtime.Object, eventType string, reason string, message string, _ ...interface{}) {
-	log.Infow(reason, zap.String("message", message), zap.String("event_type", eventType))
-}
-
-// Run launches the controller.
-func (c *Controller) Run(stop chan struct{}) error {
-	rootCtx, rootCancel := context.WithCancel(context.Background())
-	defer rootCancel()
-	go func() {
-		<-stop
-		rootCancel()
-	}()
-	c.MetricsCollector.ResetLeader(false)
-
-	go func() {
-		if err := c.apiServer.Run(rootCtx.Done()); err != nil {
-			log.Errorf("failed to launch API Server: %s", err)
-		}
-	}()
-
-	lock := &resourcelock.LeaseLock{
-		LeaseMeta: metav1.ObjectMeta{
-			Namespace: c.namespace,
-			Name:      c.cfg.Kubernetes.ElectionID,
-		},
-		Client: c.kubeClient.Client.CoordinationV1(),
-		LockConfig: resourcelock.ResourceLockConfig{
-			Identity:      c.name,
-			EventRecorder: c,
-		},
-	}
-	cfg := leaderelection.LeaderElectionConfig{
-		Lock:          lock,
-		LeaseDuration: 15 * time.Second,
-		RenewDeadline: 5 * time.Second,
-		RetryPeriod:   2 * time.Second,
-		Callbacks: leaderelection.LeaderCallbacks{
-			OnStartedLeading: c.run,
-			OnNewLeader: func(identity string) {
-				log.Warnf("found a new leader %s", identity)
-				if identity != c.name {
-					log.Infow("controller now is running as a candidate",
-						zap.String("namespace", c.namespace),
-						zap.String("pod", c.name),
-					)
-					c.MetricsCollector.ResetLeader(false)
-					// delete the old APISIX cluster, so that the cached state
-					// like synchronization won't be used next time the candidate
-					// becomes the leader again.
-					c.apisix.DeleteCluster(c.cfg.APISIX.DefaultClusterName)
-				}
-			},
-			OnStoppedLeading: func() {
-				log.Infow("controller now is running as a candidate",
-					zap.String("namespace", c.namespace),
-					zap.String("pod", c.name),
-				)
-				c.MetricsCollector.ResetLeader(false)
-				// delete the old APISIX cluster, so that the cached state
-				// like synchronization won't be used next time the candidate
-				// becomes the leader again.
-				c.apisix.DeleteCluster(c.cfg.APISIX.DefaultClusterName)
-			},
-		},
-		ReleaseOnCancel: true,
-		Name:            "ingress-apisix",
-	}
-	elector, err := leaderelection.NewLeaderElector(cfg)
-	if err != nil {
-		log.Errorf("failed to create leader elector: %s", err.Error())
-		return err
-	}
-
-election:
-	curCtx, cancel := context.WithCancel(rootCtx)
-	c.leaderContextCancelFunc = cancel
-	elector.Run(curCtx)
-	select {
-	case <-rootCtx.Done():
-		return nil
-	default:
-		goto election
-	}
-}
-
-func (c *Controller) run(ctx context.Context) {
-	log.Infow("controller tries to leading ...",
-		zap.String("namespace", c.namespace),
-		zap.String("pod", c.name),
-	)
-
-	var cancelFunc context.CancelFunc
-	ctx, cancelFunc = context.WithCancel(ctx)
-	defer cancelFunc()
-
-	// give up leader
-	defer c.leaderContextCancelFunc()
-
-	clusterOpts := &apisix.ClusterOptions{
-		Name:             c.cfg.APISIX.DefaultClusterName,
-		AdminKey:         c.cfg.APISIX.DefaultClusterAdminKey,
-		BaseURL:          c.cfg.APISIX.DefaultClusterBaseURL,
-		MetricsCollector: c.MetricsCollector,
-	}
-	err := c.apisix.AddCluster(ctx, clusterOpts)
-	if err != nil && err != apisix.ErrDuplicatedCluster {
-		// TODO give up the leader role
-		log.Errorf("failed to add default cluster: %s", err)
-		return
-	}
-
-	if err := c.apisix.Cluster(c.cfg.APISIX.DefaultClusterName).HasSynced(ctx); err != nil {
-		// TODO give up the leader role
-		log.Errorf("failed to wait the default cluster to be ready: %s", err)
-
-		// re-create apisix cluster, used in next c.run
-		if err = c.apisix.UpdateCluster(ctx, clusterOpts); err != nil {
-			log.Errorf("failed to update default cluster: %s", err)
-			return
-		}
-		return
-	}
-
-	c.initWhenStartLeading()
-
-	c.namespaceProvider, err = namespace.NewWatchingProvider(ctx, c.kubeClient, c.cfg)
-	if err != nil {
-		ctx.Done()
-		return
-	}
-
-	c.gatewayProvider, err = gateway.NewGatewayProvider(&gateway.ProviderOptions{
-		Cfg:               c.cfg,
-		APISIX:            c.apisix,
-		APISIXClusterName: c.cfg.APISIX.DefaultClusterName,
-		KubeTranslator:    c.translator,
-		RestConfig:        nil,
-		KubeClient:        c.kubeClient.Client,
-		MetricsCollector:  c.MetricsCollector,
-		NamespaceProvider: c.namespaceProvider,
-	})
-	if err != nil {
-		ctx.Done()
-		return
-	}
-
-	// compare resources of k8s with objects of APISIX
-	if err = c.CompareResources(ctx); err != nil {
-		ctx.Done()
-		return
-	}
-
-	e := utils.ParallelExecutor{}
-
-	e.Add(func() {
-		c.checkClusterHealth(ctx, cancelFunc)
-	})
-	e.Add(func() {
-		c.podInformer.Run(ctx.Done())
-	})
-	e.Add(func() {
-		c.epInformer.Run(ctx.Done())
-	})
-	e.Add(func() {
-		c.svcInformer.Run(ctx.Done())
-	})
-	e.Add(func() {
-		c.ingressInformer.Run(ctx.Done())
-	})
-	e.Add(func() {
-		c.apisixRouteInformer.Run(ctx.Done())
-	})
-	e.Add(func() {
-		c.apisixUpstreamInformer.Run(ctx.Done())
-	})
-	e.Add(func() {
-		c.apisixClusterConfigInformer.Run(ctx.Done())
-	})
-	e.Add(func() {
-		c.secretInformer.Run(ctx.Done())
-	})
-	e.Add(func() {
-		c.apisixTlsInformer.Run(ctx.Done())
-	})
-	e.Add(func() {
-		c.apisixConsumerInformer.Run(ctx.Done())
-	})
-	e.Add(func() {
-		c.apisixPluginConfigInformer.Run(ctx.Done())
-	})
-	e.Add(func() {
-		c.podController.run(ctx)
-	})
-	e.Add(func() {
-		if c.cfg.Kubernetes.WatchEndpointSlices {
-			c.endpointSliceController.run(ctx)
-		} else {
-			c.endpointsController.run(ctx)
-		}
-	})
-
-	e.Add(func() {
-		c.namespaceProvider.Run(ctx)
-	})
-
-	if c.cfg.Kubernetes.EnableGatewayAPI {
-		e.Add(func() {
-			c.gatewayProvider.Run(ctx)
-		})
-	}
-
-	e.Add(func() {
-		c.apisixUpstreamController.run(ctx)
-	})
-	e.Add(func() {
-		c.ingressController.run(ctx)
-	})
-	e.Add(func() {
-		c.apisixRouteController.run(ctx)
-	})
-	e.Add(func() {
-		c.apisixClusterConfigController.run(ctx)
-	})
-	e.Add(func() {
-		c.apisixTlsController.run(ctx)
-	})
-	e.Add(func() {
-		c.secretController.run(ctx)
-	})
-	e.Add(func() {
-		c.apisixConsumerController.run(ctx)
-	})
-	e.Add(func() {
-		c.apisixPluginConfigController.run(ctx)
-	})
-
-	e.Add(func() {
-		c.resourceSyncLoop(ctx, c.cfg.ApisixResourceSyncInterval.Duration)
-	})
-	c.MetricsCollector.ResetLeader(true)
-
-	log.Infow("controller now is running as leader",
-		zap.String("namespace", c.namespace),
-		zap.String("pod", c.name),
-	)
-
-	<-ctx.Done()
-	e.Wait()
-
-	for _, execErr := range e.Errors() {
-		log.Error(execErr.Error())
-	}
-	if len(e.Errors()) > 0 {
-		log.Error("Start failed, abort...")
-		cancelFunc()
-	}
-}
-
-// isWatchingNamespace accepts a resource key, getting the namespace part
-// and checking whether the namespace is being watched.
-func (c *Controller) isWatchingNamespace(key string) (ok bool) {
-	return c.namespaceProvider.IsWatchingNamespace(key)
-}
-
-func (c *Controller) syncSSL(ctx context.Context, ssl *apisixv1.Ssl, event types.EventType) error {
-	var (
-		err error
-	)
-	clusterName := c.cfg.APISIX.DefaultClusterName
-	if event == types.EventDelete {
-		err = c.apisix.Cluster(clusterName).SSL().Delete(ctx, ssl)
-	} else if event == types.EventUpdate {
-		_, err = c.apisix.Cluster(clusterName).SSL().Update(ctx, ssl)
-	} else {
-		_, err = c.apisix.Cluster(clusterName).SSL().Create(ctx, ssl)
-	}
-	return err
-}
-
-func (c *Controller) syncConsumer(ctx context.Context, consumer *apisixv1.Consumer, event types.EventType) (err error) {
-	clusterName := c.cfg.APISIX.DefaultClusterName
-	if event == types.EventDelete {
-		err = c.apisix.Cluster(clusterName).Consumer().Delete(ctx, consumer)
-	} else if event == types.EventUpdate {
-		_, err = c.apisix.Cluster(clusterName).Consumer().Update(ctx, consumer)
-	} else {
-		_, err = c.apisix.Cluster(clusterName).Consumer().Create(ctx, consumer)
-	}
-	return
-}
-
-func (c *Controller) syncEndpoint(ctx context.Context, ep kube.Endpoint) error {
-	namespace, err := ep.Namespace()
-	if err != nil {
-		return err
-	}
-	svcName := ep.ServiceName()
-	svc, err := c.svcLister.Services(namespace).Get(svcName)
-	if err != nil {
-		if k8serrors.IsNotFound(err) {
-			log.Infof("service %s/%s not found", namespace, svcName)
-			return nil
-		}
-		log.Errorf("failed to get service %s/%s: %s", namespace, svcName, err)
-		return err
-	}
-
-	switch c.cfg.Kubernetes.APIVersion {
-	case config.ApisixV2beta3:
-		var subsets []configv2beta3.ApisixUpstreamSubset
-		subsets = append(subsets, configv2beta3.ApisixUpstreamSubset{})
-		auKube, err := c.apisixUpstreamLister.V2beta3(namespace, svcName)
-		if err != nil {
-			if !k8serrors.IsNotFound(err) {
-				log.Errorf("failed to get ApisixUpstream %s/%s: %s", namespace, svcName, err)
-				return err
-			}
-		} else if auKube.V2beta3().Spec != nil && len(auKube.V2beta3().Spec.Subsets) > 0 {
-			subsets = append(subsets, auKube.V2beta3().Spec.Subsets...)
-		}
-		clusters := c.apisix.ListClusters()
-		for _, port := range svc.Spec.Ports {
-			for _, subset := range subsets {
-				nodes, err := c.translator.TranslateUpstreamNodes(ep, port.Port, subset.Labels)
-				if err != nil {
-					log.Errorw("failed to translate upstream nodes",
-						zap.Error(err),
-						zap.Any("endpoints", ep),
-						zap.Int32("port", port.Port),
-					)
-				}
-				name := apisixv1.ComposeUpstreamName(namespace, svcName, subset.Name, port.Port)
-				for _, cluster := range clusters {
-					if err := c.syncUpstreamNodesChangeToCluster(ctx, cluster, nodes, name); err != nil {
-						return err
-					}
-				}
-			}
-		}
-	case config.ApisixV2:
-		var subsets []configv2.ApisixUpstreamSubset
-		subsets = append(subsets, configv2.ApisixUpstreamSubset{})
-		auKube, err := c.apisixUpstreamLister.V2beta3(namespace, svcName)
-		if err != nil {
-			if !k8serrors.IsNotFound(err) {
-				log.Errorf("failed to get ApisixUpstream %s/%s: %s", namespace, svcName, err)
-				return err
-			}
-		} else if auKube.V2().Spec != nil && len(auKube.V2().Spec.Subsets) > 0 {
-			subsets = append(subsets, auKube.V2().Spec.Subsets...)
-		}
-		clusters := c.apisix.ListClusters()
-		for _, port := range svc.Spec.Ports {
-			for _, subset := range subsets {
-				nodes, err := c.translator.TranslateUpstreamNodes(ep, port.Port, subset.Labels)
-				if err != nil {
-					log.Errorw("failed to translate upstream nodes",
-						zap.Error(err),
-						zap.Any("endpoints", ep),
-						zap.Int32("port", port.Port),
-					)
-				}
-				name := apisixv1.ComposeUpstreamName(namespace, svcName, subset.Name, port.Port)
-				for _, cluster := range clusters {
-					if err := c.syncUpstreamNodesChangeToCluster(ctx, cluster, nodes, name); err != nil {
-						return err
-					}
-				}
-			}
-		}
-	default:
-		panic(fmt.Errorf("unsupported ApisixUpstream version %v", c.cfg.Kubernetes.APIVersion))
-	}
-	return nil
-}
-
-func (c *Controller) syncUpstreamNodesChangeToCluster(ctx context.Context, cluster apisix.Cluster, nodes apisixv1.UpstreamNodes, upsName string) error {
-	upstream, err := cluster.Upstream().Get(ctx, upsName)
-	if err != nil {
-		if err == apisixcache.ErrNotFound {
-			log.Warnw("upstream is not referenced",
-				zap.String("cluster", cluster.String()),
-				zap.String("upstream", upsName),
-			)
-			return nil
-		} else {
-			log.Errorw("failed to get upstream",
-				zap.String("upstream", upsName),
-				zap.String("cluster", cluster.String()),
-				zap.Error(err),
-			)
-			return err
-		}
-	}
-
-	upstream.Nodes = nodes
-
-	log.Debugw("upstream binds new nodes",
-		zap.Any("upstream", upstream),
-		zap.String("cluster", cluster.String()),
-	)
-
-	updated := &utils.Manifest{
-		Upstreams: []*apisixv1.Upstream{upstream},
-	}
-	return c.syncManifests(ctx, nil, updated, nil)
-}
-
-func (c *Controller) checkClusterHealth(ctx context.Context, cancelFunc context.CancelFunc) {
-	defer cancelFunc()
-	t := time.NewTicker(5 * time.Second)
-	defer t.Stop()
-	for {
-		select {
-		case <-ctx.Done():
-			return
-		case <-t.C:
-		}
-
-		err := c.apisix.Cluster(c.cfg.APISIX.DefaultClusterName).HealthCheck(ctx)
-		if err != nil {
-			// Finally failed health check, then give up leader.
-			log.Warnf("failed to check health for default cluster: %s, give up leader", err)
-			c.apiServer.HealthState.Lock()
-			defer c.apiServer.HealthState.Unlock()
-
-			c.apiServer.HealthState.Err = err
-			return
-		}
-		log.Debugf("success check health for default cluster")
-		c.MetricsCollector.IncrCheckClusterHealth(c.name)
-	}
-}
-
-func (c *Controller) syncAllResources() {
-	wg := sync.WaitGroup{}
-	goAttach := func(handler func()) {
-		wg.Add(1)
-		go func() {
-			defer wg.Done()
-			handler()
-		}()
-	}
-	goAttach(func() {
-		c.apisixConsumerController.ResourceSync()
-	})
-	goAttach(func() {
-		c.apisixRouteController.ResourceSync()
-	})
-	goAttach(func() {
-		c.apisixClusterConfigController.ResourceSync()
-	})
-	goAttach(func() {
-		c.apisixPluginConfigController.ResourceSync()
-	})
-	goAttach(func() {
-		c.apisixUpstreamController.ResourceSync()
-	})
-	goAttach(func() {
-		c.apisixTlsController.ResourceSync()
-	})
-	goAttach(func() {
-		c.ingressController.ResourceSync()
-	})
-	wg.Wait()
-}
-
-func (c *Controller) resourceSyncLoop(ctx context.Context, interval time.Duration) {
-	// The interval shall not be less than 60 seconds.
-	if interval < _mininumApisixResourceSyncInterval {
-		log.Warnw("The apisix-resource-sync-interval shall not be less than 60 seconds.",
-			zap.String("apisix-resource-sync-interval", interval.String()),
-		)
-		interval = _mininumApisixResourceSyncInterval
-	}
-	ticker := time.NewTicker(interval)
-	defer ticker.Stop()
-	for {
-		select {
-		case <-ticker.C:
-			c.syncAllResources()
-			continue
-		case <-ctx.Done():
-			return
-		}
-	}
-}
diff --git a/pkg/ingress/status.go b/pkg/ingress/status.go
deleted file mode 100644
index 9042a66c..00000000
--- a/pkg/ingress/status.go
+++ /dev/null
@@ -1,343 +0,0 @@
-// Licensed to the Apache Software Foundation (ASF) under one or more
-// contributor license agreements.  See the NOTICE file distributed with
-// this work for additional information regarding copyright ownership.
-// The ASF licenses this file to You under the Apache License, Version 2.0
-// (the "License"); you may not use this file except in compliance with
-// the License.  You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package ingress
-
-import (
-	"context"
-
-	"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"
-	"k8s.io/apimachinery/pkg/runtime"
-
-	"github.com/apache/apisix-ingress-controller/pkg/ingress/utils"
-	configv2 "github.com/apache/apisix-ingress-controller/pkg/kube/apisix/apis/config/v2"
-	configv2beta2 "github.com/apache/apisix-ingress-controller/pkg/kube/apisix/apis/config/v2beta2"
-	configv2beta3 "github.com/apache/apisix-ingress-controller/pkg/kube/apisix/apis/config/v2beta3"
-	"github.com/apache/apisix-ingress-controller/pkg/log"
-)
-
-const (
-	_conditionType        = "ResourcesAvailable"
-	_commonSuccessMessage = "Sync Successfully"
-)
-
-// verifyGeneration verify generation to decide whether to update status
-func (c *Controller) verifyGeneration(conditions *[]metav1.Condition, newCondition metav1.Condition) bool {
-	existingCondition := meta.FindStatusCondition(*conditions, newCondition.Type)
-	if existingCondition != nil && existingCondition.ObservedGeneration >= newCondition.ObservedGeneration {
-		return false
-	}
-	return true
-}
-
-// recordStatus record resources status
-func (c *Controller) recordStatus(at interface{}, reason string, err error, status metav1.ConditionStatus, generation int64) {
-	// build condition
-	message := _commonSuccessMessage
-	if err != nil {
-		message = err.Error()
-	}
-	condition := metav1.Condition{
-		Type:               _conditionType,
-		Reason:             reason,
-		Status:             status,
-		Message:            message,
-		ObservedGeneration: generation,
-	}
-	client := c.kubeClient.APISIXClient
-	kubeClient := c.kubeClient.Client
-
-	if kubeObj, ok := at.(runtime.Object); ok {
-		at = kubeObj.DeepCopyObject()
-	}
-
-	switch v := at.(type) {
-	case *configv2beta3.ApisixTls:
-		// set to status
-		if v.Status.Conditions == nil {
-			conditions := make([]metav1.Condition, 0)
-			v.Status.Conditions = conditions
-		}
-		if c.verifyGeneration(&v.Status.Conditions, condition) {
-			meta.SetStatusCondition(&v.Status.Conditions, condition)
-			if _, errRecord := client.ApisixV2beta3().ApisixTlses(v.Namespace).
-				UpdateStatus(context.TODO(), v, metav1.UpdateOptions{}); errRecord != nil {
-				log.Errorw("failed to record status change for ApisixTls",
-					zap.Error(errRecord),
-					zap.String("name", v.Name),
-					zap.String("namespace", v.Namespace),
-				)
-			}
-		}
-	case *configv2.ApisixTls:
-		// set to status
-		if v.Status.Conditions == nil {
-			conditions := make([]metav1.Condition, 0)
-			v.Status.Conditions = conditions
-		}
-		if c.verifyGeneration(&v.Status.Conditions, condition) {
-			meta.SetStatusCondition(&v.Status.Conditions, condition)
-			if _, errRecord := client.ApisixV2().ApisixTlses(v.Namespace).
-				UpdateStatus(context.TODO(), v, metav1.UpdateOptions{}); errRecord != nil {
-				log.Errorw("failed to record status change for ApisixTls",
-					zap.Error(errRecord),
-					zap.String("name", v.Name),
-					zap.String("namespace", v.Namespace),
-				)
-			}
-		}
-	case *configv2beta3.ApisixUpstream:
-		// set to status
-		if v.Status.Conditions == nil {
-			conditions := make([]metav1.Condition, 0)
-			v.Status.Conditions = conditions
-		}
-		if c.verifyGeneration(&v.Status.Conditions, condition) {
-			meta.SetStatusCondition(&v.Status.Conditions, condition)
-			if _, errRecord := client.ApisixV2beta3().ApisixUpstreams(v.Namespace).
-				UpdateStatus(context.TODO(), v, metav1.UpdateOptions{}); errRecord != nil {
-				log.Errorw("failed to record status change for ApisixUpstream",
-					zap.Error(errRecord),
-					zap.String("name", v.Name),
-					zap.String("namespace", v.Namespace),
-				)
-			}
-		}
-
-	case *configv2.ApisixUpstream:
-		// set to status
-		if v.Status.Conditions == nil {
-			conditions := make([]metav1.Condition, 0)
-			v.Status.Conditions = conditions
-		}
-		if c.verifyGeneration(&v.Status.Conditions, condition) {
-			meta.SetStatusCondition(&v.Status.Conditions, condition)
-			if _, errRecord := client.ApisixV2().ApisixUpstreams(v.Namespace).
-				UpdateStatus(context.TODO(), v, metav1.UpdateOptions{}); errRecord != nil {
-				log.Errorw("failed to record status change for ApisixUpstream",
-					zap.Error(errRecord),
-					zap.String("name", v.Name),
-					zap.String("namespace", v.Namespace),
-				)
-			}
-		}
-	case *configv2.ApisixRoute:
-		// set to status
-		if v.Status.Conditions == nil {
-			conditions := make([]metav1.Condition, 0)
-			v.Status.Conditions = conditions
-		}
-		if c.verifyGeneration(&v.Status.Conditions, condition) {
-			meta.SetStatusCondition(&v.Status.Conditions, condition)
-			if _, errRecord := client.ApisixV2().ApisixRoutes(v.Namespace).
-				UpdateStatus(context.TODO(), v, metav1.UpdateOptions{}); errRecord != nil {
-				log.Errorw("failed to record status change for ApisixRoute",
-					zap.Error(errRecord),
-					zap.String("name", v.Name),
-					zap.String("namespace", v.Namespace),
-				)
-			}
-		}
-	case *configv2beta2.ApisixRoute:
-		// set to status
-		if v.Status.Conditions == nil {
-			conditions := make([]metav1.Condition, 0)
-			v.Status.Conditions = conditions
-		}
-		if c.verifyGeneration(&v.Status.Conditions, condition) {
-			meta.SetStatusCondition(&v.Status.Conditions, condition)
-			if _, errRecord := client.ApisixV2beta2().ApisixRoutes(v.Namespace).
-				UpdateStatus(context.TODO(), v, metav1.UpdateOptions{}); errRecord != nil {
-				log.Errorw("failed to record status change for ApisixRoute",
-					zap.Error(errRecord),
-					zap.String("name", v.Name),
-					zap.String("namespace", v.Namespace),
-				)
-			}
-		}
-	case *configv2beta3.ApisixRoute:
-		// set to status
-		if v.Status.Conditions == nil {
-			conditions := make([]metav1.Condition, 0)
-			v.Status.Conditions = conditions
-		}
-		if c.verifyGeneration(&v.Status.Conditions, condition) {
-			meta.SetStatusCondition(&v.Status.Conditions, condition)
-			if _, errRecord := client.ApisixV2beta3().ApisixRoutes(v.Namespace).
-				UpdateStatus(context.TODO(), v, metav1.UpdateOptions{}); errRecord != nil {
-				log.Errorw("failed to record status change for ApisixRoute",
-					zap.Error(errRecord),
-					zap.String("name", v.Name),
-					zap.String("namespace", v.Namespace),
-				)
-			}
-		}
-	case *configv2beta3.ApisixConsumer:
-		// set to status
-		if v.Status.Conditions == nil {
-			conditions := make([]metav1.Condition, 0)
-			v.Status.Conditions = conditions
-		}
-		if c.verifyGeneration(&v.Status.Conditions, condition) {
-			meta.SetStatusCondition(&v.Status.Conditions, condition)
-			if _, errRecord := client.ApisixV2beta3().ApisixConsumers(v.Namespace).
-				UpdateStatus(context.TODO(), v, metav1.UpdateOptions{}); errRecord != nil {
-				log.Errorw("failed to record status change for ApisixConsumer",
-					zap.Error(errRecord),
-					zap.String("name", v.Name),
-					zap.String("namespace", v.Namespace),
-				)
-			}
-		}
-	case *configv2.ApisixConsumer:
-		// set to status
-		if v.Status.Conditions == nil {
-			conditions := make([]metav1.Condition, 0)
-			v.Status.Conditions = conditions
-		}
-		if c.verifyGeneration(&v.Status.Conditions, condition) {
-			meta.SetStatusCondition(&v.Status.Conditions, condition)
-			if _, errRecord := client.ApisixV2().ApisixConsumers(v.Namespace).
-				UpdateStatus(context.TODO(), v, metav1.UpdateOptions{}); errRecord != nil {
-				log.Errorw("failed to record status change for ApisixConsumer",
-					zap.Error(errRecord),
-					zap.String("name", v.Name),
-					zap.String("namespace", v.Namespace),
-				)
-			}
-		}
-	case *configv2beta3.ApisixPluginConfig:
-		// set to status
-		if v.Status.Conditions == nil {
-			conditions := make([]metav1.Condition, 0)
-			v.Status.Conditions = conditions
-		}
-		if c.verifyGeneration(&v.Status.Conditions, condition) {
-			meta.SetStatusCondition(&v.Status.Conditions, condition)
-			if _, errRecord := client.ApisixV2beta3().ApisixPluginConfigs(v.Namespace).
-				UpdateStatus(context.TODO(), v, metav1.UpdateOptions{}); errRecord != nil {
-				log.Errorw("failed to record status change for ApisixPluginConfig",
-					zap.Error(errRecord),
-					zap.String("name", v.Name),
-					zap.String("namespace", v.Namespace),
-				)
-			}
-		}
-	case *configv2beta3.ApisixClusterConfig:
-		// set to status
-		if v.Status.Conditions == nil {
-			conditions := make([]metav1.Condition, 0)
-			v.Status.Conditions = conditions
-		}
-		if c.verifyGeneration(&v.Status.Conditions, condition) {
-			meta.SetStatusCondition(&v.Status.Conditions, condition)
-			if _, errRecord := client.ApisixV2beta3().ApisixClusterConfigs().
-				UpdateStatus(context.TODO(), v, metav1.UpdateOptions{}); errRecord != nil {
-				log.Errorw("failed to record status change for ApisixClusterConfig",
-					zap.Error(errRecord),
-					zap.String("name", v.Name),
-				)
-			}
-		}
-	case *configv2.ApisixClusterConfig:
-		// set to status
-		if v.Status.Conditions == nil {
-			conditions := make([]metav1.Condition, 0)
-			v.Status.Conditions = conditions
-		}
-		if c.verifyGeneration(&v.Status.Conditions, condition) {
-			meta.SetStatusCondition(&v.Status.Conditions, condition)
-			if _, errRecord := client.ApisixV2().ApisixClusterConfigs().
-				UpdateStatus(context.TODO(), v, metav1.UpdateOptions{}); errRecord != nil {
-				log.Errorw("failed to record status change for ApisixClusterConfig",
-					zap.Error(errRecord),
-					zap.String("name", v.Name),
-				)
-			}
-		}
-	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.ObjectMeta.Generation = generation
-		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.ObjectMeta.Generation = generation
-		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.ObjectMeta.Generation = generation
-		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)
-	}
-}
-
-// ingressLBStatusIPs organizes the available addresses
-func (c *Controller) ingressLBStatusIPs() ([]apiv1.LoadBalancerIngress, error) {
-	return utils.IngressLBStatusIPs(c.cfg.IngressPublishService, c.cfg.IngressStatusAddress, c.kubeClient.Client)
-}
diff --git a/pkg/kube/translation/ingress_test.go b/pkg/kube/translation/ingress_test.go
deleted file mode 100644
index ab81b966..00000000
--- a/pkg/kube/translation/ingress_test.go
+++ /dev/null
@@ -1,1376 +0,0 @@
-// Licensed to the Apache Software Foundation (ASF) under one or more
-// contributor license agreements.  See the NOTICE file distributed with
-// this work for additional information regarding copyright ownership.
-// The ASF licenses this file to You under the Apache License, Version 2.0
-// (the "License"); you may not use this file except in compliance with
-// the License.  You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-package translation
-
-import (
-	"context"
-	"path"
-	"testing"
-
-	"github.com/stretchr/testify/assert"
-	corev1 "k8s.io/api/core/v1"
-	extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
-	networkingv1 "k8s.io/api/networking/v1"
-	networkingv1beta1 "k8s.io/api/networking/v1beta1"
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"k8s.io/apimachinery/pkg/util/intstr"
-	"k8s.io/client-go/informers"
-	"k8s.io/client-go/kubernetes/fake"
-	"k8s.io/client-go/tools/cache"
-
-	"github.com/apache/apisix-ingress-controller/pkg/config"
-	"github.com/apache/apisix-ingress-controller/pkg/id"
-	"github.com/apache/apisix-ingress-controller/pkg/kube"
-	configv2 "github.com/apache/apisix-ingress-controller/pkg/kube/apisix/apis/config/v2"
-	fakeapisix "github.com/apache/apisix-ingress-controller/pkg/kube/apisix/client/clientset/versioned/fake"
-	apisixinformers "github.com/apache/apisix-ingress-controller/pkg/kube/apisix/client/informers/externalversions"
-	apisixconst "github.com/apache/apisix-ingress-controller/pkg/kube/apisix/const"
-	"github.com/apache/apisix-ingress-controller/pkg/kube/translation/annotations"
-	apisixv1 "github.com/apache/apisix-ingress-controller/pkg/types/apisix/v1"
-	v1 "github.com/apache/apisix-ingress-controller/pkg/types/apisix/v1"
-)
-
-var (
-	_testSvc = &corev1.Service{
-		TypeMeta: metav1.TypeMeta{},
-		ObjectMeta: metav1.ObjectMeta{
-			Name:      "test-service",
-			Namespace: "default",
-		},
-		Spec: corev1.ServiceSpec{
-			Ports: []corev1.ServicePort{
-				{
-					Name: "port1",
-					Port: 80,
-					TargetPort: intstr.IntOrString{
-						Type:   intstr.Int,
-						IntVal: 9080,
-					},
-				},
-				{
-					Name: "port2",
-					Port: 443,
-					TargetPort: intstr.IntOrString{
-						Type:   intstr.Int,
-						IntVal: 9443,
-					},
-				},
-			},
-		},
-	}
-	_testEp = &corev1.Endpoints{
-		TypeMeta: metav1.TypeMeta{},
-		ObjectMeta: metav1.ObjectMeta{
-			Name:      "test-service",
-			Namespace: "default",
-		},
-		Subsets: []corev1.EndpointSubset{
-			{
-				Ports: []corev1.EndpointPort{
-					{
-						Name: "port1",
-						Port: 9080,
-					},
-					{
-						Name: "port2",
-						Port: 9443,
-					},
-				},
-				Addresses: []corev1.EndpointAddress{
-					{IP: "192.168.1.1"},
-					{IP: "192.168.1.2"},
-				},
-			},
-		},
-	}
-)
-
-func TestTranslateIngressV1NoBackend(t *testing.T) {
-	prefix := networkingv1.PathTypePrefix
-	// no backend.
-	ing := &networkingv1.Ingress{
-		ObjectMeta: metav1.ObjectMeta{
-			Name:      "test",
-			Namespace: "default",
-		},
-		Spec: networkingv1.IngressSpec{
-			Rules: []networkingv1.IngressRule{
-				{
-					Host: "apisix.apache.org",
-					IngressRuleValue: networkingv1.IngressRuleValue{
-						HTTP: &networkingv1.HTTPIngressRuleValue{
-							Paths: []networkingv1.HTTPIngressPath{
-								{
-									Path:     "/foo",
-									PathType: &prefix,
-								},
-							},
-						},
-					},
-				},
-			},
-		},
-	}
-	tr := &translator{}
-	ctx, err := tr.translateIngressV1(ing, false)
-	assert.Nil(t, err)
-	assert.Len(t, ctx.Routes, 1)
-	assert.Len(t, ctx.Upstreams, 0)
-	assert.Len(t, ctx.PluginConfigs, 0)
-	assert.Equal(t, "", ctx.Routes[0].UpstreamId)
-	assert.Equal(t, "", ctx.Routes[0].PluginConfigId)
-	assert.Equal(t, []string{"/foo", "/foo/*"}, ctx.Routes[0].Uris)
-}
-
-func TestTranslateIngressV1BackendWithInvalidService(t *testing.T) {
-	prefix := networkingv1.PathTypePrefix
-	// no backend.
-	ing := &networkingv1.Ingress{
-		ObjectMeta: metav1.ObjectMeta{
-			Name:      "test",
-			Namespace: "default",
-		},
-		Spec: networkingv1.IngressSpec{
-			Rules: []networkingv1.IngressRule{
-				{
-					Host: "apisix.apache.org",
-					IngressRuleValue: networkingv1.IngressRuleValue{
-						HTTP: &networkingv1.HTTPIngressRuleValue{
-							Paths: []networkingv1.HTTPIngressPath{
-								{
-									Path:     "/foo",
-									PathType: &prefix,
-									Backend: networkingv1.IngressBackend{
-										Service: &networkingv1.IngressServiceBackend{
-											Name: "test-service",
-											Port: networkingv1.ServiceBackendPort{
-												Name: "undefined-port",
-											},
-										},
-									},
-								},
-							},
-						},
-					},
-				},
-			},
-		},
-	}
-	client := fake.NewSimpleClientset()
-	informersFactory := informers.NewSharedInformerFactory(client, 0)
-	svcInformer := informersFactory.Core().V1().Services().Informer()
-	svcLister := informersFactory.Core().V1().Services().Lister()
-	tr := &translator{
-		TranslatorOptions: &TranslatorOptions{
-			ServiceLister: svcLister,
-		},
-	}
-	ctx, err := tr.translateIngressV1(ing, false)
-	assert.NotNil(t, err)
-	assert.Nil(t, ctx)
-	assert.Equal(t, "service \"test-service\" not found", err.Error())
-
-	processCh := make(chan struct{})
-	svcInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
-		AddFunc: func(obj interface{}) {
-			processCh <- struct{}{}
-		},
-	})
-
-	stopCh := make(chan struct{})
-	defer close(stopCh)
-	go svcInformer.Run(stopCh)
-	cache.WaitForCacheSync(stopCh, svcInformer.HasSynced)
-
-	_, err = client.CoreV1().Services("default").Create(context.Background(), _testSvc, metav1.CreateOptions{})
-	assert.Nil(t, err)
-	_, err = client.CoreV1().Endpoints("default").Create(context.Background(), _testEp, metav1.CreateOptions{})
-	assert.Nil(t, err)
-
-	<-processCh
-	ctx, err = tr.translateIngressV1(ing, false)
-	assert.Nil(t, ctx, nil)
-	assert.Equal(t, &translateError{
-		field:  "service",
-		reason: "port not found",
-	}, err)
-}
-
-func TestTranslateIngressV1WithRegex(t *testing.T) {
-	testTranslateIngressV1WithRegexReferenceUpstreamVersion(t, config.ApisixV2)
-	testTranslateIngressV1WithRegexReferenceUpstreamVersion(t, config.ApisixV2beta3)
-}
-
-func testTranslateIngressV1WithRegexReferenceUpstreamVersion(t *testing.T, apiVersion string) func(t *testing.T) {
-	return func(t *testing.T) {
-		prefix := networkingv1.PathTypeImplementationSpecific
-		regexPath := "/foo/*/bar"
-		ing := &networkingv1.Ingress{
-			ObjectMeta: metav1.ObjectMeta{
-				Name:      "test",
-				Namespace: "default",
-				Annotations: map[string]string{
-					"k8s.apisix.apache.org/use-regex": "true",
-				},
-			},
-			Spec: networkingv1.IngressSpec{
-				Rules: []networkingv1.IngressRule{
-					{
-						Host: "apisix.apache.org",
-						IngressRuleValue: networkingv1.IngressRuleValue{
-							HTTP: &networkingv1.HTTPIngressRuleValue{
-								Paths: []networkingv1.HTTPIngressPath{
-									{
-										Path:     regexPath,
-										PathType: &prefix,
-										Backend: networkingv1.IngressBackend{
-											Service: &networkingv1.IngressServiceBackend{
-												Name: "test-service",
-												Port: networkingv1.ServiceBackendPort{
-													Name: "port1",
-												},
-											},
-										},
-									},
-								},
-							},
-						},
-					},
-				},
-			},
-		}
-		client := fake.NewSimpleClientset()
-		informersFactory := informers.NewSharedInformerFactory(client, 0)
-		svcInformer := informersFactory.Core().V1().Services().Informer()
-		svcLister := informersFactory.Core().V1().Services().Lister()
-		epLister, epInformer := kube.NewEndpointListerAndInformer(informersFactory, false)
-		apisixClient := fakeapisix.NewSimpleClientset()
-		apisixInformersFactory := apisixinformers.NewSharedInformerFactory(apisixClient, 0)
-		processCh := make(chan struct{})
-		svcInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
-			AddFunc: func(obj interface{}) {
-				processCh <- struct{}{}
-			},
-		})
-		epInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
-			AddFunc: func(obj interface{}) {
-				processCh <- struct{}{}
-			},
-		})
-
-		stopCh := make(chan struct{})
-		defer close(stopCh)
-		go svcInformer.Run(stopCh)
-		go epInformer.Run(stopCh)
-		cache.WaitForCacheSync(stopCh, svcInformer.HasSynced)
-
-		_, err := client.CoreV1().Services("default").Create(context.Background(), _testSvc, metav1.CreateOptions{})
-		assert.Nil(t, err)
-		_, err = client.CoreV1().Endpoints("default").Create(context.Background(), _testEp, metav1.CreateOptions{})
-		assert.Nil(t, err)
-
-		tr := &translator{
-			TranslatorOptions: &TranslatorOptions{
-				ServiceLister:  svcLister,
-				EndpointLister: epLister,
-				ApisixUpstreamLister: kube.NewApisixUpstreamLister(
-					apisixInformersFactory.Apisix().V2beta3().ApisixUpstreams().Lister(),
-					apisixInformersFactory.Apisix().V2().ApisixUpstreams().Lister(),
-				),
-				APIVersion: apiVersion,
-			},
-		}
-
-		<-processCh
-		<-processCh
-		ctx, err := tr.translateIngressV1(ing, false)
-		assert.Nil(t, err)
-		assert.Len(t, ctx.Routes, 1)
-		assert.Len(t, ctx.Upstreams, 1)
-		// the number of the PluginConfigs should be zero, cause there no available Annotations matched te rule
-		assert.Len(t, ctx.PluginConfigs, 0)
-		routeVars, err := tr.translateRouteMatchExprs([]configv2.ApisixRouteHTTPMatchExpr{{
-			Subject: configv2.ApisixRouteHTTPMatchExprSubject{
-				Scope: apisixconst.ScopePath,
-			},
-			Op:    apisixconst.OpRegexMatch,
-			Value: &regexPath,
-		}})
-		assert.Nil(t, err)
-
-		var expectedVars v1.Vars = routeVars
-
-		assert.Equal(t, []string{"/*"}, ctx.Routes[0].Uris)
-		assert.Equal(t, expectedVars, ctx.Routes[0].Vars)
-	}
-}
-
-func TestTranslateIngressV1(t *testing.T) {
-	testTranslateIngressV1ReferenceUpstreamVersion(t, config.ApisixV2beta3)
-	testTranslateIngressV1ReferenceUpstreamVersion(t, config.ApisixV2)
-}
-
-func testTranslateIngressV1ReferenceUpstreamVersion(t *testing.T, apiVersoin string) func(*testing.T) {
-	return func(*testing.T) {
-		prefix := networkingv1.PathTypePrefix
-		// no backend.
-		ing := &networkingv1.Ingress{
-			ObjectMeta: metav1.ObjectMeta{
-				Name:      "test",
-				Namespace: "default",
-				Annotations: map[string]string{
-					"k8s.apisix.apache.org/use-regex":                                  "true",
-					path.Join(annotations.AnnotationsPrefix, "enable-cors"):            "true",
-					path.Join(annotations.AnnotationsPrefix, "allowlist-source-range"): "127.0.0.1",
-					path.Join(annotations.AnnotationsPrefix, "plugin-config-name"):     "echo-and-cors-apc",
-				},
-			},
-			Spec: networkingv1.IngressSpec{
-				Rules: []networkingv1.IngressRule{
-					{
-						Host: "apisix.apache.org",
-						IngressRuleValue: networkingv1.IngressRuleValue{
-							HTTP: &networkingv1.HTTPIngressRuleValue{
-								Paths: []networkingv1.HTTPIngressPath{
-									{
-										Path:     "/foo",
-										PathType: &prefix,
-										Backend: networkingv1.IngressBackend{
-											Service: &networkingv1.IngressServiceBackend{
-												Name: "test-service",
-												Port: networkingv1.ServiceBackendPort{
-													Name: "port1",
-												},
-											},
-										},
-									},
-									{
-										Path: "/bar",
-										Backend: networkingv1.IngressBackend{
-											Service: &networkingv1.IngressServiceBackend{
-												Name: "test-service",
-												Port: networkingv1.ServiceBackendPort{
-													Number: 443,
-												},
-											},
-										},
-									},
-								},
-							},
-						},
-					},
-				},
-			},
-		}
-		client := fake.NewSimpleClientset()
-		informersFactory := informers.NewSharedInformerFactory(client, 0)
-		svcInformer := informersFactory.Core().V1().Services().Informer()
-		svcLister := informersFactory.Core().V1().Services().Lister()
-		epLister, epInformer := kube.NewEndpointListerAndInformer(informersFactory, false)
-		apisixClient := fakeapisix.NewSimpleClientset()
-		apisixInformersFactory := apisixinformers.NewSharedInformerFactory(apisixClient, 0)
-		processCh := make(chan struct{})
-		svcInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
-			AddFunc: func(obj interface{}) {
-				processCh <- struct{}{}
-			},
-		})
-		epInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
-			AddFunc: func(obj interface{}) {
-				processCh <- struct{}{}
-			},
-		})
-
-		stopCh := make(chan struct{})
-		defer close(stopCh)
-		go svcInformer.Run(stopCh)
-		go epInformer.Run(stopCh)
-		cache.WaitForCacheSync(stopCh, svcInformer.HasSynced)
-
-		_, err := client.CoreV1().Services("default").Create(context.Background(), _testSvc, metav1.CreateOptions{})
-		assert.Nil(t, err)
-		_, err = client.CoreV1().Endpoints("default").Create(context.Background(), _testEp, metav1.CreateOptions{})
-		assert.Nil(t, err)
-
-		tr := &translator{
-			TranslatorOptions: &TranslatorOptions{
-				ServiceLister:  svcLister,
-				EndpointLister: epLister,
-				ApisixUpstreamLister: kube.NewApisixUpstreamLister(
-					apisixInformersFactory.Apisix().V2beta3().ApisixUpstreams().Lister(),
-					apisixInformersFactory.Apisix().V2().ApisixUpstreams().Lister(),
-				),
-				APIVersion: apiVersoin,
-			},
-		}
-
-		<-processCh
-		<-processCh
-		ctx, err := tr.translateIngressV1(ing, false)
-		annoExtractor := annotations.NewExtractor(ing.Annotations)
-		pluginConfigName := annoExtractor.GetStringAnnotation(path.Join(annotations.AnnotationsPrefix, "plugin-config-name"))
-
-		assert.Nil(t, err)
-		assert.Len(t, ctx.Routes, 2)
-		assert.Len(t, ctx.Upstreams, 2)
-
-		assert.Equal(t, []string{"/foo", "/foo/*"}, ctx.Routes[0].Uris)
-		assert.Equal(t, ctx.Upstreams[0].ID, ctx.Routes[0].UpstreamId)
-		assert.Equal(t, "apisix.apache.org", ctx.Routes[0].Host)
-		assert.Len(t, ctx.Routes[0].Plugins, 2)
-		assert.Equal(t, ctx.Routes[0].PluginConfigId, id.GenID(apisixv1.ComposePluginConfigName(ing.Namespace, pluginConfigName)))
-		assert.Equal(t, []string{"/bar"}, ctx.Routes[1].Uris)
-		assert.Equal(t, ctx.Upstreams[1].ID, ctx.Routes[1].UpstreamId)
-		assert.Equal(t, "apisix.apache.org", ctx.Routes[1].Host)
-		assert.Len(t, ctx.Routes[1].Plugins, 2)
-		assert.Equal(t, ctx.Routes[1].PluginConfigId, id.GenID(apisixv1.ComposePluginConfigName(ing.Namespace, pluginConfigName)))
-
-		assert.Equal(t, "roundrobin", ctx.Upstreams[0].Type)
-		assert.Equal(t, "http", ctx.Upstreams[0].Scheme)
-		assert.Len(t, ctx.Upstreams[0].Nodes, 2)
-		assert.Equal(t, 9080, ctx.Upstreams[0].Nodes[0].Port)
-		assert.Equal(t, "192.168.1.1", ctx.Upstreams[0].Nodes[0].Host)
-		assert.Equal(t, 9080, ctx.Upstreams[0].Nodes[1].Port)
-		assert.Equal(t, "192.168.1.2", ctx.Upstreams[0].Nodes[1].Host)
-
-		assert.Equal(t, "roundrobin", ctx.Upstreams[1].Type)
-		assert.Equal(t, "http", ctx.Upstreams[1].Scheme)
-		assert.Len(t, ctx.Upstreams[1].Nodes, 2)
-		assert.Equal(t, 9443, ctx.Upstreams[1].Nodes[0].Port)
-		assert.Equal(t, "192.168.1.1", ctx.Upstreams[1].Nodes[0].Host)
-		assert.Equal(t, 9443, ctx.Upstreams[1].Nodes[1].Port)
-		assert.Equal(t, "192.168.1.2", ctx.Upstreams[1].Nodes[1].Host)
-	}
-}
-
-func TestTranslateIngressV1beta1NoBackend(t *testing.T) {
-	prefix := networkingv1beta1.PathTypePrefix
-	// no backend.
-	ing := &networkingv1beta1.Ingress{
-		ObjectMeta: metav1.ObjectMeta{
-			Name:      "test",
-			Namespace: "default",
-		},
-		Spec: networkingv1beta1.IngressSpec{
-			Rules: []networkingv1beta1.IngressRule{
-				{
-					Host: "apisix.apache.org",
-					IngressRuleValue: networkingv1beta1.IngressRuleValue{
-						HTTP: &networkingv1beta1.HTTPIngressRuleValue{
-							Paths: []networkingv1beta1.HTTPIngressPath{
-								{
-									Path:     "/foo",
-									PathType: &prefix,
-								},
-							},
-						},
-					},
-				},
-			},
-		},
-	}
-	tr := &translator{}
-	ctx, err := tr.translateIngressV1beta1(ing, false)
-	assert.Nil(t, err)
-	assert.Len(t, ctx.Routes, 1)
-	assert.Len(t, ctx.Upstreams, 0)
-	assert.Len(t, ctx.PluginConfigs, 0)
-	assert.Equal(t, "", ctx.Routes[0].UpstreamId)
-	assert.Equal(t, "", ctx.Routes[0].PluginConfigId)
-	assert.Equal(t, []string{"/foo", "/foo/*"}, ctx.Routes[0].Uris)
-}
-
-func TestTranslateIngressV1beta1BackendWithInvalidService(t *testing.T) {
-	prefix := networkingv1beta1.PathTypePrefix
-	// no backend.
-	ing := &networkingv1beta1.Ingress{
-		ObjectMeta: metav1.ObjectMeta{
-			Name:      "test",
-			Namespace: "default",
-		},
-		Spec: networkingv1beta1.IngressSpec{
-			Rules: []networkingv1beta1.IngressRule{
-				{
-					Host: "apisix.apache.org",
-					IngressRuleValue: networkingv1beta1.IngressRuleValue{
-						HTTP: &networkingv1beta1.HTTPIngressRuleValue{
-							Paths: []networkingv1beta1.HTTPIngressPath{
-								{
-									Path:     "/foo",
-									PathType: &prefix,
-									Backend: networkingv1beta1.IngressBackend{
-										ServiceName: "test-service",
-										ServicePort: intstr.IntOrString{
-											Type:   intstr.String,
-											StrVal: "undefined-port",
-										},
-									},
-								},
-							},
-						},
-					},
-				},
-			},
-		},
-	}
-	client := fake.NewSimpleClientset()
-	informersFactory := informers.NewSharedInformerFactory(client, 0)
-	svcInformer := informersFactory.Core().V1().Services().Informer()
-	svcLister := informersFactory.Core().V1().Services().Lister()
-	tr := &translator{
-		TranslatorOptions: &TranslatorOptions{
-			ServiceLister: svcLister,
-		},
-	}
-	ctx, err := tr.translateIngressV1beta1(ing, false)
-	assert.NotNil(t, err)
-	assert.Nil(t, ctx)
-	assert.Equal(t, "service \"test-service\" not found", err.Error())
-
-	processCh := make(chan struct{})
-	svcInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
-		AddFunc: func(obj interface{}) {
-			processCh <- struct{}{}
-		},
-	})
-
-	stopCh := make(chan struct{})
-	defer close(stopCh)
-	go svcInformer.Run(stopCh)
-	cache.WaitForCacheSync(stopCh, svcInformer.HasSynced)
-
-	_, err = client.CoreV1().Services("default").Create(context.Background(), _testSvc, metav1.CreateOptions{})
-	assert.Nil(t, err)
-	_, err = client.CoreV1().Endpoints("default").Create(context.Background(), _testEp, metav1.CreateOptions{})
-	assert.Nil(t, err)
-
-	<-processCh
-	ctx, err = tr.translateIngressV1beta1(ing, false)
-	assert.Nil(t, ctx)
-	assert.Equal(t, &translateError{
-		field:  "service",
-		reason: "port not found",
-	}, err)
-}
-
-func TestTranslateIngressV1beta1WithRegex(t *testing.T) {
-	prefix := networkingv1beta1.PathTypeImplementationSpecific
-	// no backend.
-	regexPath := "/foo/*/bar"
-	ing := &networkingv1beta1.Ingress{
-		ObjectMeta: metav1.ObjectMeta{
-			Name:      "test",
-			Namespace: "default",
-			Annotations: map[string]string{
-				"k8s.apisix.apache.org/use-regex": "true",
-			},
-		},
-		Spec: networkingv1beta1.IngressSpec{
-			Rules: []networkingv1beta1.IngressRule{
-				{
-					Host: "apisix.apache.org",
-					IngressRuleValue: networkingv1beta1.IngressRuleValue{
-						HTTP: &networkingv1beta1.HTTPIngressRuleValue{
-							Paths: []networkingv1beta1.HTTPIngressPath{
-								{
-									Path:     regexPath,
-									PathType: &prefix,
-									Backend: networkingv1beta1.IngressBackend{
-										ServiceName: "test-service",
-										ServicePort: intstr.IntOrString{
-											Type:   intstr.String,
-											StrVal: "port1",
-										},
-									},
-								},
-							},
-						},
-					},
-				},
-			},
-		},
-	}
-	client := fake.NewSimpleClientset()
-	informersFactory := informers.NewSharedInformerFactory(client, 0)
-	svcInformer := informersFactory.Core().V1().Services().Informer()
-	svcLister := informersFactory.Core().V1().Services().Lister()
-	epLister, epInformer := kube.NewEndpointListerAndInformer(informersFactory, false)
-	apisixClient := fakeapisix.NewSimpleClientset()
-	apisixInformersFactory := apisixinformers.NewSharedInformerFactory(apisixClient, 0)
-	processCh := make(chan struct{})
-	svcInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
-		AddFunc: func(obj interface{}) {
-			processCh <- struct{}{}
-		},
-	})
-	epInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
-		AddFunc: func(obj interface{}) {
-			processCh <- struct{}{}
-		},
-	})
-
-	stopCh := make(chan struct{})
-	defer close(stopCh)
-	go svcInformer.Run(stopCh)
-	go epInformer.Run(stopCh)
-	cache.WaitForCacheSync(stopCh, svcInformer.HasSynced)
-
-	_, err := client.CoreV1().Services("default").Create(context.Background(), _testSvc, metav1.CreateOptions{})
-	assert.Nil(t, err)
-	_, err = client.CoreV1().Endpoints("default").Create(context.Background(), _testEp, metav1.CreateOptions{})
-	assert.Nil(t, err)
-
-	tr := &translator{
-		TranslatorOptions: &TranslatorOptions{
-			ServiceLister:  svcLister,
-			EndpointLister: epLister,
-			ApisixUpstreamLister: kube.NewApisixUpstreamLister(
-				apisixInformersFactory.Apisix().V2beta3().ApisixUpstreams().Lister(),
-				apisixInformersFactory.Apisix().V2().ApisixUpstreams().Lister(),
-			),
-			APIVersion: config.DefaultAPIVersion,
-		},
-	}
-
-	<-processCh
-	<-processCh
-	ctx, err := tr.translateIngressV1beta1(ing, false)
-	assert.Nil(t, err)
-	assert.Len(t, ctx.Routes, 1)
-	assert.Len(t, ctx.Upstreams, 1)
-	// the number of the PluginConfigs should be zero, cause there no available Annotations matched te rule
-	assert.Len(t, ctx.PluginConfigs, 0)
-
-	routeVars, err := tr.translateRouteMatchExprs([]configv2.ApisixRouteHTTPMatchExpr{{
-		Subject: configv2.ApisixRouteHTTPMatchExprSubject{
-			Scope: apisixconst.ScopePath,
-		},
-		Op:    apisixconst.OpRegexMatch,
-		Value: &regexPath,
-	}})
-	assert.Nil(t, err)
-	var expectedVars v1.Vars = routeVars
-	assert.Equal(t, []string{"/*"}, ctx.Routes[0].Uris)
-	assert.Equal(t, expectedVars, ctx.Routes[0].Vars)
-}
-
-func TestTranslateIngressV1beta1(t *testing.T) {
-	prefix := networkingv1beta1.PathTypePrefix
-	// no backend.
-	ing := &networkingv1beta1.Ingress{
-		ObjectMeta: metav1.ObjectMeta{
-			Name:      "test",
-			Namespace: "default",
-			Annotations: map[string]string{
-				"k8s.apisix.apache.org/use-regex":                                  "true",
-				path.Join(annotations.AnnotationsPrefix, "enable-cors"):            "true",
-				path.Join(annotations.AnnotationsPrefix, "allowlist-source-range"): "127.0.0.1",
-				path.Join(annotations.AnnotationsPrefix, "enable-cors222"):         "true",
-				path.Join(annotations.AnnotationsPrefix, "plugin-config-name"):     "echo-and-cors-apc",
-			},
-		},
-		Spec: networkingv1beta1.IngressSpec{
-			Rules: []networkingv1beta1.IngressRule{
-				{
-					Host: "apisix.apache.org",
-					IngressRuleValue: networkingv1beta1.IngressRuleValue{
-						HTTP: &networkingv1beta1.HTTPIngressRuleValue{
-							Paths: []networkingv1beta1.HTTPIngressPath{
-								{
-									Path:     "/foo",
-									PathType: &prefix,
-									Backend: networkingv1beta1.IngressBackend{
-										ServiceName: "test-service",
-										ServicePort: intstr.IntOrString{
-											Type:   intstr.String,
-											StrVal: "port1",
-										},
-									},
-								},
-								{
-									Path: "/bar",
-									Backend: networkingv1beta1.IngressBackend{
-										ServiceName: "test-service",
-										ServicePort: intstr.IntOrString{
-											Type:   intstr.Int,
-											IntVal: 443,
-										},
-									},
-								},
-							},
-						},
-					},
-				},
-			},
-		},
-	}
-	client := fake.NewSimpleClientset()
-	informersFactory := informers.NewSharedInformerFactory(client, 0)
-	svcInformer := informersFactory.Core().V1().Services().Informer()
-	svcLister := informersFactory.Core().V1().Services().Lister()
-	epLister, epInformer := kube.NewEndpointListerAndInformer(informersFactory, false)
-	apisixClient := fakeapisix.NewSimpleClientset()
-	apisixInformersFactory := apisixinformers.NewSharedInformerFactory(apisixClient, 0)
-	processCh := make(chan struct{})
-	svcInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
-		AddFunc: func(obj interface{}) {
-			processCh <- struct{}{}
-		},
-	})
-	epInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
-		AddFunc: func(obj interface{}) {
-			processCh <- struct{}{}
-		},
-	})
-
-	stopCh := make(chan struct{})
-	defer close(stopCh)
-	go svcInformer.Run(stopCh)
-	go epInformer.Run(stopCh)
-	cache.WaitForCacheSync(stopCh, svcInformer.HasSynced)
-
-	_, err := client.CoreV1().Services("default").Create(context.Background(), _testSvc, metav1.CreateOptions{})
-	assert.Nil(t, err)
-	_, err = client.CoreV1().Endpoints("default").Create(context.Background(), _testEp, metav1.CreateOptions{})
-	assert.Nil(t, err)
-
-	tr := &translator{
-		TranslatorOptions: &TranslatorOptions{
-			ServiceLister:  svcLister,
-			EndpointLister: epLister,
-			ApisixUpstreamLister: kube.NewApisixUpstreamLister(
-				apisixInformersFactory.Apisix().V2beta3().ApisixUpstreams().Lister(),
-				apisixInformersFactory.Apisix().V2().ApisixUpstreams().Lister(),
-			),
-			APIVersion: config.DefaultAPIVersion,
-		},
-	}
-
-	<-processCh
-	<-processCh
-	ctx, err := tr.translateIngressV1beta1(ing, false)
-	annoExtractor := annotations.NewExtractor(ing.Annotations)
-	pluginConfigName := annoExtractor.GetStringAnnotation(path.Join(annotations.AnnotationsPrefix, "plugin-config-name"))
-
-	assert.Nil(t, err)
-	assert.Len(t, ctx.Routes, 2)
-	assert.Len(t, ctx.Upstreams, 2)
-
-	assert.Equal(t, []string{"/foo", "/foo/*"}, ctx.Routes[0].Uris)
-	assert.Equal(t, ctx.Upstreams[0].ID, ctx.Routes[0].UpstreamId)
-	assert.Equal(t, "apisix.apache.org", ctx.Routes[0].Host)
-	assert.Len(t, ctx.Routes[0].Plugins, 2)
-	assert.Equal(t, ctx.Routes[0].PluginConfigId, id.GenID(apisixv1.ComposePluginConfigName(ing.Namespace, pluginConfigName)))
-
-	assert.Equal(t, []string{"/bar"}, ctx.Routes[1].Uris)
-	assert.Equal(t, ctx.Upstreams[1].ID, ctx.Routes[1].UpstreamId)
-	assert.Equal(t, "apisix.apache.org", ctx.Routes[1].Host)
-	assert.Len(t, ctx.Routes[1].Plugins, 2)
-	assert.Equal(t, ctx.Routes[1].PluginConfigId, id.GenID(apisixv1.ComposePluginConfigName(ing.Namespace, pluginConfigName)))
-
-	assert.Equal(t, "roundrobin", ctx.Upstreams[0].Type)
-	assert.Equal(t, "http", ctx.Upstreams[0].Scheme)
-	assert.Len(t, ctx.Upstreams[0].Nodes, 2)
-	assert.Equal(t, 9080, ctx.Upstreams[0].Nodes[0].Port)
-	assert.Equal(t, "192.168.1.1", ctx.Upstreams[0].Nodes[0].Host)
-	assert.Equal(t, 9080, ctx.Upstreams[0].Nodes[1].Port)
-	assert.Equal(t, "192.168.1.2", ctx.Upstreams[0].Nodes[1].Host)
-
-	assert.Equal(t, "roundrobin", ctx.Upstreams[1].Type)
-	assert.Equal(t, "http", ctx.Upstreams[1].Scheme)
-	assert.Len(t, ctx.Upstreams[1].Nodes, 2)
-	assert.Equal(t, 9443, ctx.Upstreams[1].Nodes[0].Port)
-	assert.Equal(t, "192.168.1.1", ctx.Upstreams[1].Nodes[0].Host)
-	assert.Equal(t, 9443, ctx.Upstreams[1].Nodes[1].Port)
-	assert.Equal(t, "192.168.1.2", ctx.Upstreams[1].Nodes[1].Host)
-}
-
-func TestTranslateIngressExtensionsV1beta1(t *testing.T) {
-	prefix := extensionsv1beta1.PathTypePrefix
-	// no backend.
-	ing := &extensionsv1beta1.Ingress{
-		ObjectMeta: metav1.ObjectMeta{
-			Name:      "test",
-			Namespace: "default",
-			Annotations: map[string]string{
-				"k8s.apisix.apache.org/use-regex":                                  "true",
-				path.Join(annotations.AnnotationsPrefix, "enable-cors"):            "true",
-				path.Join(annotations.AnnotationsPrefix, "allowlist-source-range"): "127.0.0.1",
-				path.Join(annotations.AnnotationsPrefix, "enable-cors222"):         "true",
-				path.Join(annotations.AnnotationsPrefix, "plugin-config-name"):     "echo-and-cors-apc",
-			},
-		},
-		Spec: extensionsv1beta1.IngressSpec{
-			Rules: []extensionsv1beta1.IngressRule{
-				{
-					Host: "apisix.apache.org",
-					IngressRuleValue: extensionsv1beta1.IngressRuleValue{
-						HTTP: &extensionsv1beta1.HTTPIngressRuleValue{
-							Paths: []extensionsv1beta1.HTTPIngressPath{
-								{
-									Path:     "/foo",
-									PathType: &prefix,
-									Backend: extensionsv1beta1.IngressBackend{
-										ServiceName: "test-service",
-										ServicePort: intstr.IntOrString{
-											Type:   intstr.String,
-											StrVal: "port1",
-										},
-									},
-								},
-								{
-									Path: "/bar",
-									Backend: extensionsv1beta1.IngressBackend{
-										ServiceName: "test-service",
-										ServicePort: intstr.IntOrString{
-											Type:   intstr.Int,
-											IntVal: 443,
-										},
-									},
-								},
-							},
-						},
-					},
-				},
-			},
-		},
-	}
-	client := fake.NewSimpleClientset()
-	informersFactory := informers.NewSharedInformerFactory(client, 0)
-	svcInformer := informersFactory.Core().V1().Services().Informer()
-	svcLister := informersFactory.Core().V1().Services().Lister()
-	epLister, epInformer := kube.NewEndpointListerAndInformer(informersFactory, false)
-	apisixClient := fakeapisix.NewSimpleClientset()
-	apisixInformersFactory := apisixinformers.NewSharedInformerFactory(apisixClient, 0)
-	processCh := make(chan struct{})
-	svcInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
-		AddFunc: func(obj interface{}) {
-			processCh <- struct{}{}
-		},
-	})
-	epInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
-		AddFunc: func(obj interface{}) {
-			processCh <- struct{}{}
-		},
-	})
-
-	stopCh := make(chan struct{})
-	defer close(stopCh)
-	go svcInformer.Run(stopCh)
-	go epInformer.Run(stopCh)
-	cache.WaitForCacheSync(stopCh, svcInformer.HasSynced)
-
-	_, err := client.CoreV1().Services("default").Create(context.Background(), _testSvc, metav1.CreateOptions{})
-	assert.Nil(t, err)
-	_, err = client.CoreV1().Endpoints("default").Create(context.Background(), _testEp, metav1.CreateOptions{})
-	assert.Nil(t, err)
-
-	tr := &translator{
-		TranslatorOptions: &TranslatorOptions{
-			ServiceLister:  svcLister,
-			EndpointLister: epLister,
-			ApisixUpstreamLister: kube.NewApisixUpstreamLister(
-				apisixInformersFactory.Apisix().V2beta3().ApisixUpstreams().Lister(),
-				apisixInformersFactory.Apisix().V2().ApisixUpstreams().Lister(),
-			),
-			APIVersion: config.DefaultAPIVersion,
-		},
-	}
-
-	<-processCh
-	<-processCh
-	ctx, err := tr.translateIngressExtensionsV1beta1(ing, false)
-	annoExtractor := annotations.NewExtractor(ing.Annotations)
-	pluginConfigName := annoExtractor.GetStringAnnotation(path.Join(annotations.AnnotationsPrefix, "plugin-config-name"))
-
-	assert.Nil(t, err)
-	assert.Len(t, ctx.Routes, 2)
-	assert.Len(t, ctx.Upstreams, 2)
-
-	assert.Equal(t, []string{"/foo", "/foo/*"}, ctx.Routes[0].Uris)
-	assert.Equal(t, ctx.Upstreams[0].ID, ctx.Routes[0].UpstreamId)
-	assert.Equal(t, "apisix.apache.org", ctx.Routes[0].Host)
-	assert.Len(t, ctx.Routes[0].Plugins, 2)
-	assert.Equal(t, ctx.Routes[0].PluginConfigId, id.GenID(apisixv1.ComposePluginConfigName(ing.Namespace, pluginConfigName)))
-
-	assert.Equal(t, []string{"/bar"}, ctx.Routes[1].Uris)
-	assert.Equal(t, ctx.Upstreams[1].ID, ctx.Routes[1].UpstreamId)
-	assert.Equal(t, "apisix.apache.org", ctx.Routes[1].Host)
-	assert.Len(t, ctx.Routes[1].Plugins, 2)
-	assert.Equal(t, ctx.Routes[1].PluginConfigId, id.GenID(apisixv1.ComposePluginConfigName(ing.Namespace, pluginConfigName)))
-
-	assert.Equal(t, "roundrobin", ctx.Upstreams[0].Type)
-	assert.Equal(t, "http", ctx.Upstreams[0].Scheme)
-	assert.Len(t, ctx.Upstreams[0].Nodes, 2)
-	assert.Equal(t, 9080, ctx.Upstreams[0].Nodes[0].Port)
-	assert.Equal(t, "192.168.1.1", ctx.Upstreams[0].Nodes[0].Host)
-	assert.Equal(t, 9080, ctx.Upstreams[0].Nodes[1].Port)
-	assert.Equal(t, "192.168.1.2", ctx.Upstreams[0].Nodes[1].Host)
-
-	assert.Equal(t, "roundrobin", ctx.Upstreams[1].Type)
-	assert.Equal(t, "http", ctx.Upstreams[1].Scheme)
-	assert.Len(t, ctx.Upstreams[1].Nodes, 2)
-	assert.Equal(t, 9443, ctx.Upstreams[1].Nodes[0].Port)
-	assert.Equal(t, "192.168.1.1", ctx.Upstreams[1].Nodes[0].Host)
-	assert.Equal(t, 9443, ctx.Upstreams[1].Nodes[1].Port)
-	assert.Equal(t, "192.168.1.2", ctx.Upstreams[1].Nodes[1].Host)
-}
-
-func TestTranslateIngressExtensionsV1beta1BackendWithInvalidService(t *testing.T) {
-	prefix := extensionsv1beta1.PathTypePrefix
-	// no backend.
-	ing := &extensionsv1beta1.Ingress{
-		ObjectMeta: metav1.ObjectMeta{
-			Name:      "test",
-			Namespace: "default",
-		},
-		Spec: extensionsv1beta1.IngressSpec{
-			Rules: []extensionsv1beta1.IngressRule{
-				{
-					Host: "apisix.apache.org",
-					IngressRuleValue: extensionsv1beta1.IngressRuleValue{
-						HTTP: &extensionsv1beta1.HTTPIngressRuleValue{
-							Paths: []extensionsv1beta1.HTTPIngressPath{
-								{
-									Path:     "/foo",
-									PathType: &prefix,
-									Backend: extensionsv1beta1.IngressBackend{
-										ServiceName: "test-service",
-										ServicePort: intstr.IntOrString{
-											Type:   intstr.String,
-											StrVal: "undefined-port",
-										},
-									},
-								},
-							},
-						},
-					},
-				},
-			},
-		},
-	}
-	client := fake.NewSimpleClientset()
-	informersFactory := informers.NewSharedInformerFactory(client, 0)
-	svcInformer := informersFactory.Core().V1().Services().Informer()
-	svcLister := informersFactory.Core().V1().Services().Lister()
-	tr := &translator{
-		TranslatorOptions: &TranslatorOptions{
-			ServiceLister: svcLister,
-		},
-	}
-	ctx, err := tr.translateIngressExtensionsV1beta1(ing, false)
-	assert.Nil(t, ctx)
-	assert.NotNil(t, err)
-	assert.Equal(t, "service \"test-service\" not found", err.Error())
-
-	processCh := make(chan struct{})
-	svcInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
-		AddFunc: func(obj interface{}) {
-			processCh <- struct{}{}
-		},
-	})
-
-	stopCh := make(chan struct{})
-	defer close(stopCh)
-	go svcInformer.Run(stopCh)
-	cache.WaitForCacheSync(stopCh, svcInformer.HasSynced)
-
-	_, err = client.CoreV1().Services("default").Create(context.Background(), _testSvc, metav1.CreateOptions{})
-	assert.Nil(t, err)
-	_, err = client.CoreV1().Endpoints("default").Create(context.Background(), _testEp, metav1.CreateOptions{})
-	assert.Nil(t, err)
-
-	<-processCh
-	ctx, err = tr.translateIngressExtensionsV1beta1(ing, false)
-	assert.Nil(t, ctx)
-	assert.Equal(t, &translateError{
-		field:  "service",
-		reason: "port not found",
-	}, err)
-}
-
-func TestTranslateIngressExtensionsV1beta1WithRegex(t *testing.T) {
-	prefix := extensionsv1beta1.PathTypeImplementationSpecific
-	regexPath := "/foo/*/bar"
-	ing := &extensionsv1beta1.Ingress{
-		ObjectMeta: metav1.ObjectMeta{
-			Name:      "test",
-			Namespace: "default",
-			Annotations: map[string]string{
-				"k8s.apisix.apache.org/use-regex": "true",
-			},
-		},
-		Spec: extensionsv1beta1.IngressSpec{
-			Rules: []extensionsv1beta1.IngressRule{
-				{
-					Host: "apisix.apache.org",
-					IngressRuleValue: extensionsv1beta1.IngressRuleValue{
-						HTTP: &extensionsv1beta1.HTTPIngressRuleValue{
-							Paths: []extensionsv1beta1.HTTPIngressPath{
-								{
-									Path:     regexPath,
-									PathType: &prefix,
-									Backend: extensionsv1beta1.IngressBackend{
-										ServiceName: "test-service",
-										ServicePort: intstr.IntOrString{
-											Type:   intstr.String,
-											StrVal: "port1",
-										},
-									},
-								},
-							},
-						},
-					},
-				},
-			},
-		},
-	}
-	client := fake.NewSimpleClientset()
-	informersFactory := informers.NewSharedInformerFactory(client, 0)
-	svcInformer := informersFactory.Core().V1().Services().Informer()
-	svcLister := informersFactory.Core().V1().Services().Lister()
-	epLister, epInformer := kube.NewEndpointListerAndInformer(informersFactory, false)
-	apisixClient := fakeapisix.NewSimpleClientset()
-	apisixInformersFactory := apisixinformers.NewSharedInformerFactory(apisixClient, 0)
-	processCh := make(chan struct{})
-	svcInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
-		AddFunc: func(obj interface{}) {
-			processCh <- struct{}{}
-		},
-	})
-	epInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
-		AddFunc: func(obj interface{}) {
-			processCh <- struct{}{}
-		},
-	})
-
-	stopCh := make(chan struct{})
-	defer close(stopCh)
-	go svcInformer.Run(stopCh)
-	go epInformer.Run(stopCh)
-	cache.WaitForCacheSync(stopCh, svcInformer.HasSynced)
-
-	_, err := client.CoreV1().Services("default").Create(context.Background(), _testSvc, metav1.CreateOptions{})
-	assert.Nil(t, err)
-	_, err = client.CoreV1().Endpoints("default").Create(context.Background(), _testEp, metav1.CreateOptions{})
-	assert.Nil(t, err)
-
-	tr := &translator{
-		TranslatorOptions: &TranslatorOptions{
-			ServiceLister:  svcLister,
-			EndpointLister: epLister,
-			ApisixUpstreamLister: kube.NewApisixUpstreamLister(
-				apisixInformersFactory.Apisix().V2beta3().ApisixUpstreams().Lister(),
-				apisixInformersFactory.Apisix().V2().ApisixUpstreams().Lister(),
-			),
-			APIVersion: config.DefaultAPIVersion,
-		},
-	}
-
-	<-processCh
-	<-processCh
-	ctx, err := tr.translateIngressExtensionsV1beta1(ing, false)
-	assert.Nil(t, err)
-	assert.Len(t, ctx.Routes, 1)
-	assert.Len(t, ctx.Upstreams, 1)
-	// the number of the PluginConfigs should be zero, cause there no available Annotations matched te rule
-	assert.Len(t, ctx.PluginConfigs, 0)
-	routeVars, err := tr.translateRouteMatchExprs([]configv2.ApisixRouteHTTPMatchExpr{{
-		Subject: configv2.ApisixRouteHTTPMatchExprSubject{
-			Scope: apisixconst.ScopePath,
-		},
-		Op:    apisixconst.OpRegexMatch,
-		Value: &regexPath,
-	}})
-	assert.Nil(t, err)
-
-	var expectedVars v1.Vars = routeVars
-
-	assert.Equal(t, []string{"/*"}, ctx.Routes[0].Uris)
-	assert.Equal(t, expectedVars, ctx.Routes[0].Vars)
-}
-
-func TestTranslateIngressV1WithWebsocket(t *testing.T) {
-	prefix := networkingv1.PathTypeImplementationSpecific
-	regexPath := "/foo/*/bar"
-	ing := &networkingv1.Ingress{
-		ObjectMeta: metav1.ObjectMeta{
-			Name:      "test",
-			Namespace: "default",
-			Annotations: map[string]string{
-				"k8s.apisix.apache.org/enable-websocket": "true",
-			},
-		},
-		Spec: networkingv1.IngressSpec{
-			Rules: []networkingv1.IngressRule{
-				{
-					Host: "apisix.apache.org",
-					IngressRuleValue: networkingv1.IngressRuleValue{
-						HTTP: &networkingv1.HTTPIngressRuleValue{
-							Paths: []networkingv1.HTTPIngressPath{
-								{
-									Path:     regexPath,
-									PathType: &prefix,
-									Backend: networkingv1.IngressBackend{
-										Service: &networkingv1.IngressServiceBackend{
-											Name: "test-service",
-											Port: networkingv1.ServiceBackendPort{
-												Name: "port1",
-											},
-										},
-									},
-								},
-							},
-						},
-					},
-				},
-			},
-		},
-	}
-	client := fake.NewSimpleClientset()
-	informersFactory := informers.NewSharedInformerFactory(client, 0)
-	svcInformer := informersFactory.Core().V1().Services().Informer()
-	svcLister := informersFactory.Core().V1().Services().Lister()
-	epLister, epInformer := kube.NewEndpointListerAndInformer(informersFactory, false)
-	apisixClient := fakeapisix.NewSimpleClientset()
-	apisixInformersFactory := apisixinformers.NewSharedInformerFactory(apisixClient, 0)
-	processCh := make(chan struct{})
-	svcInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
-		AddFunc: func(obj interface{}) {
-			processCh <- struct{}{}
-		},
-	})
-	epInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
-		AddFunc: func(obj interface{}) {
-			processCh <- struct{}{}
-		},
-	})
-
-	stopCh := make(chan struct{})
-	defer close(stopCh)
-	go svcInformer.Run(stopCh)
-	go epInformer.Run(stopCh)
-	cache.WaitForCacheSync(stopCh, svcInformer.HasSynced)
-
-	_, err := client.CoreV1().Services("default").Create(context.Background(), _testSvc, metav1.CreateOptions{})
-	assert.Nil(t, err)
-	_, err = client.CoreV1().Endpoints("default").Create(context.Background(), _testEp, metav1.CreateOptions{})
-	assert.Nil(t, err)
-
-	tr := &translator{
-		TranslatorOptions: &TranslatorOptions{
-			ServiceLister:  svcLister,
-			EndpointLister: epLister,
-			ApisixUpstreamLister: kube.NewApisixUpstreamLister(
-				apisixInformersFactory.Apisix().V2beta3().ApisixUpstreams().Lister(),
-				apisixInformersFactory.Apisix().V2().ApisixUpstreams().Lister(),
-			),
-			APIVersion: config.DefaultAPIVersion,
-		},
-	}
-
-	<-processCh
-	<-processCh
-	ctx, err := tr.translateIngressV1(ing, false)
-	assert.Nil(t, err)
-	assert.Len(t, ctx.Routes, 1)
-	assert.Len(t, ctx.Upstreams, 1)
-	// the number of the PluginConfigs should be zero, cause there no available Annotations matched te rule
-	assert.Len(t, ctx.PluginConfigs, 0)
-
-	assert.Equal(t, true, ctx.Routes[0].EnableWebsocket)
-}
-
-func TestTranslateIngressV1beta1WithWebsocket(t *testing.T) {
-	prefix := networkingv1beta1.PathTypeImplementationSpecific
-	// no backend.
-	regexPath := "/foo/*/bar"
-	ing := &networkingv1beta1.Ingress{
-		ObjectMeta: metav1.ObjectMeta{
-			Name:      "test",
-			Namespace: "default",
-			Annotations: map[string]string{
-				"k8s.apisix.apache.org/enable-websocket": "true",
-			},
-		},
-		Spec: networkingv1beta1.IngressSpec{
-			Rules: []networkingv1beta1.IngressRule{
-				{
-					Host: "apisix.apache.org",
-					IngressRuleValue: networkingv1beta1.IngressRuleValue{
-						HTTP: &networkingv1beta1.HTTPIngressRuleValue{
-							Paths: []networkingv1beta1.HTTPIngressPath{
-								{
-									Path:     regexPath,
-									PathType: &prefix,
-									Backend: networkingv1beta1.IngressBackend{
-										ServiceName: "test-service",
-										ServicePort: intstr.IntOrString{
-											Type:   intstr.String,
-											StrVal: "port1",
-										},
-									},
-								},
-							},
-						},
-					},
-				},
-			},
-		},
-	}
-	client := fake.NewSimpleClientset()
-	informersFactory := informers.NewSharedInformerFactory(client, 0)
-	svcInformer := informersFactory.Core().V1().Services().Informer()
-	svcLister := informersFactory.Core().V1().Services().Lister()
-	epLister, epInformer := kube.NewEndpointListerAndInformer(informersFactory, false)
-	apisixClient := fakeapisix.NewSimpleClientset()
-	apisixInformersFactory := apisixinformers.NewSharedInformerFactory(apisixClient, 0)
-	processCh := make(chan struct{})
-	svcInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
-		AddFunc: func(obj interface{}) {
-			processCh <- struct{}{}
-		},
-	})
-	epInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
-		AddFunc: func(obj interface{}) {
-			processCh <- struct{}{}
-		},
-	})
-
-	stopCh := make(chan struct{})
-	defer close(stopCh)
-	go svcInformer.Run(stopCh)
-	go epInformer.Run(stopCh)
-	cache.WaitForCacheSync(stopCh, svcInformer.HasSynced)
-
-	_, err := client.CoreV1().Services("default").Create(context.Background(), _testSvc, metav1.CreateOptions{})
-	assert.Nil(t, err)
-	_, err = client.CoreV1().Endpoints("default").Create(context.Background(), _testEp, metav1.CreateOptions{})
-	assert.Nil(t, err)
-
-	tr := &translator{
-		TranslatorOptions: &TranslatorOptions{
-			ServiceLister:  svcLister,
-			EndpointLister: epLister,
-			ApisixUpstreamLister: kube.NewApisixUpstreamLister(
-				apisixInformersFactory.Apisix().V2beta3().ApisixUpstreams().Lister(),
-				apisixInformersFactory.Apisix().V2().ApisixUpstreams().Lister(),
-			),
-			APIVersion: config.DefaultAPIVersion,
-		},
-	}
-
-	<-processCh
-	<-processCh
-	ctx, err := tr.translateIngressV1beta1(ing, false)
-	assert.Nil(t, err)
-	assert.Len(t, ctx.Routes, 1)
-	assert.Len(t, ctx.Upstreams, 1)
-	// the number of the PluginConfigs should be zero, cause there no available Annotations matched te rule
-	assert.Len(t, ctx.PluginConfigs, 0)
-
-	assert.Nil(t, err)
-	assert.Equal(t, true, ctx.Routes[0].EnableWebsocket)
-}
-
-func TestTranslateIngressExtensionsV1beta1WithWebsocket(t *testing.T) {
-	prefix := extensionsv1beta1.PathTypeImplementationSpecific
-	regexPath := "/foo/*/bar"
-	ing := &extensionsv1beta1.Ingress{
-		ObjectMeta: metav1.ObjectMeta{
-			Name:      "test",
-			Namespace: "default",
-			Annotations: map[string]string{
-				"k8s.apisix.apache.org/enable-websocket": "true",
-			},
-		},
-		Spec: extensionsv1beta1.IngressSpec{
-			Rules: []extensionsv1beta1.IngressRule{
-				{
-					Host: "apisix.apache.org",
-					IngressRuleValue: extensionsv1beta1.IngressRuleValue{
-						HTTP: &extensionsv1beta1.HTTPIngressRuleValue{
-							Paths: []extensionsv1beta1.HTTPIngressPath{
-								{
-									Path:     regexPath,
-									PathType: &prefix,
-									Backend: extensionsv1beta1.IngressBackend{
-										ServiceName: "test-service",
-										ServicePort: intstr.IntOrString{
-											Type:   intstr.String,
-											StrVal: "port1",
-										},
-									},
-								},
-							},
-						},
-					},
-				},
-			},
-		},
-	}
-	client := fake.NewSimpleClientset()
-	informersFactory := informers.NewSharedInformerFactory(client, 0)
-	svcInformer := informersFactory.Core().V1().Services().Informer()
-	svcLister := informersFactory.Core().V1().Services().Lister()
-	epLister, epInformer := kube.NewEndpointListerAndInformer(informersFactory, false)
-	apisixClient := fakeapisix.NewSimpleClientset()
-	apisixInformersFactory := apisixinformers.NewSharedInformerFactory(apisixClient, 0)
-	processCh := make(chan struct{})
-	svcInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
-		AddFunc: func(obj interface{}) {
-			processCh <- struct{}{}
-		},
-	})
-	epInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
-		AddFunc: func(obj interface{}) {
-			processCh <- struct{}{}
-		},
-	})
-
-	stopCh := make(chan struct{})
-	defer close(stopCh)
-	go svcInformer.Run(stopCh)
-	go epInformer.Run(stopCh)
-	cache.WaitForCacheSync(stopCh, svcInformer.HasSynced)
-
-	_, err := client.CoreV1().Services("default").Create(context.Background(), _testSvc, metav1.CreateOptions{})
-	assert.Nil(t, err)
-	_, err = client.CoreV1().Endpoints("default").Create(context.Background(), _testEp, metav1.CreateOptions{})
-	assert.Nil(t, err)
-
-	tr := &translator{
-		TranslatorOptions: &TranslatorOptions{
-			ServiceLister:  svcLister,
-			EndpointLister: epLister,
-			ApisixUpstreamLister: kube.NewApisixUpstreamLister(
-				apisixInformersFactory.Apisix().V2beta3().ApisixUpstreams().Lister(),
-				apisixInformersFactory.Apisix().V2().ApisixUpstreams().Lister(),
-			),
-			APIVersion: config.DefaultAPIVersion,
-		},
-	}
-
-	<-processCh
-	<-processCh
-	ctx, err := tr.translateIngressExtensionsV1beta1(ing, false)
-	assert.Nil(t, err)
-	assert.Len(t, ctx.Routes, 1)
-	assert.Len(t, ctx.Upstreams, 1)
-	// the number of the PluginConfigs should be zero, cause there no available Annotations matched te rule
-	assert.Len(t, ctx.PluginConfigs, 0)
-
-	assert.Equal(t, true, ctx.Routes[0].EnableWebsocket)
-}
diff --git a/pkg/kube/translation/translator.go b/pkg/kube/translation/translator.go
deleted file mode 100644
index 28f0afe8..00000000
--- a/pkg/kube/translation/translator.go
+++ /dev/null
@@ -1,426 +0,0 @@
-// Licensed to the Apache Software Foundation (ASF) under one or more
-// contributor license agreements.  See the NOTICE file distributed with
-// this work for additional information regarding copyright ownership.
-// The ASF licenses this file to You under the Apache License, Version 2.0
-// (the "License"); you may not use this file except in compliance with
-// the License.  You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-package translation
-
-import (
-	"fmt"
-
-	"go.uber.org/zap"
-	corev1 "k8s.io/api/core/v1"
-	k8serrors "k8s.io/apimachinery/pkg/api/errors"
-	listerscorev1 "k8s.io/client-go/listers/core/v1"
-
-	"github.com/apache/apisix-ingress-controller/pkg/apisix"
-	config "github.com/apache/apisix-ingress-controller/pkg/config"
-	"github.com/apache/apisix-ingress-controller/pkg/kube"
-	configv2 "github.com/apache/apisix-ingress-controller/pkg/kube/apisix/apis/config/v2"
-	configv2beta2 "github.com/apache/apisix-ingress-controller/pkg/kube/apisix/apis/config/v2beta2"
-	configv2beta3 "github.com/apache/apisix-ingress-controller/pkg/kube/apisix/apis/config/v2beta3"
-	"github.com/apache/apisix-ingress-controller/pkg/log"
-	"github.com/apache/apisix-ingress-controller/pkg/types"
-	apisixv1 "github.com/apache/apisix-ingress-controller/pkg/types/apisix/v1"
-)
-
-const (
-	_defaultWeight = 100
-)
-
-type translateError struct {
-	field  string
-	reason string
-}
-
-func (te *translateError) Error() string {
-	return fmt.Sprintf("%s: %s", te.field, te.reason)
-}
-
-// Translator translates Apisix* CRD resources to the description in APISIX.
-type Translator interface {
-	// TranslateUpstreamNodes translate Endpoints resources to APISIX Upstream nodes
-	// according to the give port. Extra labels can be passed to filter the ultimate
-	// upstream nodes.
-	TranslateUpstreamNodes(kube.Endpoint, int32, types.Labels) (apisixv1.UpstreamNodes, error)
-	// TranslateUpstreamConfigV2beta3 translates ApisixUpstreamConfig (part of ApisixUpstream)
-	// to APISIX Upstream, it doesn't fill the the Upstream metadata and nodes.
-	TranslateUpstreamConfigV2beta3(*configv2beta3.ApisixUpstreamConfig) (*apisixv1.Upstream, error)
-	// TranslateUpstreamConfigV2 translates ApisixUpstreamConfig (part of ApisixUpstream)
-	// to APISIX Upstream, it doesn't fill the the Upstream metadata and nodes.
-	TranslateUpstreamConfigV2(*configv2.ApisixUpstreamConfig) (*apisixv1.Upstream, error)
-	// TranslateUpstream composes an upstream according to the
-	// given namespace, name (searching Service/Endpoints) and port (filtering Endpoints).
-	// The returned Upstream doesn't have metadata info.
-	// It doesn't assign any metadata fields, so it's caller's responsibility to decide
-	// the metadata.
-	// Note the subset is used to filter the ultimate node list, only pods whose labels
-	// matching the subset labels (defined in ApisixUpstream) will be selected.
-	// When the subset is not found, the node list will be empty. When the subset is empty,
-	// all pods IP will be filled.
-	TranslateUpstream(string, string, string, int32) (*apisixv1.Upstream, error)
-	// TranslateIngress composes a couple of APISIX Routes and upstreams according
-	// to the given Ingress resource.
-	// For old objects, you cannot use TranslateIngress to build. Because it needs to parse the latest service, which will cause data inconsistency.
-	TranslateIngress(kube.Ingress, ...bool) (*TranslateContext, error)
-	// TranslateOldIngress get route objects from cache
-	// Build upstream and plugin_config through route
-	TranslateOldIngress(kube.Ingress) (*TranslateContext, error)
-	// TranslateRouteV2beta2 translates the configv2beta2.ApisixRoute object into several Route,
-	// and Upstream resources.
-	TranslateRouteV2beta2(*configv2beta2.ApisixRoute) (*TranslateContext, error)
-	// TranslateRouteV2beta2NotStrictly translates the configv2beta2.ApisixRoute object into several Route,
-	// and Upstream  resources not strictly, only used for delete event.
-	TranslateRouteV2beta2NotStrictly(*configv2beta2.ApisixRoute) (*TranslateContext, error)
-	// TranslateRouteV2beta3 translates the configv2beta3.ApisixRoute object into several Route,
-	// Upstream and PluginConfig resources.
-	TranslateRouteV2beta3(*configv2beta3.ApisixRoute) (*TranslateContext, error)
-	// TranslateRouteV2beta3NotStrictly translates the configv2beta3.ApisixRoute object into several Route,
-	// Upstream and PluginConfig resources not strictly, only used for delete event.
-	TranslateRouteV2beta3NotStrictly(*configv2beta3.ApisixRoute) (*TranslateContext, error)
-	// TranslateRouteV2 translates the configv2.ApisixRoute object into several Route,
-	// Upstream and PluginConfig resources.
-	TranslateRouteV2(*configv2.ApisixRoute) (*TranslateContext, error)
-	// TranslateRouteV2NotStrictly translates the configv2.ApisixRoute object into several Route,
-	// Upstream and PluginConfig resources not strictly, only used for delete event.
-	TranslateRouteV2NotStrictly(*configv2.ApisixRoute) (*TranslateContext, error)
-	// TranslateOldRoute get route and stream_route objects from cache
-	// Build upstream and plugin_config through route and stream_route
-	TranslateOldRoute(kube.ApisixRoute) (*TranslateContext, error)
-	// TranslateSSLV2Beta3 translates the configv2beta3.ApisixTls object into the APISIX SSL resource.
-	TranslateSSLV2Beta3(*configv2beta3.ApisixTls) (*apisixv1.Ssl, error)
-	// TranslateSSLV2 translates the configv2.ApisixTls object into the APISIX SSL resource.
-	TranslateSSLV2(*configv2.ApisixTls) (*apisixv1.Ssl, error)
-	// TranslateClusterConfig translates the configv2beta3.ApisixClusterConfig object into the APISIX
-	// Global Rule resource.
-	TranslateClusterConfigV2beta3(*configv2beta3.ApisixClusterConfig) (*apisixv1.GlobalRule, error)
-	// TranslateClusterConfigV2 translates the configv2.ApisixClusterConfig object into the APISIX
-	// Global Rule resource.
-	TranslateClusterConfigV2(*configv2.ApisixClusterConfig) (*apisixv1.GlobalRule, error)
-	// TranslateApisixConsumer translates the configv2beta3.APisixConsumer object into the APISIX Consumer
-	// resource.
-	TranslateApisixConsumerV2beta3(*configv2beta3.ApisixConsumer) (*apisixv1.Consumer, error)
-	// TranslateApisixConsumerV2 translates the configv2beta3.APisixConsumer object into the APISIX Consumer
-	// resource.
-	TranslateApisixConsumerV2(ac *configv2.ApisixConsumer) (*apisixv1.Consumer, error)
-	// TranslatePluginConfigV2beta3 translates the configv2.ApisixPluginConfig object into several PluginConfig
-	// resources.
-	TranslatePluginConfigV2beta3(*configv2beta3.ApisixPluginConfig) (*TranslateContext, error)
-	// TranslatePluginConfigV2beta3NotStrictly translates the configv2beta3.ApisixPluginConfig object into several PluginConfig
-	// resources not strictly, only used for delete event.
-	TranslatePluginConfigV2beta3NotStrictly(*configv2beta3.ApisixPluginConfig) (*TranslateContext, error)
-	// TranslatePluginConfigV2 translates the configv2.ApisixPluginConfig object into several PluginConfig
-	// resources.
-	TranslatePluginConfigV2(*configv2.ApisixPluginConfig) (*TranslateContext, error)
-	// TranslatePluginConfigV2NotStrictly translates the configv2.ApisixPluginConfig object into several PluginConfig
-	// resources not strictly, only used for delete event.
-	TranslatePluginConfigV2NotStrictly(*configv2.ApisixPluginConfig) (*TranslateContext, error)
-	// ExtractKeyPair extracts certificate and private key pair from secret
-	// Supports APISIX style ("cert" and "key") and Kube style ("tls.crt" and "tls.key)
-	ExtractKeyPair(s *corev1.Secret, hasPrivateKey bool) ([]byte, []byte, error)
-}
-
-// TranslatorOptions contains options to help Translator
-// work well.
-type TranslatorOptions struct {
-	PodCache             types.PodCache
-	PodLister            listerscorev1.PodLister
-	EndpointLister       kube.EndpointLister
-	ServiceLister        listerscorev1.ServiceLister
-	ApisixUpstreamLister kube.ApisixUpstreamLister
-	SecretLister         listerscorev1.SecretLister
-	UseEndpointSlices    bool
-	APIVersion           string
-	Apisix               apisix.APISIX
-	ClusterName          string
-}
-
-type translator struct {
-	*TranslatorOptions
-}
-
-// NewTranslator initializes a APISIX CRD resources Translator.
-func NewTranslator(opts *TranslatorOptions) Translator {
-	return &translator{
-		TranslatorOptions: opts,
-	}
-}
-
-func (t *translator) TranslateUpstreamConfigV2beta3(au *configv2beta3.ApisixUpstreamConfig) (*apisixv1.Upstream, error) {
-	ups := apisixv1.NewDefaultUpstream()
-	if err := t.translateUpstreamScheme(au.Scheme, ups); err != nil {
-		return nil, err
-	}
-	if err := t.translateUpstreamLoadBalancerV2beta3(au.LoadBalancer, ups); err != nil {
-		return nil, err
-	}
-	if err := t.translateUpstreamHealthCheckV2beta3(au.HealthCheck, ups); err != nil {
-		return nil, err
-	}
-	if err := t.translateUpstreamRetriesAndTimeoutV2beta3(au.Retries, au.Timeout, ups); err != nil {
-		return nil, err
-	}
-	if err := t.translateClientTLSV2beta3(au.TLSSecret, ups); err != nil {
-		return nil, err
-	}
-	return ups, nil
-}
-
-func (t *translator) TranslateUpstreamConfigV2(au *configv2.ApisixUpstreamConfig) (*apisixv1.Upstream, error) {
-	ups := apisixv1.NewDefaultUpstream()
-	if err := t.translateUpstreamScheme(au.Scheme, ups); err != nil {
-		return nil, err
-	}
-	if err := t.translateUpstreamLoadBalancerV2(au.LoadBalancer, ups); err != nil {
-		return nil, err
-	}
-	if err := t.translateUpstreamHealthCheckV2(au.HealthCheck, ups); err != nil {
-		return nil, err
-	}
-	if err := t.translateUpstreamRetriesAndTimeoutV2(au.Retries, au.Timeout, ups); err != nil {
-		return nil, err
-	}
-	if err := t.translateClientTLSV2(au.TLSSecret, ups); err != nil {
-		return nil, err
-	}
-	return ups, nil
-}
-
-func (t *translator) TranslateUpstream(namespace, name, subset string, port int32) (*apisixv1.Upstream, error) {
-	endpoint, err := t.EndpointLister.GetEndpoint(namespace, name)
-	if err != nil {
-		return nil, &translateError{
-			field:  "endpoints",
-			reason: err.Error(),
-		}
-	}
-
-	switch t.APIVersion {
-	case config.ApisixV2beta3:
-		return t.translateUpstreamV2beta3(&endpoint, namespace, name, subset, port)
-	case config.ApisixV2:
-		return t.translateUpstreamV2(&endpoint, namespace, name, subset, port)
-	default:
-		panic(fmt.Errorf("unsupported ApisixUpstream version %v", t.APIVersion))
-	}
-}
-
-func (t *translator) translateUpstreamV2(ep *kube.Endpoint, namespace, name, subset string, port int32) (*apisixv1.Upstream, error) {
-	au, err := t.ApisixUpstreamLister.V2(namespace, name)
-	ups := apisixv1.NewDefaultUpstream()
-	if err != nil {
-		if k8serrors.IsNotFound(err) {
-			// If subset in ApisixRoute is not empty but the ApisixUpstream resource not found,
-			// just set an empty node list.
-			if subset != "" {
-				ups.Nodes = apisixv1.UpstreamNodes{}
-				return ups, nil
-			}
-		} else {
-			return nil, &translateError{
-				field:  "ApisixUpstream",
-				reason: err.Error(),
-			}
-		}
-	}
-	var labels types.Labels
-	if subset != "" {
-		for _, ss := range au.V2().Spec.Subsets {
-			if ss.Name == subset {
-				labels = ss.Labels
-				break
-			}
-		}
-	}
-	// Filter nodes by subset.
-	nodes, err := t.TranslateUpstreamNodes(*ep, port, labels)
-	if err != nil {
-		return nil, err
-	}
-	if au == nil || au.V2().Spec == nil {
-		ups.Nodes = nodes
-		return ups, nil
-	}
-
-	upsCfg := &au.V2().Spec.ApisixUpstreamConfig
-	for _, pls := range au.V2().Spec.PortLevelSettings {
-		if pls.Port == port {
-			upsCfg = &pls.ApisixUpstreamConfig
-			break
-		}
-	}
-	ups, err = t.TranslateUpstreamConfigV2(upsCfg)
-	if err != nil {
-		return nil, err
-	}
-	ups.Nodes = nodes
-	return ups, nil
-}
-
-func (t *translator) translateUpstreamV2beta3(ep *kube.Endpoint, namespace, name, subset string, port int32) (*apisixv1.Upstream, error) {
-	au, err := t.ApisixUpstreamLister.V2beta3(namespace, name)
-	ups := apisixv1.NewDefaultUpstream()
-	if err != nil {
-		if k8serrors.IsNotFound(err) {
-			// If subset in ApisixRoute is not empty but the ApisixUpstream resource not found,
-			// just set an empty node list.
-			if subset != "" {
-				ups.Nodes = apisixv1.UpstreamNodes{}
-				return ups, nil
-			}
-		} else {
-			return nil, &translateError{
-				field:  "ApisixUpstream",
-				reason: err.Error(),
-			}
-		}
-	}
-	if err != nil {
-		if k8serrors.IsNotFound(err) {
-			// If subset in ApisixRoute is not empty but the ApisixUpstream resource not found,
-			// just set an empty node list.
-			if subset != "" {
-				ups.Nodes = apisixv1.UpstreamNodes{}
-				return ups, nil
-			}
-		} else {
-			return nil, &translateError{
-				field:  "ApisixUpstream",
-				reason: err.Error(),
-			}
-		}
-	}
-	var labels types.Labels
-	if subset != "" {
-		for _, ss := range au.V2beta3().Spec.Subsets {
-			if ss.Name == subset {
-				labels = ss.Labels
-				break
-			}
-		}
-	}
-	// Filter nodes by subset.
-	nodes, err := t.TranslateUpstreamNodes(*ep, port, labels)
-	if err != nil {
-		return nil, err
-	}
-	if au == nil || au.V2beta3().Spec == nil {
-		ups.Nodes = nodes
-		return ups, nil
-	}
-
-	upsCfg := &au.V2beta3().Spec.ApisixUpstreamConfig
-	for _, pls := range au.V2beta3().Spec.PortLevelSettings {
-		if pls.Port == port {
-			upsCfg = &pls.ApisixUpstreamConfig
-			break
-		}
-	}
-	ups, err = t.TranslateUpstreamConfigV2beta3(upsCfg)
-	if err != nil {
-		return nil, err
-	}
-	ups.Nodes = nodes
-	return ups, nil
-}
-
-func (t *translator) TranslateUpstreamNodes(endpoint kube.Endpoint, port int32, labels types.Labels) (apisixv1.UpstreamNodes, error) {
-	namespace, err := endpoint.Namespace()
-	if err != nil {
-		log.Errorw("failed to get endpoint namespace",
-			zap.Error(err),
-			zap.Any("endpoint", endpoint),
-		)
-		return nil, err
-	}
-	svcName := endpoint.ServiceName()
-	svc, err := t.ServiceLister.Services(namespace).Get(svcName)
-	if err != nil {
-		return nil, &translateError{
-			field:  "service",
-			reason: err.Error(),
-		}
-	}
-
-	var svcPort *corev1.ServicePort
-	for _, exposePort := range svc.Spec.Ports {
-		if exposePort.Port == port {
-			svcPort = &exposePort
-			break
-		}
-	}
-	if svcPort == nil {
-		return nil, &translateError{
-			field:  "service.spec.ports",
-			reason: "port not defined",
-		}
-	}
-	// As nodes is not optional, here we create an empty slice,
-	// not a nil slice.
-	nodes := make(apisixv1.UpstreamNodes, 0)
-	for _, hostport := range endpoint.Endpoints(svcPort) {
-		nodes = append(nodes, apisixv1.UpstreamNode{
-			Host: hostport.Host,
-			Port: hostport.Port,
-			// FIXME Custom node weight
-			Weight: _defaultWeight,
-		})
-	}
-	if labels != nil {
-		nodes = t.filterNodesByLabels(nodes, labels, namespace)
-		return nodes, nil
-	}
-	return nodes, nil
-}
-
-func (t *translator) TranslateIngress(ing kube.Ingress, args ...bool) (*TranslateContext, error) {
-	var skipVerify = false
-	if len(args) != 0 {
-		skipVerify = args[0]
-	}
-	switch ing.GroupVersion() {
-	case kube.IngressV1:
-		return t.translateIngressV1(ing.V1(), skipVerify)
-	case kube.IngressV1beta1:
-		return t.translateIngressV1beta1(ing.V1beta1(), skipVerify)
-	case kube.IngressExtensionsV1beta1:
-		return t.translateIngressExtensionsV1beta1(ing.ExtensionsV1beta1(), skipVerify)
-	default:
-		return nil, fmt.Errorf("translator: source group version not supported: %s", ing.GroupVersion())
-	}
-}
-
-func (t *translator) TranslateOldRoute(ar kube.ApisixRoute) (*TranslateContext, error) {
-	switch ar.GroupVersion() {
-	case config.ApisixV2:
-		return t.translateOldRouteV2(ar.V2())
-	case config.ApisixV2beta3:
-		return t.translateOldRouteV2beta3(ar.V2beta3())
-	case config.ApisixV2beta2:
-		return DefaultEmptyTranslateContext(), nil
-	default:
-		return nil, fmt.Errorf("translator: source group version not supported: %s", ar.GroupVersion())
-	}
-}
-
-func (t *translator) TranslateOldIngress(ing kube.Ingress) (*TranslateContext, error) {
-	switch ing.GroupVersion() {
-	case kube.IngressV1:
-		return t.translateOldIngressV1(ing.V1())
-	case kube.IngressV1beta1:
-		return t.translateOldIngressV1beta1(ing.V1beta1())
-	case kube.IngressExtensionsV1beta1:
-		return t.translateOldIngressExtensionsv1beta1(ing.ExtensionsV1beta1())
-	default:
-		return nil, fmt.Errorf("translator: source group version not supported: %s", ing.GroupVersion())
-	}
-}
diff --git a/pkg/kube/translation/util.go b/pkg/kube/translation/util.go
deleted file mode 100644
index 2ce379b8..00000000
--- a/pkg/kube/translation/util.go
+++ /dev/null
@@ -1,263 +0,0 @@
-// Licensed to the Apache Software Foundation (ASF) under one or more
-// contributor license agreements.  See the NOTICE file distributed with
-// this work for additional information regarding copyright ownership.
-// The ASF licenses this file to You under the Apache License, Version 2.0
-// (the "License"); you may not use this file except in compliance with
-// the License.  You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-package translation
-
-import (
-	"errors"
-	"net"
-
-	"go.uber.org/zap"
-	"k8s.io/apimachinery/pkg/util/intstr"
-
-	"github.com/apache/apisix-ingress-controller/pkg/id"
-	configv2 "github.com/apache/apisix-ingress-controller/pkg/kube/apisix/apis/config/v2"
-	configv2beta2 "github.com/apache/apisix-ingress-controller/pkg/kube/apisix/apis/config/v2beta2"
-	configv2beta3 "github.com/apache/apisix-ingress-controller/pkg/kube/apisix/apis/config/v2beta3"
-	"github.com/apache/apisix-ingress-controller/pkg/log"
-	"github.com/apache/apisix-ingress-controller/pkg/types"
-	apisixv1 "github.com/apache/apisix-ingress-controller/pkg/types/apisix/v1"
-)
-
-var (
-	_errInvalidAddress = errors.New("address is neither IP or CIDR")
-)
-
-func (t *translator) getServiceClusterIPAndPort(backend *configv2.ApisixRouteHTTPBackend, ns string) (string, int32, error) {
-	svc, err := t.ServiceLister.Services(ns).Get(backend.ServiceName)
-	if err != nil {
-		return "", 0, err
-	}
-	svcPort := int32(-1)
-	if backend.ResolveGranularity == "service" && svc.Spec.ClusterIP == "" {
-		log.Errorw("ApisixRoute refers to a headless service but want to use the service level resolve granularity",
-			zap.Any("namespace", ns),
-			zap.Any("service", svc),
-		)
-		return "", 0, errors.New("conflict headless service and backend resolve granularity")
-	}
-loop:
-	for _, port := range svc.Spec.Ports {
-		switch backend.ServicePort.Type {
-		case intstr.Int:
-			if backend.ServicePort.IntVal == port.Port {
-				svcPort = port.Port
-				break loop
-			}
-		case intstr.String:
-			if backend.ServicePort.StrVal == port.Name {
-				svcPort = port.Port
-				break loop
-			}
-		}
-	}
-	if svcPort == -1 {
-		log.Errorw("ApisixRoute refers to non-existent Service port",
-			zap.String("namespace", ns),
-			zap.String("port", backend.ServicePort.String()),
-		)
-		return "", 0, err
-	}
-
-	return svc.Spec.ClusterIP, svcPort, nil
-}
-
-// getStreamServiceClusterIPAndPortV2beta2 is for v2beta2 streamRoute
-func (t *translator) getStreamServiceClusterIPAndPortV2beta2(backend configv2beta2.ApisixRouteStreamBackend, ns string) (string, int32, error) {
-	svc, err := t.ServiceLister.Services(ns).Get(backend.ServiceName)
-	if err != nil {
-		return "", 0, err
-	}
-	svcPort := int32(-1)
-	if backend.ResolveGranularity == "service" && svc.Spec.ClusterIP == "" {
-		log.Errorw("ApisixRoute refers to a headless service but want to use the service level resolve granularity",
-			zap.String("ApisixRoute namespace", ns),
-			zap.Any("service", svc),
-		)
-		return "", 0, errors.New("conflict headless service and backend resolve granularity")
-	}
-loop:
-	for _, port := range svc.Spec.Ports {
-		switch backend.ServicePort.Type {
-		case intstr.Int:
-			if backend.ServicePort.IntVal == port.Port {
-				svcPort = port.Port
-				break loop
-			}
-		case intstr.String:
-			if backend.ServicePort.StrVal == port.Name {
-				svcPort = port.Port
-				break loop
-			}
-		}
-	}
-	if svcPort == -1 {
-		log.Errorw("ApisixRoute refers to non-existent Service port",
-			zap.String("ApisixRoute namespace", ns),
-			zap.String("port", backend.ServicePort.String()),
-		)
-		return "", 0, err
-	}
-
-	return svc.Spec.ClusterIP, svcPort, nil
-}
-
-// getStreamServiceClusterIPAndPortV2beta3 is for v2beta3 streamRoute
-func (t *translator) getStreamServiceClusterIPAndPortV2beta3(backend configv2beta3.ApisixRouteStreamBackend, ns string) (string, int32, error) {
-	svc, err := t.ServiceLister.Services(ns).Get(backend.ServiceName)
-	if err != nil {
-		return "", 0, err
-	}
-	svcPort := int32(-1)
-	if backend.ResolveGranularity == "service" && svc.Spec.ClusterIP == "" {
-		log.Errorw("ApisixRoute refers to a headless service but want to use the service level resolve granularity",
-			zap.String("ApisixRoute namespace", ns),
-			zap.Any("service", svc),
-		)
-		return "", 0, errors.New("conflict headless service and backend resolve granularity")
-	}
-loop:
-	for _, port := range svc.Spec.Ports {
-		switch backend.ServicePort.Type {
-		case intstr.Int:
-			if backend.ServicePort.IntVal == port.Port {
-				svcPort = port.Port
-				break loop
-			}
-		case intstr.String:
-			if backend.ServicePort.StrVal == port.Name {
-				svcPort = port.Port
-				break loop
-			}
-		}
-	}
-	if svcPort == -1 {
-		log.Errorw("ApisixRoute refers to non-existent Service port",
-			zap.String("ApisixRoute namespace", ns),
-			zap.String("port", backend.ServicePort.String()),
-		)
-		return "", 0, err
-	}
-
-	return svc.Spec.ClusterIP, svcPort, nil
-}
-
-// getStreamServiceClusterIPAndPortV2 is for v2 streamRoute
-func (t *translator) getStreamServiceClusterIPAndPortV2(backend configv2.ApisixRouteStreamBackend, ns string) (string, int32, error) {
-	svc, err := t.ServiceLister.Services(ns).Get(backend.ServiceName)
-	if err != nil {
-		return "", 0, err
-	}
-	svcPort := int32(-1)
-	if backend.ResolveGranularity == "service" && svc.Spec.ClusterIP == "" {
-		log.Errorw("ApisixRoute refers to a headless service but want to use the service level resolve granularity",
-			zap.String("ApisixRoute namespace", ns),
-			zap.Any("service", svc),
-		)
-		return "", 0, errors.New("conflict headless service and backend resolve granularity")
-	}
-loop:
-	for _, port := range svc.Spec.Ports {
-		switch backend.ServicePort.Type {
-		case intstr.Int:
-			if backend.ServicePort.IntVal == port.Port {
-				svcPort = port.Port
-				break loop
-			}
-		case intstr.String:
-			if backend.ServicePort.StrVal == port.Name {
-				svcPort = port.Port
-				break loop
-			}
-		}
-	}
-	if svcPort == -1 {
-		log.Errorw("ApisixRoute refers to non-existent Service port",
-			zap.String("ApisixRoute namespace", ns),
-			zap.String("port", backend.ServicePort.String()),
-		)
-		return "", 0, err
-	}
-
-	return svc.Spec.ClusterIP, svcPort, nil
-}
-
-// translateUpstreamNotStrictly translates Upstream nodes with a loose way, only generate ID and Name for delete Event.
-func (t *translator) translateUpstreamNotStrictly(namespace, svcName, subset string, svcPort int32) (*apisixv1.Upstream, error) {
-	ups := &apisixv1.Upstream{}
-	ups.Name = apisixv1.ComposeUpstreamName(namespace, svcName, subset, svcPort)
-	ups.ID = id.GenID(ups.Name)
-	return ups, nil
-}
-
-func (t *translator) translateUpstream(namespace, svcName, subset, svcResolveGranularity, svcClusterIP string, svcPort int32) (*apisixv1.Upstream, error) {
-	ups, err := t.TranslateUpstream(namespace, svcName, subset, svcPort)
-	if err != nil {
-		return nil, err
-	}
-	if svcResolveGranularity == "service" {
-		ups.Nodes = apisixv1.UpstreamNodes{
-			{
-				Host:   svcClusterIP,
-				Port:   int(svcPort),
-				Weight: _defaultWeight,
-			},
-		}
-	}
-	ups.Name = apisixv1.ComposeUpstreamName(namespace, svcName, subset, svcPort)
-	ups.ID = id.GenID(ups.Name)
-	return ups, nil
-}
-
-func (t *translator) filterNodesByLabels(nodes apisixv1.UpstreamNodes, labels types.Labels, namespace string) apisixv1.UpstreamNodes {
-	if labels == nil {
-		return nodes
-	}
-
-	filteredNodes := make(apisixv1.UpstreamNodes, 0)
-	for _, node := range nodes {
-		podName, err := t.PodCache.GetNameByIP(node.Host)
-		if err != nil {
-			log.Errorw("failed to find pod name by ip, ignore it",
-				zap.Error(err),
-				zap.String("pod_ip", node.Host),
-			)
-			continue
-		}
-		pod, err := t.PodLister.Pods(namespace).Get(podName)
-		if err != nil {
-			log.Errorw("failed to find pod, ignore it",
-				zap.Error(err),
-				zap.String("pod_name", podName),
-			)
-			continue
-		}
-		if labels.IsSubsetOf(pod.Labels) {
-			filteredNodes = append(filteredNodes, node)
-		}
-	}
-	return filteredNodes
-}
-
-func validateRemoteAddrs(remoteAddrs []string) error {
-	for _, addr := range remoteAddrs {
-		if ip := net.ParseIP(addr); ip == nil {
-			// addr is not an IP address, try to parse it as a CIDR.
-			if _, _, err := net.ParseCIDR(addr); err != nil {
-				return _errInvalidAddress
-			}
-		}
-	}
-	return nil
-}
diff --git a/pkg/ingress/apisix_cluster_config.go b/pkg/providers/apisix/apisix_cluster_config.go
similarity index 63%
rename from pkg/ingress/apisix_cluster_config.go
rename to pkg/providers/apisix/apisix_cluster_config.go
index fa346381..d611cbb9 100644
--- a/pkg/ingress/apisix_cluster_config.go
+++ b/pkg/providers/apisix/apisix_cluster_config.go
@@ -12,7 +12,7 @@
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 // See the License for the specific language governing permissions and
 // limitations under the License.
-package ingress
+package apisix
 
 import (
 	"context"
@@ -22,37 +22,51 @@ import (
 	"go.uber.org/zap"
 	corev1 "k8s.io/api/core/v1"
 	k8serrors "k8s.io/apimachinery/pkg/api/errors"
+	"k8s.io/apimachinery/pkg/api/meta"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/client-go/tools/cache"
 	"k8s.io/client-go/util/workqueue"
 
 	"github.com/apache/apisix-ingress-controller/pkg/apisix"
 	"github.com/apache/apisix-ingress-controller/pkg/config"
 	"github.com/apache/apisix-ingress-controller/pkg/kube"
+	configv2 "github.com/apache/apisix-ingress-controller/pkg/kube/apisix/apis/config/v2"
+	configv2beta3 "github.com/apache/apisix-ingress-controller/pkg/kube/apisix/apis/config/v2beta3"
 	"github.com/apache/apisix-ingress-controller/pkg/log"
+	"github.com/apache/apisix-ingress-controller/pkg/providers/utils"
 	"github.com/apache/apisix-ingress-controller/pkg/types"
 )
 
 type apisixClusterConfigController struct {
-	controller *Controller
-	workqueue  workqueue.RateLimitingInterface
-	workers    int
+	*apisixCommon
+
+	workqueue workqueue.RateLimitingInterface
+	workers   int
+
+	apisixClusterConfigLister   kube.ApisixClusterConfigLister
+	apisixClusterConfigInformer cache.SharedIndexInformer
 }
 
-func (c *Controller) newApisixClusterConfigController() *apisixClusterConfigController {
-	ctl := &apisixClusterConfigController{
-		controller: c,
-		workqueue:  workqueue.NewNamedRateLimitingQueue(workqueue.NewItemFastSlowRateLimiter(time.Second, 60*time.Second, 5), "ApisixClusterConfig"),
-		workers:    1,
+func newApisixClusterConfigController(common *apisixCommon,
+	apisixClusterConfigInformer cache.SharedIndexInformer, apisixClusterConfigLister kube.ApisixClusterConfigLister) *apisixClusterConfigController {
+	c := &apisixClusterConfigController{
+		apisixCommon: common,
+		workqueue:    workqueue.NewNamedRateLimitingQueue(workqueue.NewItemFastSlowRateLimiter(time.Second, 60*time.Second, 5), "ApisixClusterConfig"),
+		workers:      1,
+
+		apisixClusterConfigLister:   apisixClusterConfigLister,
+		apisixClusterConfigInformer: apisixClusterConfigInformer,
 	}
+
 	c.apisixClusterConfigInformer.AddEventHandler(
 		cache.ResourceEventHandlerFuncs{
-			AddFunc:    ctl.onAdd,
-			UpdateFunc: ctl.onUpdate,
-			DeleteFunc: ctl.onDelete,
+			AddFunc:    c.onAdd,
+			UpdateFunc: c.onUpdate,
+			DeleteFunc: c.onDelete,
 		},
 	)
-	return ctl
+	return c
 }
 
 func (c *apisixClusterConfigController) run(ctx context.Context) {
@@ -60,7 +74,7 @@ func (c *apisixClusterConfigController) run(ctx context.Context) {
 	defer log.Info("ApisixClusterConfig controller exited")
 	defer c.workqueue.ShutDown()
 
-	if ok := cache.WaitForCacheSync(ctx.Done(), c.controller.apisixClusterConfigInformer.HasSynced); !ok {
+	if ok := cache.WaitForCacheSync(ctx.Done(), c.apisixClusterConfigInformer.HasSynced); !ok {
 		log.Error("cache sync failed")
 		return
 	}
@@ -94,9 +108,9 @@ func (c *apisixClusterConfigController) sync(ctx context.Context, ev *types.Even
 	var multiVersioned kube.ApisixClusterConfig
 	switch event.GroupVersion {
 	case config.ApisixV2beta3:
-		multiVersioned, err = c.controller.apisixClusterConfigLister.V2beta3(name)
+		multiVersioned, err = c.apisixClusterConfigLister.V2beta3(name)
 	case config.ApisixV2:
-		multiVersioned, err = c.controller.apisixClusterConfigLister.V2(name)
+		multiVersioned, err = c.apisixClusterConfigLister.V2(name)
 	default:
 		return fmt.Errorf("unsupported ApisixClusterConfig group version %s", event.GroupVersion)
 	}
@@ -134,9 +148,9 @@ func (c *apisixClusterConfigController) sync(ctx context.Context, ev *types.Even
 		acc := multiVersioned.V2beta3()
 		// Currently we don't handle multiple cluster, so only process
 		// the default apisix cluster.
-		if acc.Name != c.controller.cfg.APISIX.DefaultClusterName {
+		if acc.Name != c.Config.APISIX.DefaultClusterName {
 			log.Infow("ignore non-default apisix cluster config",
-				zap.String("default_cluster_name", c.controller.cfg.APISIX.DefaultClusterName),
+				zap.String("default_cluster_name", c.Config.APISIX.DefaultClusterName),
 				zap.Any("ApisixClusterConfig", acc),
 			)
 			return nil
@@ -159,27 +173,27 @@ func (c *apisixClusterConfigController) sync(ctx context.Context, ev *types.Even
 			)
 			// TODO we may first call AddCluster.
 			// Since now we already have the default cluster, we just call UpdateCluster.
-			if err := c.controller.apisix.UpdateCluster(ctx, clusterOpts); err != nil {
+			if err := c.APISIX.UpdateCluster(ctx, clusterOpts); err != nil {
 				log.Errorw("failed to update cluster",
 					zap.String("cluster_name", acc.Name),
 					zap.Error(err),
 					zap.Any("opts", clusterOpts),
 				)
-				c.controller.recorderEvent(acc, corev1.EventTypeWarning, _resourceSyncAborted, err)
-				c.controller.recordStatus(acc, _resourceSyncAborted, err, metav1.ConditionFalse, acc.GetGeneration())
+				c.RecordEvent(acc, corev1.EventTypeWarning, utils.ResourceSyncAborted, err)
+				c.recordStatus(acc, utils.ResourceSyncAborted, err, metav1.ConditionFalse, acc.GetGeneration())
 				return err
 			}
 		}
 
-		globalRule, err := c.controller.translator.TranslateClusterConfigV2beta3(acc)
+		globalRule, err := c.translator.TranslateClusterConfigV2beta3(acc)
 		if err != nil {
 			log.Errorw("failed to translate ApisixClusterConfig",
 				zap.Error(err),
 				zap.String("key", key),
 				zap.Any("object", acc),
 			)
-			c.controller.recorderEvent(acc, corev1.EventTypeWarning, _resourceSyncAborted, err)
-			c.controller.recordStatus(acc, _resourceSyncAborted, err, metav1.ConditionFalse, acc.GetGeneration())
+			c.RecordEvent(acc, corev1.EventTypeWarning, utils.ResourceSyncAborted, err)
+			c.recordStatus(acc, utils.ResourceSyncAborted, err, metav1.ConditionFalse, acc.GetGeneration())
 			return err
 		}
 		log.Debugw("translated global_rule",
@@ -188,29 +202,29 @@ func (c *apisixClusterConfigController) sync(ctx context.Context, ev *types.Even
 
 		// TODO multiple cluster support
 		if ev.Type == types.EventAdd {
-			_, err = c.controller.apisix.Cluster(acc.Name).GlobalRule().Create(ctx, globalRule)
+			_, err = c.APISIX.Cluster(acc.Name).GlobalRule().Create(ctx, globalRule)
 		} else {
-			_, err = c.controller.apisix.Cluster(acc.Name).GlobalRule().Update(ctx, globalRule)
+			_, err = c.APISIX.Cluster(acc.Name).GlobalRule().Update(ctx, globalRule)
 		}
 		if err != nil {
 			log.Errorw("failed to reflect global_rule changes to apisix cluster",
 				zap.Any("global_rule", globalRule),
 				zap.Any("cluster", acc.Name),
 			)
-			c.controller.recorderEvent(acc, corev1.EventTypeWarning, _resourceSyncAborted, err)
-			c.controller.recordStatus(acc, _resourceSyncAborted, err, metav1.ConditionFalse, acc.GetGeneration())
+			c.RecordEvent(acc, corev1.EventTypeWarning, utils.ResourceSyncAborted, err)
+			c.recordStatus(acc, utils.ResourceSyncAborted, err, metav1.ConditionFalse, acc.GetGeneration())
 			return err
 		}
-		c.controller.recorderEvent(acc, corev1.EventTypeNormal, _resourceSynced, nil)
-		c.controller.recordStatus(acc, _resourceSynced, nil, metav1.ConditionTrue, acc.GetGeneration())
+		c.RecordEvent(acc, corev1.EventTypeNormal, utils.ResourceSynced, nil)
+		c.recordStatus(acc, utils.ResourceSynced, nil, metav1.ConditionTrue, acc.GetGeneration())
 		return nil
 	case config.ApisixV2:
 		acc := multiVersioned.V2()
 		// Currently we don't handle multiple cluster, so only process
 		// the default apisix cluster.
-		if acc.Name != c.controller.cfg.APISIX.DefaultClusterName {
+		if acc.Name != c.Config.APISIX.DefaultClusterName {
 			log.Infow("ignore non-default apisix cluster config",
-				zap.String("default_cluster_name", c.controller.cfg.APISIX.DefaultClusterName),
+				zap.String("default_cluster_name", c.Config.APISIX.DefaultClusterName),
 				zap.Any("ApisixClusterConfig", acc),
 			)
 			return nil
@@ -233,27 +247,27 @@ func (c *apisixClusterConfigController) sync(ctx context.Context, ev *types.Even
 			)
 			// TODO we may first call AddCluster.
 			// Since now we already have the default cluster, we just call UpdateCluster.
-			if err := c.controller.apisix.UpdateCluster(ctx, clusterOpts); err != nil {
+			if err := c.APISIX.UpdateCluster(ctx, clusterOpts); err != nil {
 				log.Errorw("failed to update cluster",
 					zap.String("cluster_name", acc.Name),
 					zap.Error(err),
 					zap.Any("opts", clusterOpts),
 				)
-				c.controller.recorderEvent(acc, corev1.EventTypeWarning, _resourceSyncAborted, err)
-				c.controller.recordStatus(acc, _resourceSyncAborted, err, metav1.ConditionFalse, acc.GetGeneration())
+				c.RecordEvent(acc, corev1.EventTypeWarning, utils.ResourceSyncAborted, err)
+				c.recordStatus(acc, utils.ResourceSyncAborted, err, metav1.ConditionFalse, acc.GetGeneration())
 				return err
 			}
 		}
 
-		globalRule, err := c.controller.translator.TranslateClusterConfigV2(acc)
+		globalRule, err := c.translator.TranslateClusterConfigV2(acc)
 		if err != nil {
 			log.Errorw("failed to translate ApisixClusterConfig",
 				zap.Error(err),
 				zap.String("key", key),
 				zap.Any("object", acc),
 			)
-			c.controller.recorderEvent(acc, corev1.EventTypeWarning, _resourceSyncAborted, err)
-			c.controller.recordStatus(acc, _resourceSyncAborted, err, metav1.ConditionFalse, acc.GetGeneration())
+			c.RecordEvent(acc, corev1.EventTypeWarning, utils.ResourceSyncAborted, err)
+			c.recordStatus(acc, utils.ResourceSyncAborted, err, metav1.ConditionFalse, acc.GetGeneration())
 			return err
 		}
 		log.Debugw("translated global_rule",
@@ -262,21 +276,21 @@ func (c *apisixClusterConfigController) sync(ctx context.Context, ev *types.Even
 
 		// TODO multiple cluster support
 		if ev.Type == types.EventAdd {
-			_, err = c.controller.apisix.Cluster(acc.Name).GlobalRule().Create(ctx, globalRule)
+			_, err = c.APISIX.Cluster(acc.Name).GlobalRule().Create(ctx, globalRule)
 		} else {
-			_, err = c.controller.apisix.Cluster(acc.Name).GlobalRule().Update(ctx, globalRule)
+			_, err = c.APISIX.Cluster(acc.Name).GlobalRule().Update(ctx, globalRule)
 		}
 		if err != nil {
 			log.Errorw("failed to reflect global_rule changes to apisix cluster",
 				zap.Any("global_rule", globalRule),
 				zap.Any("cluster", acc.Name),
 			)
-			c.controller.recorderEvent(acc, corev1.EventTypeWarning, _resourceSyncAborted, err)
-			c.controller.recordStatus(acc, _resourceSyncAborted, err, metav1.ConditionFalse, acc.GetGeneration())
+			c.RecordEvent(acc, corev1.EventTypeWarning, utils.ResourceSyncAborted, err)
+			c.recordStatus(acc, utils.ResourceSyncAborted, err, metav1.ConditionFalse, acc.GetGeneration())
 			return err
 		}
-		c.controller.recorderEvent(acc, corev1.EventTypeNormal, _resourceSynced, nil)
-		c.controller.recordStatus(acc, _resourceSynced, nil, metav1.ConditionTrue, acc.GetGeneration())
+		c.RecordEvent(acc, corev1.EventTypeNormal, utils.ResourceSynced, nil)
+		c.recordStatus(acc, utils.ResourceSynced, nil, metav1.ConditionTrue, acc.GetGeneration())
 		return nil
 	default:
 		return fmt.Errorf("unsupported ApisixClusterConfig group version %s", event.GroupVersion)
@@ -286,7 +300,7 @@ func (c *apisixClusterConfigController) sync(ctx context.Context, ev *types.Even
 func (c *apisixClusterConfigController) handleSyncErr(obj interface{}, err error) {
 	if err == nil {
 		c.workqueue.Forget(obj)
-		c.controller.MetricsCollector.IncrSyncOperation("clusterConfig", "success")
+		c.MetricsCollector.IncrSyncOperation("clusterConfig", "success")
 		return
 	}
 	event := obj.(*types.Event)
@@ -304,7 +318,7 @@ func (c *apisixClusterConfigController) handleSyncErr(obj interface{}, err error
 	)
 
 	c.workqueue.AddRateLimited(obj)
-	c.controller.MetricsCollector.IncrSyncOperation("clusterConfig", "failure")
+	c.MetricsCollector.IncrSyncOperation("clusterConfig", "failure")
 }
 
 func (c *apisixClusterConfigController) onAdd(obj interface{}) {
@@ -331,7 +345,7 @@ func (c *apisixClusterConfigController) onAdd(obj interface{}) {
 		},
 	})
 
-	c.controller.MetricsCollector.IncrEvents("clusterConfig", "add")
+	c.MetricsCollector.IncrEvents("clusterConfig", "add")
 }
 
 func (c *apisixClusterConfigController) onUpdate(oldObj, newObj interface{}) {
@@ -367,7 +381,7 @@ func (c *apisixClusterConfigController) onUpdate(oldObj, newObj interface{}) {
 		},
 	})
 
-	c.controller.MetricsCollector.IncrEvents("clusterConfig", "update")
+	c.MetricsCollector.IncrEvents("clusterConfig", "update")
 }
 
 func (c *apisixClusterConfigController) onDelete(obj interface{}) {
@@ -401,18 +415,18 @@ func (c *apisixClusterConfigController) onDelete(obj interface{}) {
 		Tombstone: acc,
 	})
 
-	c.controller.MetricsCollector.IncrEvents("clusterConfig", "delete")
+	c.MetricsCollector.IncrEvents("clusterConfig", "delete")
 }
 
 func (c *apisixClusterConfigController) ResourceSync() {
-	objs := c.controller.apisixClusterConfigInformer.GetIndexer().List()
+	objs := c.apisixClusterConfigInformer.GetIndexer().List()
 	for _, obj := range objs {
 		key, err := cache.MetaNamespaceKeyFunc(obj)
 		if err != nil {
 			log.Errorw("ApisixClusterConfig sync failed, found ApisixClusterConfig resource with bad meta namespace key", zap.String("error", err.Error()))
 			continue
 		}
-		if !c.controller.isWatchingNamespace(key) {
+		if !c.namespaceProvider.IsWatchingNamespace(key) {
 			continue
 		}
 		acc, err := kube.NewApisixClusterConfig(obj)
@@ -429,3 +443,62 @@ func (c *apisixClusterConfigController) ResourceSync() {
 		})
 	}
 }
+
+// recordStatus record resources status
+func (c *apisixClusterConfigController) recordStatus(at interface{}, reason string, err error, status metav1.ConditionStatus, generation int64) {
+	// build condition
+	message := utils.CommonSuccessMessage
+	if err != nil {
+		message = err.Error()
+	}
+	condition := metav1.Condition{
+		Type:               utils.ConditionType,
+		Reason:             reason,
+		Status:             status,
+		Message:            message,
+		ObservedGeneration: generation,
+	}
+	apisixClient := c.KubeClient.APISIXClient
+
+	if kubeObj, ok := at.(runtime.Object); ok {
+		at = kubeObj.DeepCopyObject()
+	}
+
+	switch v := at.(type) {
+	case *configv2beta3.ApisixClusterConfig:
+		// set to status
+		if v.Status.Conditions == nil {
+			conditions := make([]metav1.Condition, 0)
+			v.Status.Conditions = conditions
+		}
+		if utils.VerifyGeneration(&v.Status.Conditions, condition) {
+			meta.SetStatusCondition(&v.Status.Conditions, condition)
+			if _, errRecord := apisixClient.ApisixV2beta3().ApisixClusterConfigs().
+				UpdateStatus(context.TODO(), v, metav1.UpdateOptions{}); errRecord != nil {
+				log.Errorw("failed to record status change for ApisixClusterConfig",
+					zap.Error(errRecord),
+					zap.String("name", v.Name),
+				)
+			}
+		}
+	case *configv2.ApisixClusterConfig:
+		// set to status
+		if v.Status.Conditions == nil {
+			conditions := make([]metav1.Condition, 0)
+			v.Status.Conditions = conditions
+		}
+		if utils.VerifyGeneration(&v.Status.Conditions, condition) {
+			meta.SetStatusCondition(&v.Status.Conditions, condition)
+			if _, errRecord := apisixClient.ApisixV2().ApisixClusterConfigs().
+				UpdateStatus(context.TODO(), v, metav1.UpdateOptions{}); errRecord != nil {
+				log.Errorw("failed to record status change for ApisixClusterConfig",
+					zap.Error(errRecord),
+					zap.String("name", v.Name),
+				)
+			}
+		}
+	default:
+		// This should not be executed
+		log.Errorf("unsupported resource record: %s", v)
+	}
+}
diff --git a/pkg/ingress/apisix_consumer.go b/pkg/providers/apisix/apisix_consumer.go
similarity index 61%
rename from pkg/ingress/apisix_consumer.go
rename to pkg/providers/apisix/apisix_consumer.go
index 581239dd..b2f94a19 100644
--- a/pkg/ingress/apisix_consumer.go
+++ b/pkg/providers/apisix/apisix_consumer.go
@@ -12,7 +12,7 @@
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 // See the License for the specific language governing permissions and
 // limitations under the License.
-package ingress
+package apisix
 
 import (
 	"context"
@@ -22,42 +22,56 @@ import (
 	"go.uber.org/zap"
 	corev1 "k8s.io/api/core/v1"
 	k8serrors "k8s.io/apimachinery/pkg/api/errors"
+	"k8s.io/apimachinery/pkg/api/meta"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/client-go/tools/cache"
 	"k8s.io/client-go/util/workqueue"
 
 	"github.com/apache/apisix-ingress-controller/pkg/config"
 	"github.com/apache/apisix-ingress-controller/pkg/kube"
+	configv2 "github.com/apache/apisix-ingress-controller/pkg/kube/apisix/apis/config/v2"
+	configv2beta3 "github.com/apache/apisix-ingress-controller/pkg/kube/apisix/apis/config/v2beta3"
 	"github.com/apache/apisix-ingress-controller/pkg/log"
+	"github.com/apache/apisix-ingress-controller/pkg/providers/utils"
 	"github.com/apache/apisix-ingress-controller/pkg/types"
 )
 
 type apisixConsumerController struct {
-	controller *Controller
-	workqueue  workqueue.RateLimitingInterface
-	workers    int
+	*apisixCommon
+
+	workqueue workqueue.RateLimitingInterface
+	workers   int
+
+	apisixConsumerLister   kube.ApisixConsumerLister
+	apisixConsumerInformer cache.SharedIndexInformer
 }
 
-func (c *Controller) newApisixConsumerController() *apisixConsumerController {
-	ctl := &apisixConsumerController{
-		controller: c,
-		workqueue:  workqueue.NewNamedRateLimitingQueue(workqueue.NewItemFastSlowRateLimiter(1*time.Second, 60*time.Second, 5), "ApisixConsumer"),
-		workers:    1,
+func newApisixConsumerController(common *apisixCommon,
+	apisixConsumerInformer cache.SharedIndexInformer, apisixConsumerLister kube.ApisixConsumerLister) *apisixConsumerController {
+	c := &apisixConsumerController{
+		apisixCommon: common,
+		workqueue:    workqueue.NewNamedRateLimitingQueue(workqueue.NewItemFastSlowRateLimiter(1*time.Second, 60*time.Second, 5), "ApisixConsumer"),
+		workers:      1,
+
+		apisixConsumerLister:   apisixConsumerLister,
+		apisixConsumerInformer: apisixConsumerInformer,
 	}
-	ctl.controller.apisixConsumerInformer.AddEventHandler(
+
+	c.apisixConsumerInformer.AddEventHandler(
 		cache.ResourceEventHandlerFuncs{
-			AddFunc:    ctl.onAdd,
-			UpdateFunc: ctl.onUpdate,
-			DeleteFunc: ctl.onDelete,
+			AddFunc:    c.onAdd,
+			UpdateFunc: c.onUpdate,
+			DeleteFunc: c.onDelete,
 		},
 	)
-	return ctl
+	return c
 }
 
 func (c *apisixConsumerController) run(ctx context.Context) {
 	log.Info("ApisixConsumer controller started")
 	defer log.Info("ApisixConsumer controller exited")
-	if ok := cache.WaitForCacheSync(ctx.Done(), c.controller.apisixConsumerInformer.HasSynced); !ok {
+	if ok := cache.WaitForCacheSync(ctx.Done(), c.apisixConsumerInformer.HasSynced); !ok {
 		log.Error("cache sync failed")
 		return
 	}
@@ -92,9 +106,9 @@ func (c *apisixConsumerController) sync(ctx context.Context, ev *types.Event) er
 	var multiVersioned kube.ApisixConsumer
 	switch event.GroupVersion {
 	case config.ApisixV2beta3:
-		multiVersioned, err = c.controller.apisixConsumerLister.V2beta3(namespace, name)
+		multiVersioned, err = c.apisixConsumerLister.V2beta3(namespace, name)
 	case config.ApisixV2:
-		multiVersioned, err = c.controller.apisixConsumerLister.V2(namespace, name)
+		multiVersioned, err = c.apisixConsumerLister.V2(namespace, name)
 	default:
 		return fmt.Errorf("unsupported ApisixConsumer group version %s", event.GroupVersion)
 	}
@@ -132,14 +146,14 @@ func (c *apisixConsumerController) sync(ctx context.Context, ev *types.Event) er
 	case config.ApisixV2beta3:
 		ac := multiVersioned.V2beta3()
 
-		consumer, err := c.controller.translator.TranslateApisixConsumerV2beta3(ac)
+		consumer, err := c.translator.TranslateApisixConsumerV2beta3(ac)
 		if err != nil {
 			log.Errorw("failed to translate ApisixConsumer",
 				zap.Error(err),
 				zap.Any("ApisixConsumer", ac),
 			)
-			c.controller.recorderEvent(ac, corev1.EventTypeWarning, _resourceSyncAborted, err)
-			c.controller.recordStatus(ac, _resourceSyncAborted, err, metav1.ConditionFalse, ac.GetGeneration())
+			c.RecordEvent(ac, corev1.EventTypeWarning, utils.ResourceSyncAborted, err)
+			c.recordStatus(ac, utils.ResourceSyncAborted, err, metav1.ConditionFalse, ac.GetGeneration())
 			return err
 		}
 		log.Debugw("got consumer object from ApisixConsumer",
@@ -147,28 +161,28 @@ func (c *apisixConsumerController) sync(ctx context.Context, ev *types.Event) er
 			zap.Any("ApisixConsumer", ac),
 		)
 
-		if err := c.controller.syncConsumer(ctx, consumer, ev.Type); err != nil {
+		if err := c.SyncConsumer(ctx, consumer, ev.Type); err != nil {
 			log.Errorw("failed to sync Consumer to APISIX",
 				zap.Error(err),
 				zap.Any("consumer", consumer),
 			)
-			c.controller.recorderEvent(ac, corev1.EventTypeWarning, _resourceSyncAborted, err)
-			c.controller.recordStatus(ac, _resourceSyncAborted, err, metav1.ConditionFalse, ac.GetGeneration())
+			c.RecordEvent(ac, corev1.EventTypeWarning, utils.ResourceSyncAborted, err)
+			c.recordStatus(ac, utils.ResourceSyncAborted, err, metav1.ConditionFalse, ac.GetGeneration())
 			return err
 		}
 
-		c.controller.recorderEvent(ac, corev1.EventTypeNormal, _resourceSynced, nil)
+		c.RecordEvent(ac, corev1.EventTypeNormal, utils.ResourceSynced, nil)
 	case config.ApisixV2:
 		ac := multiVersioned.V2()
 
-		consumer, err := c.controller.translator.TranslateApisixConsumerV2(ac)
+		consumer, err := c.translator.TranslateApisixConsumerV2(ac)
 		if err != nil {
 			log.Errorw("failed to translate ApisixConsumer",
 				zap.Error(err),
 				zap.Any("ApisixConsumer", ac),
 			)
-			c.controller.recorderEvent(ac, corev1.EventTypeWarning, _resourceSyncAborted, err)
-			c.controller.recordStatus(ac, _resourceSyncAborted, err, metav1.ConditionFalse, ac.GetGeneration())
+			c.RecordEvent(ac, corev1.EventTypeWarning, utils.ResourceSyncAborted, err)
+			c.recordStatus(ac, utils.ResourceSyncAborted, err, metav1.ConditionFalse, ac.GetGeneration())
 			return err
 		}
 		log.Debugw("got consumer object from ApisixConsumer",
@@ -176,17 +190,17 @@ func (c *apisixConsumerController) sync(ctx context.Context, ev *types.Event) er
 			zap.Any("ApisixConsumer", ac),
 		)
 
-		if err := c.controller.syncConsumer(ctx, consumer, ev.Type); err != nil {
+		if err := c.SyncConsumer(ctx, consumer, ev.Type); err != nil {
 			log.Errorw("failed to sync Consumer to APISIX",
 				zap.Error(err),
 				zap.Any("consumer", consumer),
 			)
-			c.controller.recorderEvent(ac, corev1.EventTypeWarning, _resourceSyncAborted, err)
-			c.controller.recordStatus(ac, _resourceSyncAborted, err, metav1.ConditionFalse, ac.GetGeneration())
+			c.RecordEvent(ac, corev1.EventTypeWarning, utils.ResourceSyncAborted, err)
+			c.recordStatus(ac, utils.ResourceSyncAborted, err, metav1.ConditionFalse, ac.GetGeneration())
 			return err
 		}
 
-		c.controller.recorderEvent(ac, corev1.EventTypeNormal, _resourceSynced, nil)
+		c.RecordEvent(ac, corev1.EventTypeNormal, utils.ResourceSynced, nil)
 	}
 	return nil
 }
@@ -194,7 +208,7 @@ func (c *apisixConsumerController) sync(ctx context.Context, ev *types.Event) er
 func (c *apisixConsumerController) handleSyncErr(obj interface{}, err error) {
 	if err == nil {
 		c.workqueue.Forget(obj)
-		c.controller.MetricsCollector.IncrSyncOperation("consumer", "success")
+		c.MetricsCollector.IncrSyncOperation("consumer", "success")
 		return
 	}
 	event := obj.(*types.Event)
@@ -211,7 +225,7 @@ func (c *apisixConsumerController) handleSyncErr(obj interface{}, err error) {
 		zap.Error(err),
 	)
 	c.workqueue.AddRateLimited(obj)
-	c.controller.MetricsCollector.IncrSyncOperation("consumer", "failure")
+	c.MetricsCollector.IncrSyncOperation("consumer", "failure")
 }
 
 func (c *apisixConsumerController) onAdd(obj interface{}) {
@@ -225,7 +239,7 @@ func (c *apisixConsumerController) onAdd(obj interface{}) {
 		log.Errorf("found ApisixConsumer resource with bad meta namespace key: %s", err)
 		return
 	}
-	if !c.controller.isWatchingNamespace(key) {
+	if !c.namespaceProvider.IsWatchingNamespace(key) {
 		return
 	}
 	log.Debugw("ApisixConsumer add event arrived",
@@ -240,7 +254,7 @@ func (c *apisixConsumerController) onAdd(obj interface{}) {
 		},
 	})
 
-	c.controller.MetricsCollector.IncrEvents("consumer", "add")
+	c.MetricsCollector.IncrEvents("consumer", "add")
 }
 
 func (c *apisixConsumerController) onUpdate(oldObj, newObj interface{}) {
@@ -262,7 +276,7 @@ func (c *apisixConsumerController) onUpdate(oldObj, newObj interface{}) {
 		log.Errorf("found ApisixConsumer resource with bad meta namespace key: %s", err)
 		return
 	}
-	if !c.controller.isWatchingNamespace(key) {
+	if !c.namespaceProvider.IsWatchingNamespace(key) {
 		return
 	}
 	log.Debugw("ApisixConsumer update event arrived",
@@ -279,7 +293,7 @@ func (c *apisixConsumerController) onUpdate(oldObj, newObj interface{}) {
 		},
 	})
 
-	c.controller.MetricsCollector.IncrEvents("consumer", "update")
+	c.MetricsCollector.IncrEvents("consumer", "update")
 }
 
 func (c *apisixConsumerController) onDelete(obj interface{}) {
@@ -301,7 +315,7 @@ func (c *apisixConsumerController) onDelete(obj interface{}) {
 		log.Errorf("found ApisixConsumer resource with bad meta namespace key: %s", err)
 		return
 	}
-	if !c.controller.isWatchingNamespace(key) {
+	if !c.namespaceProvider.IsWatchingNamespace(key) {
 		return
 	}
 	log.Debugw("ApisixConsumer delete event arrived",
@@ -316,18 +330,18 @@ func (c *apisixConsumerController) onDelete(obj interface{}) {
 		Tombstone: ac,
 	})
 
-	c.controller.MetricsCollector.IncrEvents("consumer", "delete")
+	c.MetricsCollector.IncrEvents("consumer", "delete")
 }
 
 func (c *apisixConsumerController) ResourceSync() {
-	objs := c.controller.apisixConsumerInformer.GetIndexer().List()
+	objs := c.apisixConsumerInformer.GetIndexer().List()
 	for _, obj := range objs {
 		key, err := cache.MetaNamespaceKeyFunc(obj)
 		if err != nil {
 			log.Errorw("ApisixConsumer sync failed, found ApisixConsumer resource with bad meta namespace key", zap.String("error", err.Error()))
 			continue
 		}
-		if !c.controller.isWatchingNamespace(key) {
+		if !c.namespaceProvider.IsWatchingNamespace(key) {
 			continue
 		}
 		ac, err := kube.NewApisixConsumer(obj)
@@ -344,3 +358,64 @@ func (c *apisixConsumerController) ResourceSync() {
 		})
 	}
 }
+
+// recordStatus record resources status
+func (c *apisixConsumerController) recordStatus(at interface{}, reason string, err error, status metav1.ConditionStatus, generation int64) {
+	// build condition
+	message := utils.CommonSuccessMessage
+	if err != nil {
+		message = err.Error()
+	}
+	condition := metav1.Condition{
+		Type:               utils.ConditionType,
+		Reason:             reason,
+		Status:             status,
+		Message:            message,
+		ObservedGeneration: generation,
+	}
+	apisixClient := c.KubeClient.APISIXClient
+
+	if kubeObj, ok := at.(runtime.Object); ok {
+		at = kubeObj.DeepCopyObject()
+	}
+
+	switch v := at.(type) {
+	case *configv2beta3.ApisixConsumer:
+		// set to status
+		if v.Status.Conditions == nil {
+			conditions := make([]metav1.Condition, 0)
+			v.Status.Conditions = conditions
+		}
+		if utils.VerifyGeneration(&v.Status.Conditions, condition) {
+			meta.SetStatusCondition(&v.Status.Conditions, condition)
+			if _, errRecord := apisixClient.ApisixV2beta3().ApisixConsumers(v.Namespace).
+				UpdateStatus(context.TODO(), v, metav1.UpdateOptions{}); errRecord != nil {
+				log.Errorw("failed to record status change for ApisixConsumer",
+					zap.Error(errRecord),
+					zap.String("name", v.Name),
+					zap.String("namespace", v.Namespace),
+				)
+			}
+		}
+	case *configv2.ApisixConsumer:
+		// set to status
+		if v.Status.Conditions == nil {
+			conditions := make([]metav1.Condition, 0)
+			v.Status.Conditions = conditions
+		}
+		if utils.VerifyGeneration(&v.Status.Conditions, condition) {
+			meta.SetStatusCondition(&v.Status.Conditions, condition)
+			if _, errRecord := apisixClient.ApisixV2().ApisixConsumers(v.Namespace).
+				UpdateStatus(context.TODO(), v, metav1.UpdateOptions{}); errRecord != nil {
+				log.Errorw("failed to record status change for ApisixConsumer",
+					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)
+	}
+}
diff --git a/pkg/ingress/apisix_pluginconfig.go b/pkg/providers/apisix/apisix_plugin_config.go
similarity index 61%
rename from pkg/ingress/apisix_pluginconfig.go
rename to pkg/providers/apisix/apisix_plugin_config.go
index fa7e64a7..ea20a719 100644
--- a/pkg/ingress/apisix_pluginconfig.go
+++ b/pkg/providers/apisix/apisix_plugin_config.go
@@ -12,7 +12,7 @@
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 // See the License for the specific language governing permissions and
 // limitations under the License.
-package ingress
+package apisix
 
 import (
 	"context"
@@ -22,38 +22,51 @@ import (
 	"go.uber.org/zap"
 	v1 "k8s.io/api/core/v1"
 	k8serrors "k8s.io/apimachinery/pkg/api/errors"
+	"k8s.io/apimachinery/pkg/api/meta"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/client-go/tools/cache"
 	"k8s.io/client-go/util/workqueue"
 
 	"github.com/apache/apisix-ingress-controller/pkg/config"
-	"github.com/apache/apisix-ingress-controller/pkg/ingress/utils"
 	"github.com/apache/apisix-ingress-controller/pkg/kube"
-	"github.com/apache/apisix-ingress-controller/pkg/kube/translation"
+	configv2 "github.com/apache/apisix-ingress-controller/pkg/kube/apisix/apis/config/v2"
+	configv2beta3 "github.com/apache/apisix-ingress-controller/pkg/kube/apisix/apis/config/v2beta3"
 	"github.com/apache/apisix-ingress-controller/pkg/log"
+	"github.com/apache/apisix-ingress-controller/pkg/providers/translation"
+	"github.com/apache/apisix-ingress-controller/pkg/providers/utils"
 	"github.com/apache/apisix-ingress-controller/pkg/types"
 )
 
 type apisixPluginConfigController struct {
-	controller *Controller
-	workqueue  workqueue.RateLimitingInterface
-	workers    int
+	*apisixCommon
+
+	workqueue workqueue.RateLimitingInterface
+	workers   int
+
+	apisixPluginConfigLister   kube.ApisixPluginConfigLister
+	apisixPluginConfigInformer cache.SharedIndexInformer
 }
 
-func (c *Controller) newApisixPluginConfigController() *apisixPluginConfigController {
-	ctl := &apisixPluginConfigController{
-		controller: c,
-		workqueue:  workqueue.NewNamedRateLimitingQueue(workqueue.NewItemFastSlowRateLimiter(1*time.Second, 60*time.Second, 5), "ApisixPluginConfig"),
-		workers:    1,
+func newApisixPluginConfigController(common *apisixCommon,
+	apisixPluginConfigInformer cache.SharedIndexInformer, apisixPluginConfigLister kube.ApisixPluginConfigLister) *apisixPluginConfigController {
+	c := &apisixPluginConfigController{
+		apisixCommon: common,
+		workqueue:    workqueue.NewNamedRateLimitingQueue(workqueue.NewItemFastSlowRateLimiter(1*time.Second, 60*time.Second, 5), "ApisixPluginConfig"),
+		workers:      1,
+
+		apisixPluginConfigLister:   apisixPluginConfigLister,
+		apisixPluginConfigInformer: apisixPluginConfigInformer,
 	}
+
 	c.apisixPluginConfigInformer.AddEventHandler(
 		cache.ResourceEventHandlerFuncs{
-			AddFunc:    ctl.onAdd,
-			UpdateFunc: ctl.onUpdate,
-			DeleteFunc: ctl.onDelete,
+			AddFunc:    c.onAdd,
+			UpdateFunc: c.onUpdate,
+			DeleteFunc: c.onDelete,
 		},
 	)
-	return ctl
+	return c
 }
 
 func (c *apisixPluginConfigController) run(ctx context.Context) {
@@ -61,7 +74,7 @@ func (c *apisixPluginConfigController) run(ctx context.Context) {
 	defer log.Info("ApisixPluginConfig controller exited")
 	defer c.workqueue.ShutDown()
 
-	ok := cache.WaitForCacheSync(ctx.Done(), c.controller.apisixPluginConfigInformer.HasSynced)
+	ok := cache.WaitForCacheSync(ctx.Done(), c.apisixPluginConfigInformer.HasSynced)
 	if !ok {
 		log.Error("cache sync failed")
 		return
@@ -98,9 +111,9 @@ func (c *apisixPluginConfigController) sync(ctx context.Context, ev *types.Event
 	)
 	switch obj.GroupVersion {
 	case config.ApisixV2beta3:
-		apc, err = c.controller.apisixPluginConfigLister.V2beta3(namespace, name)
+		apc, err = c.apisixPluginConfigLister.V2beta3(namespace, name)
 	case config.ApisixV2:
-		apc, err = c.controller.apisixPluginConfigLister.V2(namespace, name)
+		apc, err = c.apisixPluginConfigLister.V2(namespace, name)
 	default:
 		return fmt.Errorf("unsupported ApisixPluginConfig group version %s", obj.GroupVersion)
 	}
@@ -138,9 +151,9 @@ func (c *apisixPluginConfigController) sync(ctx context.Context, ev *types.Event
 	switch obj.GroupVersion {
 	case config.ApisixV2beta3:
 		if ev.Type != types.EventDelete {
-			tctx, err = c.controller.translator.TranslatePluginConfigV2beta3(apc.V2beta3())
+			tctx, err = c.translator.TranslatePluginConfigV2beta3(apc.V2beta3())
 		} else {
-			tctx, err = c.controller.translator.TranslatePluginConfigV2beta3NotStrictly(apc.V2beta3())
+			tctx, err = c.translator.TranslatePluginConfigV2beta3NotStrictly(apc.V2beta3())
 		}
 		if err != nil {
 			log.Errorw("failed to translate ApisixPluginConfig v2beta3",
@@ -151,9 +164,9 @@ func (c *apisixPluginConfigController) sync(ctx context.Context, ev *types.Event
 		}
 	case config.ApisixV2:
 		if ev.Type != types.EventDelete {
-			tctx, err = c.controller.translator.TranslatePluginConfigV2(apc.V2())
+			tctx, err = c.translator.TranslatePluginConfigV2(apc.V2())
 		} else {
-			tctx, err = c.controller.translator.TranslatePluginConfigV2NotStrictly(apc.V2())
+			tctx, err = c.translator.TranslatePluginConfigV2NotStrictly(apc.V2())
 		}
 		if err != nil {
 			log.Errorw("failed to translate ApisixPluginConfig v2",
@@ -186,9 +199,9 @@ func (c *apisixPluginConfigController) sync(ctx context.Context, ev *types.Event
 		var oldCtx *translation.TranslateContext
 		switch obj.GroupVersion {
 		case config.ApisixV2beta3:
-			oldCtx, err = c.controller.translator.TranslatePluginConfigV2beta3(obj.OldObject.V2beta3())
+			oldCtx, err = c.translator.TranslatePluginConfigV2beta3(obj.OldObject.V2beta3())
 		case config.ApisixV2:
-			oldCtx, err = c.controller.translator.TranslatePluginConfigV2(obj.OldObject.V2())
+			oldCtx, err = c.translator.TranslatePluginConfigV2(obj.OldObject.V2())
 		}
 		if err != nil {
 			log.Errorw("failed to translate old ApisixPluginConfig",
@@ -206,7 +219,7 @@ func (c *apisixPluginConfigController) sync(ctx context.Context, ev *types.Event
 		added, updated, deleted = m.Diff(om)
 	}
 
-	return c.controller.syncManifests(ctx, added, updated, deleted)
+	return c.SyncManifests(ctx, added, updated, deleted)
 }
 
 func (c *apisixPluginConfigController) handleSyncErr(obj interface{}, errOrigin error) {
@@ -223,15 +236,15 @@ func (c *apisixPluginConfigController) handleSyncErr(obj interface{}, errOrigin
 	namespace, name, errLocal := cache.SplitMetaNamespaceKey(event.Key)
 	if errLocal != nil {
 		log.Errorf("invalid resource key: %s", event.Key)
-		c.controller.MetricsCollector.IncrSyncOperation("PluginConfig", "failure")
+		c.MetricsCollector.IncrSyncOperation("PluginConfig", "failure")
 		return
 	}
 	var apc kube.ApisixPluginConfig
 	switch event.GroupVersion {
 	case config.ApisixV2beta3:
-		apc, errLocal = c.controller.apisixPluginConfigLister.V2beta3(namespace, name)
+		apc, errLocal = c.apisixPluginConfigLister.V2beta3(namespace, name)
 	case config.ApisixV2:
-		apc, errLocal = c.controller.apisixPluginConfigLister.V2(namespace, name)
+		apc, errLocal = c.apisixPluginConfigLister.V2(namespace, name)
 	default:
 		errLocal = fmt.Errorf("unsupported ApisixPluginConfig group version %s", event.GroupVersion)
 	}
@@ -240,11 +253,11 @@ func (c *apisixPluginConfigController) handleSyncErr(obj interface{}, errOrigin
 			if errLocal == nil {
 				switch apc.GroupVersion() {
 				case config.ApisixV2beta3:
-					c.controller.recorderEvent(apc.V2beta3(), v1.EventTypeNormal, _resourceSynced, nil)
-					c.controller.recordStatus(apc.V2beta3(), _resourceSynced, nil, metav1.ConditionTrue, apc.V2beta3().GetGeneration())
+					c.RecordEvent(apc.V2beta3(), v1.EventTypeNormal, utils.ResourceSynced, nil)
+					c.recordStatus(apc.V2beta3(), utils.ResourceSynced, nil, metav1.ConditionTrue, apc.V2beta3().GetGeneration())
 				case config.ApisixV2:
-					c.controller.recorderEvent(apc.V2(), v1.EventTypeNormal, _resourceSynced, nil)
-					c.controller.recordStatus(apc.V2(), _resourceSynced, nil, metav1.ConditionTrue, apc.V2().GetGeneration())
+					c.RecordEvent(apc.V2(), v1.EventTypeNormal, utils.ResourceSynced, nil)
+					c.recordStatus(apc.V2(), utils.ResourceSynced, nil, metav1.ConditionTrue, apc.V2().GetGeneration())
 				}
 			} else {
 				log.Errorw("failed list ApisixPluginConfig",
@@ -255,7 +268,7 @@ func (c *apisixPluginConfigController) handleSyncErr(obj interface{}, errOrigin
 			}
 		}
 		c.workqueue.Forget(obj)
-		c.controller.MetricsCollector.IncrSyncOperation("PluginConfig", "success")
+		c.MetricsCollector.IncrSyncOperation("PluginConfig", "success")
 		return
 	}
 	log.Warnw("sync ApisixPluginConfig failed, will retry",
@@ -265,11 +278,11 @@ func (c *apisixPluginConfigController) handleSyncErr(obj interface{}, errOrigin
 	if errLocal == nil {
 		switch apc.GroupVersion() {
 		case config.ApisixV2beta3:
-			c.controller.recorderEvent(apc.V2beta3(), v1.EventTypeWarning, _resourceSyncAborted, errOrigin)
-			c.controller.recordStatus(apc.V2beta3(), _resourceSyncAborted, errOrigin, metav1.ConditionFalse, apc.V2beta3().GetGeneration())
+			c.RecordEvent(apc.V2beta3(), v1.EventTypeWarning, utils.ResourceSyncAborted, errOrigin)
+			c.recordStatus(apc.V2beta3(), utils.ResourceSyncAborted, errOrigin, metav1.ConditionFalse, apc.V2beta3().GetGeneration())
 		case config.ApisixV2:
-			c.controller.recorderEvent(apc.V2(), v1.EventTypeWarning, _resourceSyncAborted, errOrigin)
-			c.controller.recordStatus(apc.V2(), _resourceSyncAborted, errOrigin, metav1.ConditionFalse, apc.V2().GetGeneration())
+			c.RecordEvent(apc.V2(), v1.EventTypeWarning, utils.ResourceSyncAborted, errOrigin)
+			c.recordStatus(apc.V2(), utils.ResourceSyncAborted, errOrigin, metav1.ConditionFalse, apc.V2().GetGeneration())
 		}
 	} else {
 		log.Errorw("failed list ApisixPluginConfig",
@@ -279,7 +292,7 @@ func (c *apisixPluginConfigController) handleSyncErr(obj interface{}, errOrigin
 		)
 	}
 	c.workqueue.AddRateLimited(obj)
-	c.controller.MetricsCollector.IncrSyncOperation("PluginConfig", "failure")
+	c.MetricsCollector.IncrSyncOperation("PluginConfig", "failure")
 }
 
 func (c *apisixPluginConfigController) onAdd(obj interface{}) {
@@ -288,7 +301,7 @@ func (c *apisixPluginConfigController) onAdd(obj interface{}) {
 		log.Errorf("found ApisixPluginConfig resource with bad meta namespace key: %s", err)
 		return
 	}
-	if !c.controller.isWatchingNamespace(key) {
+	if !c.namespaceProvider.IsWatchingNamespace(key) {
 		return
 	}
 	log.Debugw("ApisixPluginConfig add event arrived",
@@ -303,7 +316,7 @@ func (c *apisixPluginConfigController) onAdd(obj interface{}) {
 		},
 	})
 
-	c.controller.MetricsCollector.IncrEvents("PluginConfig", "add")
+	c.MetricsCollector.IncrEvents("PluginConfig", "add")
 }
 
 func (c *apisixPluginConfigController) onUpdate(oldObj, newObj interface{}) {
@@ -317,7 +330,7 @@ func (c *apisixPluginConfigController) onUpdate(oldObj, newObj interface{}) {
 		log.Errorf("found ApisixPluginConfig resource with bad meta namespace key: %s", err)
 		return
 	}
-	if !c.controller.isWatchingNamespace(key) {
+	if !c.namespaceProvider.IsWatchingNamespace(key) {
 		return
 	}
 	log.Debugw("ApisixPluginConfig update event arrived",
@@ -333,7 +346,7 @@ func (c *apisixPluginConfigController) onUpdate(oldObj, newObj interface{}) {
 		},
 	})
 
-	c.controller.MetricsCollector.IncrEvents("PluginConfig", "update")
+	c.MetricsCollector.IncrEvents("PluginConfig", "update")
 }
 
 func (c *apisixPluginConfigController) onDelete(obj interface{}) {
@@ -350,7 +363,7 @@ func (c *apisixPluginConfigController) onDelete(obj interface{}) {
 		log.Errorf("found ApisixPluginConfig resource with bad meta namesapce key: %s", err)
 		return
 	}
-	if !c.controller.isWatchingNamespace(key) {
+	if !c.namespaceProvider.IsWatchingNamespace(key) {
 		return
 	}
 	log.Debugw("ApisixPluginConfig delete event arrived",
@@ -365,18 +378,18 @@ func (c *apisixPluginConfigController) onDelete(obj interface{}) {
 		Tombstone: apc,
 	})
 
-	c.controller.MetricsCollector.IncrEvents("PluginConfig", "delete")
+	c.MetricsCollector.IncrEvents("PluginConfig", "delete")
 }
 
 func (c *apisixPluginConfigController) ResourceSync() {
-	objs := c.controller.apisixPluginConfigInformer.GetIndexer().List()
+	objs := c.apisixPluginConfigInformer.GetIndexer().List()
 	for _, obj := range objs {
 		key, err := cache.MetaNamespaceKeyFunc(obj)
 		if err != nil {
 			log.Errorw("ApisixPluginConfig sync failed, found ApisixPluginConfig resource with bad meta namespace key", zap.String("error", err.Error()))
 			continue
 		}
-		if !c.controller.isWatchingNamespace(key) {
+		if !c.namespaceProvider.IsWatchingNamespace(key) {
 			continue
 		}
 		apc := kube.MustNewApisixPluginConfig(obj)
@@ -389,3 +402,64 @@ func (c *apisixPluginConfigController) ResourceSync() {
 		})
 	}
 }
+
+// recordStatus record resources status
+func (c *apisixPluginConfigController) recordStatus(at interface{}, reason string, err error, status metav1.ConditionStatus, generation int64) {
+	// build condition
+	message := utils.CommonSuccessMessage
+	if err != nil {
+		message = err.Error()
+	}
+	condition := metav1.Condition{
+		Type:               utils.ConditionType,
+		Reason:             reason,
+		Status:             status,
+		Message:            message,
+		ObservedGeneration: generation,
+	}
+	apisixClient := c.KubeClient.APISIXClient
+
+	if kubeObj, ok := at.(runtime.Object); ok {
+		at = kubeObj.DeepCopyObject()
+	}
+
+	switch v := at.(type) {
+	case *configv2beta3.ApisixPluginConfig:
+		// set to status
+		if v.Status.Conditions == nil {
+			conditions := make([]metav1.Condition, 0)
+			v.Status.Conditions = conditions
+		}
+		if utils.VerifyGeneration(&v.Status.Conditions, condition) {
+			meta.SetStatusCondition(&v.Status.Conditions, condition)
+			if _, errRecord := apisixClient.ApisixV2beta3().ApisixPluginConfigs(v.Namespace).
+				UpdateStatus(context.TODO(), v, metav1.UpdateOptions{}); errRecord != nil {
+				log.Errorw("failed to record status change for ApisixPluginConfig",
+					zap.Error(errRecord),
+					zap.String("name", v.Name),
+					zap.String("namespace", v.Namespace),
+				)
+			}
+		}
+	case *configv2.ApisixPluginConfig:
+		// set to status
+		if v.Status.Conditions == nil {
+			conditions := make([]metav1.Condition, 0)
+			v.Status.Conditions = conditions
+		}
+		if utils.VerifyGeneration(&v.Status.Conditions, condition) {
+			meta.SetStatusCondition(&v.Status.Conditions, condition)
+			if _, errRecord := apisixClient.ApisixV2().ApisixPluginConfigs(v.Namespace).
+				UpdateStatus(context.TODO(), v, metav1.UpdateOptions{}); errRecord != nil {
+				log.Errorw("failed to record status change for ApisixPluginConfig",
+					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)
+	}
+}
diff --git a/pkg/ingress/apisix_route.go b/pkg/providers/apisix/apisix_route.go
similarity index 66%
rename from pkg/ingress/apisix_route.go
rename to pkg/providers/apisix/apisix_route.go
index 28c51a87..9f0e37cd 100644
--- a/pkg/ingress/apisix_route.go
+++ b/pkg/providers/apisix/apisix_route.go
@@ -12,64 +12,76 @@
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 // See the License for the specific language governing permissions and
 // limitations under the License.
-package ingress
+package apisix
 
 import (
 	"context"
+	"fmt"
 	"sync"
 	"time"
 
 	"go.uber.org/zap"
 	v1 "k8s.io/api/core/v1"
 	k8serrors "k8s.io/apimachinery/pkg/api/errors"
+	"k8s.io/apimachinery/pkg/api/meta"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/client-go/tools/cache"
 	"k8s.io/client-go/util/workqueue"
 
 	apisixcache "github.com/apache/apisix-ingress-controller/pkg/apisix/cache"
 	"github.com/apache/apisix-ingress-controller/pkg/config"
-	"github.com/apache/apisix-ingress-controller/pkg/ingress/utils"
 	"github.com/apache/apisix-ingress-controller/pkg/kube"
 	v2 "github.com/apache/apisix-ingress-controller/pkg/kube/apisix/apis/config/v2"
+	configv2beta2 "github.com/apache/apisix-ingress-controller/pkg/kube/apisix/apis/config/v2beta2"
 	"github.com/apache/apisix-ingress-controller/pkg/kube/apisix/apis/config/v2beta3"
-	"github.com/apache/apisix-ingress-controller/pkg/kube/translation"
 	"github.com/apache/apisix-ingress-controller/pkg/log"
+	"github.com/apache/apisix-ingress-controller/pkg/providers/translation"
+	"github.com/apache/apisix-ingress-controller/pkg/providers/utils"
 	"github.com/apache/apisix-ingress-controller/pkg/types"
 	apisixv1 "github.com/apache/apisix-ingress-controller/pkg/types/apisix/v1"
 )
 
 type apisixRouteController struct {
-	controller *Controller
-	workqueue  workqueue.RateLimitingInterface
-	workers    int
+	*apisixCommon
+	workqueue workqueue.RateLimitingInterface
+	workers   int
+
+	svcInformer         cache.SharedIndexInformer
+	apisixRouteLister   kube.ApisixRouteLister
+	apisixRouteInformer cache.SharedIndexInformer
 
-	// svc meta key -> ApisixRoute name -> empty
 	svcLock sync.RWMutex
 	svcMap  map[string]map[string]struct{}
 }
 
-func (c *Controller) newApisixRouteController() *apisixRouteController {
-	ctl := &apisixRouteController{
-		controller: c,
-		workqueue:  workqueue.NewNamedRateLimitingQueue(workqueue.NewItemFastSlowRateLimiter(1*time.Second, 60*time.Second, 5), "ApisixRoute"),
-		workers:    1,
+func newApisixRouteController(common *apisixCommon, apisixRouteInformer cache.SharedIndexInformer, apisixRouteLister kube.ApisixRouteLister) *apisixRouteController {
+	c := &apisixRouteController{
+		apisixCommon: common,
+		workqueue:    workqueue.NewNamedRateLimitingQueue(workqueue.NewItemFastSlowRateLimiter(1*time.Second, 60*time.Second, 5), "ApisixRoute"),
+		workers:      1,
+
+		svcInformer:         common.SvcInformer,
+		apisixRouteLister:   apisixRouteLister,
+		apisixRouteInformer: apisixRouteInformer,
 
 		svcMap: make(map[string]map[string]struct{}),
 	}
+
 	c.apisixRouteInformer.AddEventHandler(
 		cache.ResourceEventHandlerFuncs{
-			AddFunc:    ctl.onAdd,
-			UpdateFunc: ctl.onUpdate,
-			DeleteFunc: ctl.onDelete,
+			AddFunc:    c.onAdd,
+			UpdateFunc: c.onUpdate,
+			DeleteFunc: c.onDelete,
 		},
 	)
 	c.svcInformer.AddEventHandler(
 		cache.ResourceEventHandlerFuncs{
-			AddFunc: ctl.onSvcAdd,
+			AddFunc: c.onSvcAdd,
 		},
 	)
 
-	return ctl
+	return c
 }
 
 func (c *apisixRouteController) run(ctx context.Context) {
@@ -77,7 +89,7 @@ func (c *apisixRouteController) run(ctx context.Context) {
 	defer log.Info("ApisixRoute controller exited")
 	defer c.workqueue.ShutDown()
 
-	ok := cache.WaitForCacheSync(ctx.Done(), c.controller.apisixRouteInformer.HasSynced, c.controller.svcInformer.HasSynced)
+	ok := cache.WaitForCacheSync(ctx.Done(), c.apisixRouteInformer.HasSynced, c.svcInformer.HasSynced)
 	if !ok {
 		log.Error("cache sync failed")
 		return
@@ -179,6 +191,11 @@ func (c *apisixRouteController) syncServiceRelationship(ev *types.Event, name st
 				}
 			}
 		}
+	default:
+		log.Errorw("unknown ApisixRoute version",
+			zap.String("version", obj.GroupVersion),
+			zap.String("key", obj.Key),
+		)
 	}
 
 	// NOTE:
@@ -215,11 +232,17 @@ func (c *apisixRouteController) sync(ctx context.Context, ev *types.Event) error
 	)
 	switch obj.GroupVersion {
 	case config.ApisixV2beta2:
-		ar, err = c.controller.apisixRouteLister.V2beta2(namespace, name)
+		ar, err = c.apisixRouteLister.V2beta2(namespace, name)
 	case config.ApisixV2beta3:
-		ar, err = c.controller.apisixRouteLister.V2beta3(namespace, name)
+		ar, err = c.apisixRouteLister.V2beta3(namespace, name)
 	case config.ApisixV2:
-		ar, err = c.controller.apisixRouteLister.V2(namespace, name)
+		ar, err = c.apisixRouteLister.V2(namespace, name)
+	default:
+		log.Errorw("unknown ApisixRoute version",
+			zap.String("version", obj.GroupVersion),
+			zap.String("key", obj.Key),
+		)
+		return fmt.Errorf("unknown ApisixRoute version %v", obj.GroupVersion)
 	}
 	if err != nil {
 		if !k8serrors.IsNotFound(err) {
@@ -258,9 +281,9 @@ func (c *apisixRouteController) sync(ctx context.Context, ev *types.Event) error
 	switch obj.GroupVersion {
 	case config.ApisixV2beta2:
 		if ev.Type != types.EventDelete {
-			tctx, err = c.controller.translator.TranslateRouteV2beta2(ar.V2beta2())
+			tctx, err = c.translator.TranslateRouteV2beta2(ar.V2beta2())
 		} else {
-			tctx, err = c.controller.translator.TranslateRouteV2beta2NotStrictly(ar.V2beta2())
+			tctx, err = c.translator.TranslateRouteV2beta2NotStrictly(ar.V2beta2())
 		}
 		if err != nil {
 			log.Errorw("failed to translate ApisixRoute v2beta2",
@@ -272,10 +295,10 @@ func (c *apisixRouteController) sync(ctx context.Context, ev *types.Event) error
 	case config.ApisixV2beta3:
 		if ev.Type != types.EventDelete {
 			if err = c.checkPluginNameIfNotEmptyV2beta3(ctx, ar.V2beta3()); err == nil {
-				tctx, err = c.controller.translator.TranslateRouteV2beta3(ar.V2beta3())
+				tctx, err = c.translator.TranslateRouteV2beta3(ar.V2beta3())
 			}
 		} else {
-			tctx, err = c.controller.translator.TranslateRouteV2beta3NotStrictly(ar.V2beta3())
+			tctx, err = c.translator.TranslateRouteV2beta3NotStrictly(ar.V2beta3())
 		}
 		if err != nil {
 			log.Errorw("failed to translate ApisixRoute v2beta3",
@@ -287,10 +310,10 @@ func (c *apisixRouteController) sync(ctx context.Context, ev *types.Event) error
 	case config.ApisixV2:
 		if ev.Type != types.EventDelete {
 			if err = c.checkPluginNameIfNotEmptyV2(ctx, ar.V2()); err == nil {
-				tctx, err = c.controller.translator.TranslateRouteV2(ar.V2())
+				tctx, err = c.translator.TranslateRouteV2(ar.V2())
 			}
 		} else {
-			tctx, err = c.controller.translator.TranslateRouteV2NotStrictly(ar.V2())
+			tctx, err = c.translator.TranslateRouteV2NotStrictly(ar.V2())
 		}
 		if err != nil {
 			log.Errorw("failed to translate ApisixRoute v2",
@@ -299,6 +322,12 @@ func (c *apisixRouteController) sync(ctx context.Context, ev *types.Event) error
 			)
 			return err
 		}
+	default:
+		log.Errorw("unknown ApisixRoute version",
+			zap.String("version", obj.GroupVersion),
+			zap.String("key", obj.Key),
+		)
+		return fmt.Errorf("unknown ApisixRoute version %v", obj.GroupVersion)
 	}
 
 	log.Debugw("translated ApisixRoute",
@@ -326,7 +355,7 @@ func (c *apisixRouteController) sync(ctx context.Context, ev *types.Event) error
 	} else if ev.Type == types.EventAdd {
 		added = m
 	} else {
-		oldCtx, _ := c.controller.translator.TranslateOldRoute(obj.OldObject)
+		oldCtx, _ := c.translator.TranslateOldRoute(obj.OldObject)
 		om := &utils.Manifest{
 			Routes:        oldCtx.Routes,
 			Upstreams:     oldCtx.Upstreams,
@@ -336,13 +365,13 @@ func (c *apisixRouteController) sync(ctx context.Context, ev *types.Event) error
 		added, updated, deleted = m.Diff(om)
 	}
 
-	return c.controller.syncManifests(ctx, added, updated, deleted)
+	return c.SyncManifests(ctx, added, updated, deleted)
 }
 
 func (c *apisixRouteController) checkPluginNameIfNotEmptyV2beta3(ctx context.Context, in *v2beta3.ApisixRoute) error {
 	for _, v := range in.Spec.HTTP {
 		if v.PluginConfigName != "" {
-			_, err := c.controller.apisix.Cluster(c.controller.cfg.APISIX.DefaultClusterName).PluginConfig().Get(ctx, apisixv1.ComposePluginConfigName(in.Namespace, v.PluginConfigName))
+			_, err := c.APISIX.Cluster(c.Config.APISIX.DefaultClusterName).PluginConfig().Get(ctx, apisixv1.ComposePluginConfigName(in.Namespace, v.PluginConfigName))
 			if err != nil {
 				if err == apisixcache.ErrNotFound {
 					log.Errorw("checkPluginNameIfNotEmptyV2beta3 error: plugin_config not found",
@@ -365,7 +394,7 @@ func (c *apisixRouteController) checkPluginNameIfNotEmptyV2beta3(ctx context.Con
 func (c *apisixRouteController) checkPluginNameIfNotEmptyV2(ctx context.Context, in *v2.ApisixRoute) error {
 	for _, v := range in.Spec.HTTP {
 		if v.PluginConfigName != "" {
-			_, err := c.controller.apisix.Cluster(c.controller.cfg.APISIX.DefaultClusterName).PluginConfig().Get(ctx, apisixv1.ComposePluginConfigName(in.Namespace, v.PluginConfigName))
+			_, err := c.APISIX.Cluster(c.Config.APISIX.DefaultClusterName).PluginConfig().Get(ctx, apisixv1.ComposePluginConfigName(in.Namespace, v.PluginConfigName))
 			if err != nil {
 				if err == apisixcache.ErrNotFound {
 					log.Errorw("checkPluginNameIfNotEmptyV2 error: plugin_config not found",
@@ -399,31 +428,36 @@ func (c *apisixRouteController) handleSyncErr(obj interface{}, errOrigin error)
 	namespace, name, errLocal := cache.SplitMetaNamespaceKey(event.Key)
 	if errLocal != nil {
 		log.Errorf("invalid resource key: %s", event.Key)
-		c.controller.MetricsCollector.IncrSyncOperation("route", "failure")
+		c.MetricsCollector.IncrSyncOperation("route", "failure")
 		return
 	}
 	var ar kube.ApisixRoute
 	switch event.GroupVersion {
 	case config.ApisixV2beta2:
-		ar, errLocal = c.controller.apisixRouteLister.V2beta2(namespace, name)
+		ar, errLocal = c.apisixRouteLister.V2beta2(namespace, name)
 	case config.ApisixV2beta3:
-		ar, errLocal = c.controller.apisixRouteLister.V2beta3(namespace, name)
+		ar, errLocal = c.apisixRouteLister.V2beta3(namespace, name)
 	case config.ApisixV2:
-		ar, errLocal = c.controller.apisixRouteLister.V2(namespace, name)
+		ar, errLocal = c.apisixRouteLister.V2(namespace, name)
+	default:
+		log.Errorw("unknown ApisixRoute version",
+			zap.String("version", event.GroupVersion),
+			zap.String("key", event.Key),
+		)
 	}
 	if errOrigin == nil {
 		if ev.Type != types.EventDelete {
 			if errLocal == nil {
 				switch ar.GroupVersion() {
 				case config.ApisixV2beta2:
-					c.controller.recorderEvent(ar.V2beta2(), v1.EventTypeNormal, _resourceSynced, nil)
-					c.controller.recordStatus(ar.V2beta2(), _resourceSynced, nil, metav1.ConditionTrue, ar.V2beta2().GetGeneration())
+					c.RecordEvent(ar.V2beta2(), v1.EventTypeNormal, utils.ResourceSynced, nil)
+					c.recordStatus(ar.V2beta2(), utils.ResourceSynced, nil, metav1.ConditionTrue, ar.V2beta2().GetGeneration())
 				case config.ApisixV2beta3:
-					c.controller.recorderEvent(ar.V2beta3(), v1.EventTypeNormal, _resourceSynced, nil)
-					c.controller.recordStatus(ar.V2beta3(), _resourceSynced, nil, metav1.ConditionTrue, ar.V2beta3().GetGeneration())
+					c.RecordEvent(ar.V2beta3(), v1.EventTypeNormal, utils.ResourceSynced, nil)
+					c.recordStatus(ar.V2beta3(), utils.ResourceSynced, nil, metav1.ConditionTrue, ar.V2beta3().GetGeneration())
 				case config.ApisixV2:
-					c.controller.recorderEvent(ar.V2(), v1.EventTypeNormal, _resourceSynced, nil)
-					c.controller.recordStatus(ar.V2(), _resourceSynced, nil, metav1.ConditionTrue, ar.V2().GetGeneration())
+					c.RecordEvent(ar.V2(), v1.EventTypeNormal, utils.ResourceSynced, nil)
+					c.recordStatus(ar.V2(), utils.ResourceSynced, nil, metav1.ConditionTrue, ar.V2().GetGeneration())
 				}
 			} else {
 				log.Errorw("failed list ApisixRoute",
@@ -434,7 +468,7 @@ func (c *apisixRouteController) handleSyncErr(obj interface{}, errOrigin error)
 			}
 		}
 		c.workqueue.Forget(obj)
-		c.controller.MetricsCollector.IncrSyncOperation("route", "success")
+		c.MetricsCollector.IncrSyncOperation("route", "success")
 		return
 	}
 	log.Warnw("sync ApisixRoute failed, will retry",
@@ -444,14 +478,14 @@ func (c *apisixRouteController) handleSyncErr(obj interface{}, errOrigin error)
 	if errLocal == nil {
 		switch ar.GroupVersion() {
 		case config.ApisixV2beta2:
-			c.controller.recorderEvent(ar.V2beta2(), v1.EventTypeWarning, _resourceSyncAborted, errOrigin)
-			c.controller.recordStatus(ar.V2beta2(), _resourceSyncAborted, errOrigin, metav1.ConditionFalse, ar.V2beta2().GetGeneration())
+			c.RecordEvent(ar.V2beta2(), v1.EventTypeWarning, utils.ResourceSyncAborted, errOrigin)
+			c.recordStatus(ar.V2beta2(), utils.ResourceSyncAborted, errOrigin, metav1.ConditionFalse, ar.V2beta2().GetGeneration())
 		case config.ApisixV2beta3:
-			c.controller.recorderEvent(ar.V2beta3(), v1.EventTypeWarning, _resourceSyncAborted, errOrigin)
-			c.controller.recordStatus(ar.V2beta3(), _resourceSyncAborted, errOrigin, metav1.ConditionFalse, ar.V2beta3().GetGeneration())
+			c.RecordEvent(ar.V2beta3(), v1.EventTypeWarning, utils.ResourceSyncAborted, errOrigin)
+			c.recordStatus(ar.V2beta3(), utils.ResourceSyncAborted, errOrigin, metav1.ConditionFalse, ar.V2beta3().GetGeneration())
 		case config.ApisixV2:
-			c.controller.recorderEvent(ar.V2(), v1.EventTypeWarning, _resourceSyncAborted, errOrigin)
-			c.controller.recordStatus(ar.V2(), _resourceSyncAborted, errOrigin, metav1.ConditionFalse, ar.V2().GetGeneration())
+			c.RecordEvent(ar.V2(), v1.EventTypeWarning, utils.ResourceSyncAborted, errOrigin)
+			c.recordStatus(ar.V2(), utils.ResourceSyncAborted, errOrigin, metav1.ConditionFalse, ar.V2().GetGeneration())
 		}
 	} else {
 		log.Errorw("failed list ApisixRoute",
@@ -461,7 +495,7 @@ func (c *apisixRouteController) handleSyncErr(obj interface{}, errOrigin error)
 		)
 	}
 	c.workqueue.AddRateLimited(obj)
-	c.controller.MetricsCollector.IncrSyncOperation("route", "failure")
+	c.MetricsCollector.IncrSyncOperation("route", "failure")
 }
 
 func (c *apisixRouteController) onAdd(obj interface{}) {
@@ -470,7 +504,7 @@ func (c *apisixRouteController) onAdd(obj interface{}) {
 		log.Errorf("found ApisixRoute resource with bad meta namespace key: %s", err)
 		return
 	}
-	if !c.controller.isWatchingNamespace(key) {
+	if !c.namespaceProvider.IsWatchingNamespace(key) {
 		return
 	}
 	log.Debugw("ApisixRoute add event arrived",
@@ -487,7 +521,7 @@ func (c *apisixRouteController) onAdd(obj interface{}) {
 		},
 	})
 
-	c.controller.MetricsCollector.IncrEvents("route", "add")
+	c.MetricsCollector.IncrEvents("route", "add")
 }
 
 func (c *apisixRouteController) onUpdate(oldObj, newObj interface{}) {
@@ -501,7 +535,7 @@ func (c *apisixRouteController) onUpdate(oldObj, newObj interface{}) {
 		log.Errorf("found ApisixRoute resource with bad meta namespace key: %s", err)
 		return
 	}
-	if !c.controller.isWatchingNamespace(key) {
+	if !c.namespaceProvider.IsWatchingNamespace(key) {
 		return
 	}
 	log.Debugw("ApisixRoute update event arrived",
@@ -518,7 +552,7 @@ func (c *apisixRouteController) onUpdate(oldObj, newObj interface{}) {
 		},
 	})
 
-	c.controller.MetricsCollector.IncrEvents("route", "update")
+	c.MetricsCollector.IncrEvents("route", "update")
 }
 
 func (c *apisixRouteController) onDelete(obj interface{}) {
@@ -535,7 +569,7 @@ func (c *apisixRouteController) onDelete(obj interface{}) {
 		log.Errorf("found ApisixRoute resource with bad meta namesapce key: %s", err)
 		return
 	}
-	if !c.controller.isWatchingNamespace(key) {
+	if !c.namespaceProvider.IsWatchingNamespace(key) {
 		return
 	}
 	log.Debugw("ApisixRoute delete event arrived",
@@ -551,11 +585,11 @@ func (c *apisixRouteController) onDelete(obj interface{}) {
 		Tombstone: ar,
 	})
 
-	c.controller.MetricsCollector.IncrEvents("route", "delete")
+	c.MetricsCollector.IncrEvents("route", "delete")
 }
 
 func (c *apisixRouteController) ResourceSync() {
-	objs := c.controller.apisixRouteInformer.GetIndexer().List()
+	objs := c.apisixRouteInformer.GetIndexer().List()
 
 	c.svcLock.Lock()
 	defer c.svcLock.Unlock()
@@ -570,7 +604,7 @@ func (c *apisixRouteController) ResourceSync() {
 			)
 			continue
 		}
-		if !c.controller.isWatchingNamespace(key) {
+		if !c.namespaceProvider.IsWatchingNamespace(key) {
 			continue
 		}
 		ar := kube.MustNewApisixRoute(obj)
@@ -605,6 +639,11 @@ func (c *apisixRouteController) ResourceSync() {
 					backends = append(backends, ns+"/"+backend.ServiceName)
 				}
 			}
+		default:
+			log.Errorw("unknown ApisixRoute version",
+				zap.String("version", ar.GroupVersion()),
+				zap.String("key", key),
+			)
 		}
 		for _, svcKey := range backends {
 			if _, ok := c.svcMap[svcKey]; !ok {
@@ -627,7 +666,7 @@ func (c *apisixRouteController) onSvcAdd(obj interface{}) {
 		)
 		return
 	}
-	if !c.controller.isWatchingNamespace(key) {
+	if !c.namespaceProvider.IsWatchingNamespace(key) {
 		return
 	}
 
@@ -654,7 +693,7 @@ func (c *apisixRouteController) handleSvcAdd(key string) error {
 				Type: types.EventAdd,
 				Object: kube.ApisixRouteEvent{
 					Key:          ns + "/" + route,
-					GroupVersion: c.controller.cfg.Kubernetes.ApisixRouteVersion,
+					GroupVersion: c.Kubernetes.APIVersion,
 				},
 			})
 		}
@@ -675,3 +714,81 @@ func (c *apisixRouteController) handleSvcErr(key string, errOrigin error) {
 	)
 	c.workqueue.AddRateLimited(key)
 }
+
+// recordStatus record resources status
+func (c *apisixRouteController) recordStatus(at interface{}, reason string, err error, status metav1.ConditionStatus, generation int64) {
+	// build condition
+	message := utils.CommonSuccessMessage
+	if err != nil {
+		message = err.Error()
+	}
+	condition := metav1.Condition{
+		Type:               utils.ConditionType,
+		Reason:             reason,
+		Status:             status,
+		Message:            message,
+		ObservedGeneration: generation,
+	}
+	apisixClient := c.KubeClient.APISIXClient
+
+	if kubeObj, ok := at.(runtime.Object); ok {
+		at = kubeObj.DeepCopyObject()
+	}
+
+	switch v := at.(type) {
+	case *configv2beta2.ApisixRoute:
+		// set to status
+		if v.Status.Conditions == nil {
+			conditions := make([]metav1.Condition, 0)
+			v.Status.Conditions = conditions
+		}
+		if utils.VerifyGeneration(&v.Status.Conditions, condition) {
+			meta.SetStatusCondition(&v.Status.Conditions, condition)
+			if _, errRecord := apisixClient.ApisixV2beta2().ApisixRoutes(v.Namespace).
+				UpdateStatus(context.TODO(), v, metav1.UpdateOptions{}); errRecord != nil {
+				log.Errorw("failed to record status change for ApisixRoute",
+					zap.Error(errRecord),
+					zap.String("name", v.Name),
+					zap.String("namespace", v.Namespace),
+				)
+			}
+		}
+	case *v2beta3.ApisixRoute:
+		// set to status
+		if v.Status.Conditions == nil {
+			conditions := make([]metav1.Condition, 0)
+			v.Status.Conditions = conditions
+		}
+		if utils.VerifyGeneration(&v.Status.Conditions, condition) {
+			meta.SetStatusCondition(&v.Status.Conditions, condition)
+			if _, errRecord := apisixClient.ApisixV2beta3().ApisixRoutes(v.Namespace).
+				UpdateStatus(context.TODO(), v, metav1.UpdateOptions{}); errRecord != nil {
+				log.Errorw("failed to record status change for ApisixRoute",
+					zap.Error(errRecord),
+					zap.String("name", v.Name),
+					zap.String("namespace", v.Namespace),
+				)
+			}
+		}
+	case *v2.ApisixRoute:
+		// set to status
+		if v.Status.Conditions == nil {
+			conditions := make([]metav1.Condition, 0)
+			v.Status.Conditions = conditions
+		}
+		if utils.VerifyGeneration(&v.Status.Conditions, condition) {
+			meta.SetStatusCondition(&v.Status.Conditions, condition)
+			if _, errRecord := apisixClient.ApisixV2().ApisixRoutes(v.Namespace).
+				UpdateStatus(context.TODO(), v, metav1.UpdateOptions{}); errRecord != nil {
+				log.Errorw("failed to record status change for ApisixRoute",
+					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)
+	}
+}
diff --git a/pkg/ingress/apisix_tls.go b/pkg/providers/apisix/apisix_tls.go
similarity index 61%
rename from pkg/ingress/apisix_tls.go
rename to pkg/providers/apisix/apisix_tls.go
index be484edc..d2b55225 100644
--- a/pkg/ingress/apisix_tls.go
+++ b/pkg/providers/apisix/apisix_tls.go
@@ -12,7 +12,7 @@
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 // See the License for the specific language governing permissions and
 // limitations under the License.
-package ingress
+package apisix
 
 import (
 	"context"
@@ -23,37 +23,60 @@ import (
 	"go.uber.org/zap"
 	corev1 "k8s.io/api/core/v1"
 	k8serrors "k8s.io/apimachinery/pkg/api/errors"
+	"k8s.io/apimachinery/pkg/api/meta"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/client-go/tools/cache"
 	"k8s.io/client-go/util/workqueue"
 
 	"github.com/apache/apisix-ingress-controller/pkg/config"
 	"github.com/apache/apisix-ingress-controller/pkg/kube"
+	configv2 "github.com/apache/apisix-ingress-controller/pkg/kube/apisix/apis/config/v2"
+	configv2beta3 "github.com/apache/apisix-ingress-controller/pkg/kube/apisix/apis/config/v2beta3"
 	"github.com/apache/apisix-ingress-controller/pkg/log"
+	"github.com/apache/apisix-ingress-controller/pkg/providers/utils"
 	"github.com/apache/apisix-ingress-controller/pkg/types"
 	v1 "github.com/apache/apisix-ingress-controller/pkg/types/apisix/v1"
 )
 
 type apisixTlsController struct {
-	controller *Controller
-	workqueue  workqueue.RateLimitingInterface
-	workers    int
+	*apisixCommon
+
+	workqueue workqueue.RateLimitingInterface
+	workers   int
+
+	secretInformer    cache.SharedIndexInformer
+	apisixTlsLister   kube.ApisixTlsLister
+	apisixTlsInformer cache.SharedIndexInformer
+
+	// this map enrolls which ApisixTls objects refer to a Kubernetes
+	// Secret object.
+	// type: Map<SecretKey, Map<ApisixTlsKey, ApisixTls>>
+	// SecretKey is `namespace_name`, ApisixTlsKey is kube style meta key: `namespace/name`
+	secretSSLMap *sync.Map
 }
 
-func (c *Controller) newApisixTlsController() *apisixTlsController {
-	ctl := &apisixTlsController{
-		controller: c,
-		workqueue:  workqueue.NewNamedRateLimitingQueue(workqueue.NewItemFastSlowRateLimiter(1*time.Second, 60*time.Second, 5), "ApisixTls"),
-		workers:    1,
+func newApisixTlsController(common *apisixCommon) *apisixTlsController {
+	c := &apisixTlsController{
+		apisixCommon: common,
+		workqueue:    workqueue.NewNamedRateLimitingQueue(workqueue.NewItemFastSlowRateLimiter(1*time.Second, 60*time.Second, 5), "ApisixTls"),
+		workers:      1,
+
+		secretInformer:    common.SecretInformer,
+		apisixTlsLister:   common.ApisixTlsLister,
+		apisixTlsInformer: common.ApisixTlsInformer,
+
+		secretSSLMap: new(sync.Map),
 	}
-	ctl.controller.apisixTlsInformer.AddEventHandler(
+
+	c.apisixTlsInformer.AddEventHandler(
 		cache.ResourceEventHandlerFuncs{
-			AddFunc:    ctl.onAdd,
-			UpdateFunc: ctl.onUpdate,
-			DeleteFunc: ctl.onDelete,
+			AddFunc:    c.onAdd,
+			UpdateFunc: c.onUpdate,
+			DeleteFunc: c.onDelete,
 		},
 	)
-	return ctl
+	return c
 }
 
 func (c *apisixTlsController) run(ctx context.Context) {
@@ -61,7 +84,7 @@ func (c *apisixTlsController) run(ctx context.Context) {
 	defer log.Info("ApisixTls controller exited")
 	defer c.workqueue.ShutDown()
 
-	if ok := cache.WaitForCacheSync(ctx.Done(), c.controller.apisixTlsInformer.HasSynced, c.controller.secretInformer.HasSynced); !ok {
+	if ok := cache.WaitForCacheSync(ctx.Done(), c.apisixTlsInformer.HasSynced, c.secretInformer.HasSynced); !ok {
 		log.Errorf("informers sync failed")
 		return
 	}
@@ -96,9 +119,9 @@ func (c *apisixTlsController) sync(ctx context.Context, ev *types.Event) error {
 	var multiVersionedTls kube.ApisixTls
 	switch event.GroupVersion {
 	case config.ApisixV2beta3:
-		multiVersionedTls, err = c.controller.apisixTlsLister.V2beta3(namespace, name)
+		multiVersionedTls, err = c.apisixTlsLister.V2beta3(namespace, name)
 	case config.ApisixV2:
-		multiVersionedTls, err = c.controller.apisixTlsLister.V2(namespace, name)
+		multiVersionedTls, err = c.apisixTlsLister.V2(namespace, name)
 	default:
 		return fmt.Errorf("unsupported ApisixTls group version %s", event.GroupVersion)
 	}
@@ -135,14 +158,14 @@ func (c *apisixTlsController) sync(ctx context.Context, ev *types.Event) error {
 	switch event.GroupVersion {
 	case config.ApisixV2beta3:
 		tls := multiVersionedTls.V2beta3()
-		ssl, err := c.controller.translator.TranslateSSLV2Beta3(tls)
+		ssl, err := c.translator.TranslateSSLV2Beta3(tls)
 		if err != nil {
 			log.Errorw("failed to translate ApisixTls",
 				zap.Error(err),
 				zap.Any("ApisixTls", tls),
 			)
-			c.controller.recorderEvent(tls, corev1.EventTypeWarning, _resourceSyncAborted, err)
-			c.controller.recordStatus(tls, _resourceSyncAborted, err, metav1.ConditionFalse, tls.GetGeneration())
+			c.RecordEvent(tls, corev1.EventTypeWarning, utils.ResourceSyncAborted, err)
+			c.recordStatus(tls, utils.ResourceSyncAborted, err, metav1.ConditionFalse, tls.GetGeneration())
 			return err
 		}
 		log.Debugw("got SSL object from ApisixTls",
@@ -159,28 +182,28 @@ func (c *apisixTlsController) sync(ctx context.Context, ev *types.Event) error {
 			}
 		}
 
-		if err := c.controller.syncSSL(ctx, ssl, ev.Type); err != nil {
+		if err := c.SyncSSL(ctx, ssl, ev.Type); err != nil {
 			log.Errorw("failed to sync SSL to APISIX",
 				zap.Error(err),
 				zap.Any("ssl", ssl),
 			)
-			c.controller.recorderEvent(tls, corev1.EventTypeWarning, _resourceSyncAborted, err)
-			c.controller.recordStatus(tls, _resourceSyncAborted, err, metav1.ConditionFalse, tls.GetGeneration())
+			c.RecordEvent(tls, corev1.EventTypeWarning, utils.ResourceSyncAborted, err)
+			c.recordStatus(tls, utils.ResourceSyncAborted, err, metav1.ConditionFalse, tls.GetGeneration())
 			return err
 		}
-		c.controller.recorderEvent(tls, corev1.EventTypeNormal, _resourceSynced, nil)
-		c.controller.recordStatus(tls, _resourceSynced, nil, metav1.ConditionTrue, tls.GetGeneration())
+		c.RecordEvent(tls, corev1.EventTypeNormal, utils.ResourceSynced, nil)
+		c.recordStatus(tls, utils.ResourceSynced, nil, metav1.ConditionTrue, tls.GetGeneration())
 		return err
 	case config.ApisixV2:
 		tls := multiVersionedTls.V2()
-		ssl, err := c.controller.translator.TranslateSSLV2(tls)
+		ssl, err := c.translator.TranslateSSLV2(tls)
 		if err != nil {
 			log.Errorw("failed to translate ApisixTls",
 				zap.Error(err),
 				zap.Any("ApisixTls", tls),
 			)
-			c.controller.recorderEvent(tls, corev1.EventTypeWarning, _resourceSyncAborted, err)
-			c.controller.recordStatus(tls, _resourceSyncAborted, err, metav1.ConditionFalse, tls.GetGeneration())
+			c.RecordEvent(tls, corev1.EventTypeWarning, utils.ResourceSyncAborted, err)
+			c.recordStatus(tls, utils.ResourceSyncAborted, err, metav1.ConditionFalse, tls.GetGeneration())
 			return err
 		}
 		log.Debugw("got SSL object from ApisixTls",
@@ -197,17 +220,17 @@ func (c *apisixTlsController) sync(ctx context.Context, ev *types.Event) error {
 			}
 		}
 
-		if err := c.controller.syncSSL(ctx, ssl, ev.Type); err != nil {
+		if err := c.SyncSSL(ctx, ssl, ev.Type); err != nil {
 			log.Errorw("failed to sync SSL to APISIX",
 				zap.Error(err),
 				zap.Any("ssl", ssl),
 			)
-			c.controller.recorderEvent(tls, corev1.EventTypeWarning, _resourceSyncAborted, err)
-			c.controller.recordStatus(tls, _resourceSyncAborted, err, metav1.ConditionFalse, tls.GetGeneration())
+			c.RecordEvent(tls, corev1.EventTypeWarning, utils.ResourceSyncAborted, err)
+			c.recordStatus(tls, utils.ResourceSyncAborted, err, metav1.ConditionFalse, tls.GetGeneration())
 			return err
 		}
-		c.controller.recorderEvent(tls, corev1.EventTypeNormal, _resourceSynced, nil)
-		c.controller.recordStatus(tls, _resourceSynced, nil, metav1.ConditionTrue, tls.GetGeneration())
+		c.RecordEvent(tls, corev1.EventTypeNormal, utils.ResourceSynced, nil)
+		c.recordStatus(tls, utils.ResourceSynced, nil, metav1.ConditionTrue, tls.GetGeneration())
 		return err
 	default:
 		return fmt.Errorf("unsupported ApisixTls group version %s", event.GroupVersion)
@@ -215,27 +238,27 @@ func (c *apisixTlsController) sync(ctx context.Context, ev *types.Event) error {
 }
 
 func (c *apisixTlsController) syncSecretSSL(secretKey string, apisixTlsKey string, ssl *v1.Ssl, event types.EventType) {
-	if ssls, ok := c.controller.secretSSLMap.Load(secretKey); ok {
+	if ssls, ok := c.secretSSLMap.Load(secretKey); ok {
 		sslMap := ssls.(*sync.Map)
 		switch event {
 		case types.EventDelete:
 			sslMap.Delete(apisixTlsKey)
-			c.controller.secretSSLMap.Store(secretKey, sslMap)
+			c.secretSSLMap.Store(secretKey, sslMap)
 		default:
 			sslMap.Store(apisixTlsKey, ssl)
-			c.controller.secretSSLMap.Store(secretKey, sslMap)
+			c.secretSSLMap.Store(secretKey, sslMap)
 		}
 	} else if event != types.EventDelete {
 		sslMap := new(sync.Map)
 		sslMap.Store(apisixTlsKey, ssl)
-		c.controller.secretSSLMap.Store(secretKey, sslMap)
+		c.secretSSLMap.Store(secretKey, sslMap)
 	}
 }
 
 func (c *apisixTlsController) handleSyncErr(obj interface{}, err error) {
 	if err == nil {
 		c.workqueue.Forget(obj)
-		c.controller.MetricsCollector.IncrSyncOperation("TLS", "success")
+		c.MetricsCollector.IncrSyncOperation("TLS", "success")
 		return
 	}
 
@@ -255,7 +278,7 @@ func (c *apisixTlsController) handleSyncErr(obj interface{}, err error) {
 		zap.Error(err),
 	)
 	c.workqueue.AddRateLimited(obj)
-	c.controller.MetricsCollector.IncrSyncOperation("TLS", "failure")
+	c.MetricsCollector.IncrSyncOperation("TLS", "failure")
 }
 
 func (c *apisixTlsController) onAdd(obj interface{}) {
@@ -269,7 +292,7 @@ func (c *apisixTlsController) onAdd(obj interface{}) {
 		log.Errorf("found ApisixTls object with bad namespace/name: %s, ignore it", err)
 		return
 	}
-	if !c.controller.isWatchingNamespace(key) {
+	if !c.namespaceProvider.IsWatchingNamespace(key) {
 		return
 	}
 	log.Debugw("ApisixTls add event arrived",
@@ -283,7 +306,7 @@ func (c *apisixTlsController) onAdd(obj interface{}) {
 		},
 	})
 
-	c.controller.MetricsCollector.IncrEvents("TLS", "add")
+	c.MetricsCollector.IncrEvents("TLS", "add")
 }
 
 func (c *apisixTlsController) onUpdate(prev, curr interface{}) {
@@ -305,7 +328,7 @@ func (c *apisixTlsController) onUpdate(prev, curr interface{}) {
 		log.Errorf("found ApisixTls object with bad namespace/name: %s, ignore it", err)
 		return
 	}
-	if !c.controller.isWatchingNamespace(key) {
+	if !c.namespaceProvider.IsWatchingNamespace(key) {
 		return
 	}
 	log.Debugw("ApisixTls update event arrived",
@@ -321,7 +344,7 @@ func (c *apisixTlsController) onUpdate(prev, curr interface{}) {
 		},
 	})
 
-	c.controller.MetricsCollector.IncrEvents("TLS", "update")
+	c.MetricsCollector.IncrEvents("TLS", "update")
 }
 
 func (c *apisixTlsController) onDelete(obj interface{}) {
@@ -342,7 +365,7 @@ func (c *apisixTlsController) onDelete(obj interface{}) {
 		log.Errorf("found ApisixTls resource with bad meta namespace key: %s", err)
 		return
 	}
-	if !c.controller.isWatchingNamespace(key) {
+	if !c.namespaceProvider.IsWatchingNamespace(key) {
 		return
 	}
 	log.Debugw("ApisixTls delete event arrived",
@@ -357,18 +380,18 @@ func (c *apisixTlsController) onDelete(obj interface{}) {
 		Tombstone: tls,
 	})
 
-	c.controller.MetricsCollector.IncrEvents("TLS", "delete")
+	c.MetricsCollector.IncrEvents("TLS", "delete")
 }
 
 func (c *apisixTlsController) ResourceSync() {
-	objs := c.controller.apisixTlsInformer.GetIndexer().List()
+	objs := c.apisixTlsInformer.GetIndexer().List()
 	for _, obj := range objs {
 		key, err := cache.MetaNamespaceKeyFunc(obj)
 		if err != nil {
 			log.Errorw("ApisixTls sync failed, found ApisixTls object with bad namespace/name ignore it", zap.String("error", err.Error()))
 			continue
 		}
-		if !c.controller.isWatchingNamespace(key) {
+		if !c.namespaceProvider.IsWatchingNamespace(key) {
 			continue
 		}
 		tls, err := kube.NewApisixTls(obj)
@@ -385,3 +408,64 @@ func (c *apisixTlsController) ResourceSync() {
 		})
 	}
 }
+
+// recordStatus record resources status
+func (c *apisixTlsController) recordStatus(at interface{}, reason string, err error, status metav1.ConditionStatus, generation int64) {
+	// build condition
+	message := utils.CommonSuccessMessage
+	if err != nil {
+		message = err.Error()
+	}
+	condition := metav1.Condition{
+		Type:               utils.ConditionType,
+		Reason:             reason,
+		Status:             status,
+		Message:            message,
+		ObservedGeneration: generation,
+	}
+	apisixClient := c.KubeClient.APISIXClient
+
+	if kubeObj, ok := at.(runtime.Object); ok {
+		at = kubeObj.DeepCopyObject()
+	}
+
+	switch v := at.(type) {
+	case *configv2beta3.ApisixTls:
+		// set to status
+		if v.Status.Conditions == nil {
+			conditions := make([]metav1.Condition, 0)
+			v.Status.Conditions = conditions
+		}
+		if utils.VerifyGeneration(&v.Status.Conditions, condition) {
+			meta.SetStatusCondition(&v.Status.Conditions, condition)
+			if _, errRecord := apisixClient.ApisixV2beta3().ApisixTlses(v.Namespace).
+				UpdateStatus(context.TODO(), v, metav1.UpdateOptions{}); errRecord != nil {
+				log.Errorw("failed to record status change for ApisixTls",
+					zap.Error(errRecord),
+					zap.String("name", v.Name),
+					zap.String("namespace", v.Namespace),
+				)
+			}
+		}
+	case *configv2.ApisixTls:
+		// set to status
+		if v.Status.Conditions == nil {
+			conditions := make([]metav1.Condition, 0)
+			v.Status.Conditions = conditions
+		}
+		if utils.VerifyGeneration(&v.Status.Conditions, condition) {
+			meta.SetStatusCondition(&v.Status.Conditions, condition)
+			if _, errRecord := apisixClient.ApisixV2().ApisixTlses(v.Namespace).
+				UpdateStatus(context.TODO(), v, metav1.UpdateOptions{}); errRecord != nil {
+				log.Errorw("failed to record status change for ApisixTls",
+					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)
+	}
+}
diff --git a/pkg/ingress/apisix_upstream.go b/pkg/providers/apisix/apisix_upstream.go
similarity index 64%
rename from pkg/ingress/apisix_upstream.go
rename to pkg/providers/apisix/apisix_upstream.go
index 526ba977..ff9d0678 100644
--- a/pkg/ingress/apisix_upstream.go
+++ b/pkg/providers/apisix/apisix_upstream.go
@@ -12,7 +12,7 @@
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 // See the License for the specific language governing permissions and
 // limitations under the License.
-package ingress
+package apisix
 
 import (
 	"context"
@@ -22,7 +22,10 @@ import (
 	"go.uber.org/zap"
 	corev1 "k8s.io/api/core/v1"
 	k8serrors "k8s.io/apimachinery/pkg/api/errors"
+	"k8s.io/apimachinery/pkg/api/meta"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+	listerscorev1 "k8s.io/client-go/listers/core/v1"
 	"k8s.io/client-go/tools/cache"
 	"k8s.io/client-go/util/workqueue"
 
@@ -32,30 +35,43 @@ import (
 	configv2 "github.com/apache/apisix-ingress-controller/pkg/kube/apisix/apis/config/v2"
 	configv2beta3 "github.com/apache/apisix-ingress-controller/pkg/kube/apisix/apis/config/v2beta3"
 	"github.com/apache/apisix-ingress-controller/pkg/log"
+	"github.com/apache/apisix-ingress-controller/pkg/providers/utils"
 	"github.com/apache/apisix-ingress-controller/pkg/types"
 	apisixv1 "github.com/apache/apisix-ingress-controller/pkg/types/apisix/v1"
 )
 
 type apisixUpstreamController struct {
-	controller *Controller
-	workqueue  workqueue.RateLimitingInterface
-	workers    int
+	*apisixCommon
+
+	workqueue workqueue.RateLimitingInterface
+	workers   int
+
+	svcInformer            cache.SharedIndexInformer
+	svcLister              listerscorev1.ServiceLister
+	apisixUpstreamInformer cache.SharedIndexInformer
+	apisixUpstreamLister   kube.ApisixUpstreamLister
 }
 
-func (c *Controller) newApisixUpstreamController() *apisixUpstreamController {
-	ctl := &apisixUpstreamController{
-		controller: c,
-		workqueue:  workqueue.NewNamedRateLimitingQueue(workqueue.NewItemFastSlowRateLimiter(1*time.Second, 60*time.Second, 5), "ApisixUpstream"),
-		workers:    1,
+func newApisixUpstreamController(common *apisixCommon) *apisixUpstreamController {
+	c := &apisixUpstreamController{
+		apisixCommon: common,
+		workqueue:    workqueue.NewNamedRateLimitingQueue(workqueue.NewItemFastSlowRateLimiter(1*time.Second, 60*time.Second, 5), "ApisixUpstream"),
+		workers:      1,
+
+		svcInformer:            common.SvcInformer,
+		svcLister:              common.SvcLister,
+		apisixUpstreamLister:   common.ApisixUpstreamLister,
+		apisixUpstreamInformer: common.ApisixUpstreamInformer,
 	}
-	ctl.controller.apisixUpstreamInformer.AddEventHandler(
+
+	c.apisixUpstreamInformer.AddEventHandler(
 		cache.ResourceEventHandlerFuncs{
-			AddFunc:    ctl.onAdd,
-			UpdateFunc: ctl.onUpdate,
-			DeleteFunc: ctl.onDelete,
+			AddFunc:    c.onAdd,
+			UpdateFunc: c.onUpdate,
+			DeleteFunc: c.onDelete,
 		},
 	)
-	return ctl
+	return c
 }
 
 func (c *apisixUpstreamController) run(ctx context.Context) {
@@ -63,7 +79,7 @@ func (c *apisixUpstreamController) run(ctx context.Context) {
 	defer log.Info("ApisixUpstream controller exited")
 	defer c.workqueue.ShutDown()
 
-	if ok := cache.WaitForCacheSync(ctx.Done(), c.controller.apisixUpstreamInformer.HasSynced, c.controller.svcInformer.HasSynced); !ok {
+	if ok := cache.WaitForCacheSync(ctx.Done(), c.apisixUpstreamInformer.HasSynced, c.svcInformer.HasSynced); !ok {
 		log.Error("cache sync failed")
 		return
 	}
@@ -102,9 +118,9 @@ func (c *apisixUpstreamController) sync(ctx context.Context, ev *types.Event) er
 	var multiVersioned kube.ApisixUpstream
 	switch event.GroupVersion {
 	case config.ApisixV2beta3:
-		multiVersioned, err = c.controller.apisixUpstreamLister.V2beta3(namespace, name)
+		multiVersioned, err = c.apisixUpstreamLister.V2beta3(namespace, name)
 	case config.ApisixV2:
-		multiVersioned, err = c.controller.apisixUpstreamLister.V2(namespace, name)
+		multiVersioned, err = c.apisixUpstreamLister.V2(namespace, name)
 	default:
 		return fmt.Errorf("unsupported ApisixUpstream group version %s", event.GroupVersion)
 	}
@@ -150,11 +166,11 @@ func (c *apisixUpstreamController) sync(ctx context.Context, ev *types.Event) er
 			}
 		}
 
-		svc, err := c.controller.svcLister.Services(namespace).Get(name)
+		svc, err := c.svcLister.Services(namespace).Get(name)
 		if err != nil {
 			log.Errorf("failed to get service %s: %s", key, err)
-			c.controller.recorderEvent(au, corev1.EventTypeWarning, _resourceSyncAborted, err)
-			c.controller.recordStatus(au, _resourceSyncAborted, err, metav1.ConditionFalse, au.GetGeneration())
+			c.RecordEvent(au, corev1.EventTypeWarning, utils.ResourceSyncAborted, err)
+			c.recordStatus(au, utils.ResourceSyncAborted, err, metav1.ConditionFalse, au.GetGeneration())
 			return err
 		}
 
@@ -163,19 +179,19 @@ func (c *apisixUpstreamController) sync(ctx context.Context, ev *types.Event) er
 		if au.Spec != nil && len(au.Spec.Subsets) > 0 {
 			subsets = append(subsets, au.Spec.Subsets...)
 		}
-		clusterName := c.controller.cfg.APISIX.DefaultClusterName
+		clusterName := c.Config.APISIX.DefaultClusterName
 		for _, port := range svc.Spec.Ports {
 			for _, subset := range subsets {
 				upsName := apisixv1.ComposeUpstreamName(namespace, name, subset.Name, port.Port)
 				// TODO: multiple cluster
-				ups, err := c.controller.apisix.Cluster(clusterName).Upstream().Get(ctx, upsName)
+				ups, err := c.APISIX.Cluster(clusterName).Upstream().Get(ctx, upsName)
 				if err != nil {
 					if err == apisixcache.ErrNotFound {
 						continue
 					}
 					log.Errorf("failed to get upstream %s: %s", upsName, err)
-					c.controller.recorderEvent(au, corev1.EventTypeWarning, _resourceSyncAborted, err)
-					c.controller.recordStatus(au, _resourceSyncAborted, err, metav1.ConditionFalse, au.GetGeneration())
+					c.RecordEvent(au, corev1.EventTypeWarning, utils.ResourceSyncAborted, err)
+					c.recordStatus(au, utils.ResourceSyncAborted, err, metav1.ConditionFalse, au.GetGeneration())
 					return err
 				}
 				var newUps *apisixv1.Upstream
@@ -185,14 +201,14 @@ func (c *apisixUpstreamController) sync(ctx context.Context, ev *types.Event) er
 						cfg = &au.Spec.ApisixUpstreamConfig
 					}
 					// FIXME Same ApisixUpstreamConfig might be translated multiple times.
-					newUps, err = c.controller.translator.TranslateUpstreamConfigV2beta3(cfg)
+					newUps, err = c.translator.TranslateUpstreamConfigV2beta3(cfg)
 					if err != nil {
 						log.Errorw("found malformed ApisixUpstream",
 							zap.Any("object", au),
 							zap.Error(err),
 						)
-						c.controller.recorderEvent(au, corev1.EventTypeWarning, _resourceSyncAborted, err)
-						c.controller.recordStatus(au, _resourceSyncAborted, err, metav1.ConditionFalse, au.GetGeneration())
+						c.RecordEvent(au, corev1.EventTypeWarning, utils.ResourceSyncAborted, err)
+						c.recordStatus(au, utils.ResourceSyncAborted, err, metav1.ConditionFalse, au.GetGeneration())
 						return err
 					}
 				} else {
@@ -206,22 +222,22 @@ func (c *apisixUpstreamController) sync(ctx context.Context, ev *types.Event) er
 					zap.Any("upstream", newUps),
 					zap.Any("ApisixUpstream", au),
 				)
-				if _, err := c.controller.apisix.Cluster(clusterName).Upstream().Update(ctx, newUps); err != nil {
+				if _, err := c.APISIX.Cluster(clusterName).Upstream().Update(ctx, newUps); err != nil {
 					log.Errorw("failed to update upstream",
 						zap.Error(err),
 						zap.Any("upstream", newUps),
 						zap.Any("ApisixUpstream", au),
 						zap.String("cluster", clusterName),
 					)
-					c.controller.recorderEvent(au, corev1.EventTypeWarning, _resourceSyncAborted, err)
-					c.controller.recordStatus(au, _resourceSyncAborted, err, metav1.ConditionFalse, au.GetGeneration())
+					c.RecordEvent(au, corev1.EventTypeWarning, utils.ResourceSyncAborted, err)
+					c.recordStatus(au, utils.ResourceSyncAborted, err, metav1.ConditionFalse, au.GetGeneration())
 					return err
 				}
 			}
 		}
 		if ev.Type != types.EventDelete {
-			c.controller.recorderEvent(au, corev1.EventTypeNormal, _resourceSynced, nil)
-			c.controller.recordStatus(au, _resourceSynced, nil, metav1.ConditionTrue, au.GetGeneration())
+			c.RecordEvent(au, corev1.EventTypeNormal, utils.ResourceSynced, nil)
+			c.recordStatus(au, utils.ResourceSynced, nil, metav1.ConditionTrue, au.GetGeneration())
 		}
 	case config.ApisixV2:
 		au := multiVersioned.V2()
@@ -234,11 +250,11 @@ func (c *apisixUpstreamController) sync(ctx context.Context, ev *types.Event) er
 			}
 		}
 
-		svc, err := c.controller.svcLister.Services(namespace).Get(name)
+		svc, err := c.svcLister.Services(namespace).Get(name)
 		if err != nil {
 			log.Errorf("failed to get service %s: %s", key, err)
-			c.controller.recorderEvent(au, corev1.EventTypeWarning, _resourceSyncAborted, err)
-			c.controller.recordStatus(au, _resourceSyncAborted, err, metav1.ConditionFalse, au.GetGeneration())
+			c.RecordEvent(au, corev1.EventTypeWarning, utils.ResourceSyncAborted, err)
+			c.recordStatus(au, utils.ResourceSyncAborted, err, metav1.ConditionFalse, au.GetGeneration())
 			return err
 		}
 
@@ -247,19 +263,19 @@ func (c *apisixUpstreamController) sync(ctx context.Context, ev *types.Event) er
 		if au.Spec != nil && len(au.Spec.Subsets) > 0 {
 			subsets = append(subsets, au.Spec.Subsets...)
 		}
-		clusterName := c.controller.cfg.APISIX.DefaultClusterName
+		clusterName := c.Config.APISIX.DefaultClusterName
 		for _, port := range svc.Spec.Ports {
 			for _, subset := range subsets {
 				upsName := apisixv1.ComposeUpstreamName(namespace, name, subset.Name, port.Port)
 				// TODO: multiple cluster
-				ups, err := c.controller.apisix.Cluster(clusterName).Upstream().Get(ctx, upsName)
+				ups, err := c.APISIX.Cluster(clusterName).Upstream().Get(ctx, upsName)
 				if err != nil {
 					if err == apisixcache.ErrNotFound {
 						continue
 					}
 					log.Errorf("failed to get upstream %s: %s", upsName, err)
-					c.controller.recorderEvent(au, corev1.EventTypeWarning, _resourceSyncAborted, err)
-					c.controller.recordStatus(au, _resourceSyncAborted, err, metav1.ConditionFalse, au.GetGeneration())
+					c.RecordEvent(au, corev1.EventTypeWarning, utils.ResourceSyncAborted, err)
+					c.recordStatus(au, utils.ResourceSyncAborted, err, metav1.ConditionFalse, au.GetGeneration())
 					return err
 				}
 				var newUps *apisixv1.Upstream
@@ -269,14 +285,14 @@ func (c *apisixUpstreamController) sync(ctx context.Context, ev *types.Event) er
 						cfg = &au.Spec.ApisixUpstreamConfig
 					}
 					// FIXME Same ApisixUpstreamConfig might be translated multiple times.
-					newUps, err = c.controller.translator.TranslateUpstreamConfigV2(cfg)
+					newUps, err = c.translator.TranslateUpstreamConfigV2(cfg)
 					if err != nil {
 						log.Errorw("found malformed ApisixUpstream",
 							zap.Any("object", au),
 							zap.Error(err),
 						)
-						c.controller.recorderEvent(au, corev1.EventTypeWarning, _resourceSyncAborted, err)
-						c.controller.recordStatus(au, _resourceSyncAborted, err, metav1.ConditionFalse, au.GetGeneration())
+						c.RecordEvent(au, corev1.EventTypeWarning, utils.ResourceSyncAborted, err)
+						c.recordStatus(au, utils.ResourceSyncAborted, err, metav1.ConditionFalse, au.GetGeneration())
 						return err
 					}
 				} else {
@@ -290,22 +306,22 @@ func (c *apisixUpstreamController) sync(ctx context.Context, ev *types.Event) er
 					zap.Any("upstream", newUps),
 					zap.Any("ApisixUpstream", au),
 				)
-				if _, err := c.controller.apisix.Cluster(clusterName).Upstream().Update(ctx, newUps); err != nil {
+				if _, err := c.APISIX.Cluster(clusterName).Upstream().Update(ctx, newUps); err != nil {
 					log.Errorw("failed to update upstream",
 						zap.Error(err),
 						zap.Any("upstream", newUps),
 						zap.Any("ApisixUpstream", au),
 						zap.String("cluster", clusterName),
 					)
-					c.controller.recorderEvent(au, corev1.EventTypeWarning, _resourceSyncAborted, err)
-					c.controller.recordStatus(au, _resourceSyncAborted, err, metav1.ConditionFalse, au.GetGeneration())
+					c.RecordEvent(au, corev1.EventTypeWarning, utils.ResourceSyncAborted, err)
+					c.recordStatus(au, utils.ResourceSyncAborted, err, metav1.ConditionFalse, au.GetGeneration())
 					return err
 				}
 			}
 		}
 		if ev.Type != types.EventDelete {
-			c.controller.recorderEvent(au, corev1.EventTypeNormal, _resourceSynced, nil)
-			c.controller.recordStatus(au, _resourceSynced, nil, metav1.ConditionTrue, au.GetGeneration())
+			c.RecordEvent(au, corev1.EventTypeNormal, utils.ResourceSynced, nil)
+			c.recordStatus(au, utils.ResourceSynced, nil, metav1.ConditionTrue, au.GetGeneration())
 		}
 	}
 
@@ -315,7 +331,7 @@ func (c *apisixUpstreamController) sync(ctx context.Context, ev *types.Event) er
 func (c *apisixUpstreamController) handleSyncErr(obj interface{}, err error) {
 	if err == nil {
 		c.workqueue.Forget(obj)
-		c.controller.MetricsCollector.IncrSyncOperation("upstream", "success")
+		c.MetricsCollector.IncrSyncOperation("upstream", "success")
 		return
 	}
 
@@ -333,7 +349,7 @@ func (c *apisixUpstreamController) handleSyncErr(obj interface{}, err error) {
 		zap.Error(err),
 	)
 	c.workqueue.AddRateLimited(obj)
-	c.controller.MetricsCollector.IncrSyncOperation("upstream", "failure")
+	c.MetricsCollector.IncrSyncOperation("upstream", "failure")
 }
 
 func (c *apisixUpstreamController) onAdd(obj interface{}) {
@@ -348,7 +364,7 @@ func (c *apisixUpstreamController) onAdd(obj interface{}) {
 		log.Errorf("found ApisixUpstream resource with bad meta namespace key: %s", err)
 		return
 	}
-	if !c.controller.isWatchingNamespace(key) {
+	if !c.namespaceProvider.IsWatchingNamespace(key) {
 		return
 	}
 	log.Debugw("ApisixUpstream add event arrived",
@@ -362,7 +378,7 @@ func (c *apisixUpstreamController) onAdd(obj interface{}) {
 		},
 	})
 
-	c.controller.MetricsCollector.IncrEvents("upstream", "add")
+	c.MetricsCollector.IncrEvents("upstream", "add")
 }
 
 func (c *apisixUpstreamController) onUpdate(oldObj, newObj interface{}) {
@@ -384,7 +400,7 @@ func (c *apisixUpstreamController) onUpdate(oldObj, newObj interface{}) {
 		log.Errorf("found ApisixUpstream resource with bad meta namespace key: %s", err)
 		return
 	}
-	if !c.controller.isWatchingNamespace(key) {
+	if !c.namespaceProvider.IsWatchingNamespace(key) {
 		return
 	}
 	log.Debugw("ApisixUpstream update event arrived",
@@ -401,7 +417,7 @@ func (c *apisixUpstreamController) onUpdate(oldObj, newObj interface{}) {
 		},
 	})
 
-	c.controller.MetricsCollector.IncrEvents("upstream", "update")
+	c.MetricsCollector.IncrEvents("upstream", "update")
 }
 
 func (c *apisixUpstreamController) onDelete(obj interface{}) {
@@ -423,7 +439,7 @@ func (c *apisixUpstreamController) onDelete(obj interface{}) {
 		log.Errorf("found ApisixUpstream resource with bad meta namespace key: %s", err)
 		return
 	}
-	if !c.controller.isWatchingNamespace(key) {
+	if !c.namespaceProvider.IsWatchingNamespace(key) {
 		return
 	}
 	log.Debugw("ApisixUpstream delete event arrived",
@@ -438,18 +454,18 @@ func (c *apisixUpstreamController) onDelete(obj interface{}) {
 		Tombstone: au,
 	})
 
-	c.controller.MetricsCollector.IncrEvents("upstream", "delete")
+	c.MetricsCollector.IncrEvents("upstream", "delete")
 }
 
 func (c *apisixUpstreamController) ResourceSync() {
-	objs := c.controller.apisixUpstreamInformer.GetIndexer().List()
+	objs := c.apisixUpstreamInformer.GetIndexer().List()
 	for _, obj := range objs {
 		key, err := cache.MetaNamespaceKeyFunc(obj)
 		if err != nil {
 			log.Errorw("ApisixUpstream sync failed, found ApisixUpstream resource with bad meta namespace key", zap.String("error", err.Error()))
 			continue
 		}
-		if !c.controller.isWatchingNamespace(key) {
+		if !c.namespaceProvider.IsWatchingNamespace(key) {
 			continue
 		}
 		au, err := kube.NewApisixUpstream(obj)
@@ -466,3 +482,65 @@ func (c *apisixUpstreamController) ResourceSync() {
 		})
 	}
 }
+
+// recordStatus record resources status
+func (c *apisixUpstreamController) recordStatus(at interface{}, reason string, err error, status metav1.ConditionStatus, generation int64) {
+	// build condition
+	message := utils.CommonSuccessMessage
+	if err != nil {
+		message = err.Error()
+	}
+	condition := metav1.Condition{
+		Type:               utils.ConditionType,
+		Reason:             reason,
+		Status:             status,
+		Message:            message,
+		ObservedGeneration: generation,
+	}
+	apisixClient := c.KubeClient.APISIXClient
+
+	if kubeObj, ok := at.(runtime.Object); ok {
+		at = kubeObj.DeepCopyObject()
+	}
+
+	switch v := at.(type) {
+	case *configv2beta3.ApisixUpstream:
+		// set to status
+		if v.Status.Conditions == nil {
+			conditions := make([]metav1.Condition, 0)
+			v.Status.Conditions = conditions
+		}
+		if utils.VerifyGeneration(&v.Status.Conditions, condition) {
+			meta.SetStatusCondition(&v.Status.Conditions, condition)
+			if _, errRecord := apisixClient.ApisixV2beta3().ApisixUpstreams(v.Namespace).
+				UpdateStatus(context.TODO(), v, metav1.UpdateOptions{}); errRecord != nil {
+				log.Errorw("failed to record status change for ApisixUpstream",
+					zap.Error(errRecord),
+					zap.String("name", v.Name),
+					zap.String("namespace", v.Namespace),
+				)
+			}
+		}
+
+	case *configv2.ApisixUpstream:
+		// set to status
+		if v.Status.Conditions == nil {
+			conditions := make([]metav1.Condition, 0)
+			v.Status.Conditions = conditions
+		}
+		if utils.VerifyGeneration(&v.Status.Conditions, condition) {
+			meta.SetStatusCondition(&v.Status.Conditions, condition)
+			if _, errRecord := apisixClient.ApisixV2().ApisixUpstreams(v.Namespace).
+				UpdateStatus(context.TODO(), v, metav1.UpdateOptions{}); errRecord != nil {
+				log.Errorw("failed to record status change for ApisixUpstream",
+					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)
+	}
+}
diff --git a/pkg/providers/apisix/provider.go b/pkg/providers/apisix/provider.go
new file mode 100644
index 00000000..21baa66d
--- /dev/null
+++ b/pkg/providers/apisix/provider.go
@@ -0,0 +1,202 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+package apisix
+
+import (
+	"context"
+	"fmt"
+	"sync"
+
+	"k8s.io/client-go/tools/cache"
+
+	"github.com/apache/apisix-ingress-controller/pkg/config"
+	"github.com/apache/apisix-ingress-controller/pkg/kube"
+	apisixtranslation "github.com/apache/apisix-ingress-controller/pkg/providers/apisix/translation"
+	"github.com/apache/apisix-ingress-controller/pkg/providers/k8s/namespace"
+	"github.com/apache/apisix-ingress-controller/pkg/providers/translation"
+	providertypes "github.com/apache/apisix-ingress-controller/pkg/providers/types"
+	"github.com/apache/apisix-ingress-controller/pkg/providers/utils"
+)
+
+const (
+	ProviderName = "APISIX"
+)
+
+type apisixCommon struct {
+	*providertypes.Common
+
+	namespaceProvider namespace.WatchingNamespaceProvider
+	translator        apisixtranslation.ApisixTranslator
+}
+
+var _ Provider = (*apisixProvider)(nil)
+
+type Provider interface {
+	providertypes.Provider
+
+	Init(ctx context.Context) error
+	ResourceSync()
+
+	GetSslFromSecretKey(string) *sync.Map
+}
+
+type apisixProvider struct {
+	name              string
+	common            *providertypes.Common
+	namespaceProvider namespace.WatchingNamespaceProvider
+
+	apisixTranslator              apisixtranslation.ApisixTranslator
+	apisixUpstreamController      *apisixUpstreamController
+	apisixRouteController         *apisixRouteController
+	apisixTlsController           *apisixTlsController
+	apisixClusterConfigController *apisixClusterConfigController
+	apisixConsumerController      *apisixConsumerController
+	apisixPluginConfigController  *apisixPluginConfigController
+
+	apisixRouteInformer         cache.SharedIndexInformer
+	apisixClusterConfigInformer cache.SharedIndexInformer
+	apisixConsumerInformer      cache.SharedIndexInformer
+	apisixPluginConfigInformer  cache.SharedIndexInformer
+}
+
+func NewProvider(common *providertypes.Common, namespaceProvider namespace.WatchingNamespaceProvider,
+	translator translation.Translator) (Provider, apisixtranslation.ApisixTranslator, error) {
+	p := &apisixProvider{
+		name:              ProviderName,
+		common:            common,
+		namespaceProvider: namespaceProvider,
+	}
+
+	apisixFactory := common.KubeClient.NewAPISIXSharedIndexInformerFactory()
+
+	p.apisixTranslator = apisixtranslation.NewApisixTranslator(&apisixtranslation.TranslatorOptions{
+		Apisix:        common.APISIX,
+		ClusterName:   common.Config.APISIX.DefaultClusterName,
+		ServiceLister: common.SvcLister,
+		SecretLister:  common.SecretLister,
+	}, translator)
+	c := &apisixCommon{
+		Common:            common,
+		namespaceProvider: namespaceProvider,
+		translator:        p.apisixTranslator,
+	}
+
+	switch c.Config.Kubernetes.APIVersion {
+	case config.ApisixV2beta3:
+		p.apisixRouteInformer = apisixFactory.Apisix().V2beta3().ApisixRoutes().Informer()
+		p.apisixClusterConfigInformer = apisixFactory.Apisix().V2beta3().ApisixClusterConfigs().Informer()
+		p.apisixConsumerInformer = apisixFactory.Apisix().V2beta3().ApisixConsumers().Informer()
+		p.apisixPluginConfigInformer = apisixFactory.Apisix().V2beta3().ApisixPluginConfigs().Informer()
+
+	case config.ApisixV2:
+		p.apisixRouteInformer = apisixFactory.Apisix().V2().ApisixRoutes().Informer()
+		p.apisixClusterConfigInformer = apisixFactory.Apisix().V2().ApisixClusterConfigs().Informer()
+		p.apisixConsumerInformer = apisixFactory.Apisix().V2().ApisixConsumers().Informer()
+		p.apisixPluginConfigInformer = apisixFactory.Apisix().V2().ApisixPluginConfigs().Informer()
+	default:
+		panic(fmt.Errorf("unsupported API version %v", c.Config.Kubernetes.APIVersion))
+	}
+
+	apisixRouteLister := kube.NewApisixRouteLister(
+		apisixFactory.Apisix().V2beta2().ApisixRoutes().Lister(),
+		apisixFactory.Apisix().V2beta3().ApisixRoutes().Lister(),
+		apisixFactory.Apisix().V2().ApisixRoutes().Lister(),
+	)
+	apisixClusterConfigLister := kube.NewApisixClusterConfigLister(
+		apisixFactory.Apisix().V2beta3().ApisixClusterConfigs().Lister(),
+		apisixFactory.Apisix().V2().ApisixClusterConfigs().Lister(),
+	)
+	apisixConsumerLister := kube.NewApisixConsumerLister(
+		apisixFactory.Apisix().V2beta3().ApisixConsumers().Lister(),
+		apisixFactory.Apisix().V2().ApisixConsumers().Lister(),
+	)
+	apisixPluginConfigLister := kube.NewApisixPluginConfigLister(
+		apisixFactory.Apisix().V2beta3().ApisixPluginConfigs().Lister(),
+		apisixFactory.Apisix().V2().ApisixPluginConfigs().Lister(),
+	)
+
+	p.apisixUpstreamController = newApisixUpstreamController(c)
+	p.apisixRouteController = newApisixRouteController(c, p.apisixRouteInformer, apisixRouteLister)
+	p.apisixTlsController = newApisixTlsController(c)
+	p.apisixClusterConfigController = newApisixClusterConfigController(c, p.apisixClusterConfigInformer, apisixClusterConfigLister)
+	p.apisixConsumerController = newApisixConsumerController(c, p.apisixConsumerInformer, apisixConsumerLister)
+	p.apisixPluginConfigController = newApisixPluginConfigController(c, p.apisixPluginConfigInformer, apisixPluginConfigLister)
+
+	return p, p.apisixTranslator, nil
+}
+
+func (p *apisixProvider) Run(ctx context.Context) {
+	e := utils.ParallelExecutor{}
+
+	e.Add(func() {
+		p.apisixRouteInformer.Run(ctx.Done())
+	})
+	e.Add(func() {
+		p.apisixClusterConfigInformer.Run(ctx.Done())
+	})
+	e.Add(func() {
+		p.apisixConsumerInformer.Run(ctx.Done())
+	})
+	e.Add(func() {
+		p.apisixPluginConfigInformer.Run(ctx.Done())
+	})
+
+	e.Add(func() {
+		p.apisixUpstreamController.run(ctx)
+	})
+	e.Add(func() {
+		p.apisixRouteController.run(ctx)
+	})
+	e.Add(func() {
+		p.apisixTlsController.run(ctx)
+	})
+	e.Add(func() {
+		p.apisixClusterConfigController.run(ctx)
+	})
+	e.Add(func() {
+		p.apisixConsumerController.run(ctx)
+	})
+	e.Add(func() {
+		p.apisixPluginConfigController.run(ctx)
+	})
+
+	e.Wait()
+}
+
+func (p *apisixProvider) ResourceSync() {
+	e := utils.ParallelExecutor{}
+
+	e.Add(p.apisixUpstreamController.ResourceSync)
+	e.Add(p.apisixRouteController.ResourceSync)
+	e.Add(p.apisixTlsController.ResourceSync)
+	e.Add(p.apisixClusterConfigController.ResourceSync)
+	e.Add(p.apisixConsumerController.ResourceSync)
+	e.Add(p.apisixPluginConfigController.ResourceSync)
+
+	e.Wait()
+}
+
+func (p *apisixProvider) GetSslFromSecretKey(secretMapKey string) *sync.Map {
+	ssls, ok := p.apisixTlsController.secretSSLMap.Load(secretMapKey)
+	if !ok {
+		// This secret is not concerned.
+		return nil
+	}
+	sslMap := ssls.(*sync.Map)
+	return sslMap
+}
diff --git a/pkg/ingress/compare.go b/pkg/providers/apisix/provider_init.go
similarity index 70%
rename from pkg/ingress/compare.go
rename to pkg/providers/apisix/provider_init.go
index e9117c68..bb6feef3 100644
--- a/pkg/ingress/compare.go
+++ b/pkg/providers/apisix/provider_init.go
@@ -12,7 +12,7 @@
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 // See the License for the specific language governing permissions and
 // limitations under the License.
-package ingress
+package apisix
 
 import (
 	"context"
@@ -25,12 +25,12 @@ import (
 	"github.com/apache/apisix-ingress-controller/pkg/log"
 )
 
-// CompareResources used to compare the object IDs in resources and APISIX
+// Init used to compare the object IDs in resources and APISIX
 // Find out the rest of objects in APISIX
 // AND warn them in log.
 // This func is NOT concurrency safe.
 // cc https://github.com/apache/apisix-ingress-controller/pull/742#discussion_r757197791
-func (c *Controller) CompareResources(ctx context.Context) error {
+func (p *apisixProvider) Init(ctx context.Context) error {
 	var (
 		wg                 sync.WaitGroup
 		routeMapK8S        = new(sync.Map)
@@ -48,7 +48,7 @@ func (c *Controller) CompareResources(ctx context.Context) error {
 		pluginConfigMapA6 = make(map[string]string)
 	)
 
-	namespaces := c.namespaceProvider.WatchingNamespaces()
+	namespaces := p.namespaceProvider.WatchingNamespaces()
 	for _, key := range namespaces {
 		log.Debugf("start to watch namespace: %s", key)
 		wg.Add(1)
@@ -56,15 +56,15 @@ func (c *Controller) CompareResources(ctx context.Context) error {
 			defer wg.Done()
 			// ApisixRoute
 			opts := v1.ListOptions{}
-			switch c.cfg.Kubernetes.ApisixRouteVersion {
+			switch p.common.Config.Kubernetes.APIVersion {
 			case config.ApisixV2beta3:
-				retRoutes, err := c.kubeClient.APISIXClient.ApisixV2beta3().ApisixRoutes(ns).List(ctx, opts)
+				retRoutes, err := p.common.KubeClient.APISIXClient.ApisixV2beta3().ApisixRoutes(ns).List(ctx, opts)
 				if err != nil {
 					log.Error(err.Error())
 					ctx.Done()
 				} else {
 					for _, r := range retRoutes.Items {
-						tc, err := c.translator.TranslateRouteV2beta3NotStrictly(&r)
+						tc, err := p.apisixTranslator.TranslateRouteV2beta3NotStrictly(&r)
 						if err != nil {
 							log.Error(err.Error())
 							ctx.Done()
@@ -93,13 +93,13 @@ func (c *Controller) CompareResources(ctx context.Context) error {
 					}
 				}
 			case config.ApisixV2:
-				retRoutes, err := c.kubeClient.APISIXClient.ApisixV2().ApisixRoutes(ns).List(ctx, opts)
+				retRoutes, err := p.common.KubeClient.APISIXClient.ApisixV2().ApisixRoutes(ns).List(ctx, opts)
 				if err != nil {
 					log.Error(err.Error())
 					ctx.Done()
 				} else {
 					for _, r := range retRoutes.Items {
-						tc, err := c.translator.TranslateRouteV2NotStrictly(&r)
+						tc, err := p.apisixTranslator.TranslateRouteV2NotStrictly(&r)
 						if err != nil {
 							log.Error(err.Error())
 							ctx.Done()
@@ -129,22 +129,22 @@ func (c *Controller) CompareResources(ctx context.Context) error {
 				}
 			default:
 				log.Errorw("failed to sync ApisixRoute, unexpected version",
-					zap.String("version", c.cfg.Kubernetes.ApisixRouteVersion),
+					zap.String("version", p.common.Config.Kubernetes.APIVersion),
 				)
 			}
 			// todo ApisixUpstream and ApisixPluginConfig
 			// ApisixUpstream and ApisixPluginConfig should be synced with ApisixRoute resource
 
-			switch c.cfg.Kubernetes.APIVersion {
+			switch p.common.Config.Kubernetes.APIVersion {
 			case config.ApisixV2beta3:
 				// ApisixConsumer
-				retConsumer, err := c.kubeClient.APISIXClient.ApisixV2beta3().ApisixConsumers(ns).List(ctx, opts)
+				retConsumer, err := p.common.KubeClient.APISIXClient.ApisixV2beta3().ApisixConsumers(ns).List(ctx, opts)
 				if err != nil {
 					log.Error(err.Error())
 					ctx.Done()
 				} else {
 					for _, con := range retConsumer.Items {
-						consumer, err := c.translator.TranslateApisixConsumerV2beta3(&con)
+						consumer, err := p.apisixTranslator.TranslateApisixConsumerV2beta3(&con)
 						if err != nil {
 							log.Error(err.Error())
 							ctx.Done()
@@ -154,13 +154,13 @@ func (c *Controller) CompareResources(ctx context.Context) error {
 					}
 				}
 				// ApisixTls
-				retSSL, err := c.kubeClient.APISIXClient.ApisixV2beta3().ApisixTlses(ns).List(ctx, opts)
+				retSSL, err := p.common.KubeClient.APISIXClient.ApisixV2beta3().ApisixTlses(ns).List(ctx, opts)
 				if err != nil {
 					log.Error(err.Error())
 					ctx.Done()
 				} else {
 					for _, s := range retSSL.Items {
-						ssl, err := c.translator.TranslateSSLV2Beta3(&s)
+						ssl, err := p.apisixTranslator.TranslateSSLV2Beta3(&s)
 						if err != nil {
 							log.Error(err.Error())
 							ctx.Done()
@@ -171,13 +171,13 @@ func (c *Controller) CompareResources(ctx context.Context) error {
 				}
 			case config.ApisixV2:
 				// ApisixConsumer
-				retConsumer, err := c.kubeClient.APISIXClient.ApisixV2().ApisixConsumers(ns).List(ctx, opts)
+				retConsumer, err := p.common.KubeClient.APISIXClient.ApisixV2().ApisixConsumers(ns).List(ctx, opts)
 				if err != nil {
 					log.Error(err.Error())
 					ctx.Done()
 				} else {
 					for _, con := range retConsumer.Items {
-						consumer, err := c.translator.TranslateApisixConsumerV2(&con)
+						consumer, err := p.apisixTranslator.TranslateApisixConsumerV2(&con)
 						if err != nil {
 							log.Error(err.Error())
 							ctx.Done()
@@ -187,13 +187,13 @@ func (c *Controller) CompareResources(ctx context.Context) error {
 					}
 				}
 				// ApisixTls
-				retSSL, err := c.kubeClient.APISIXClient.ApisixV2().ApisixTlses(ns).List(ctx, opts)
+				retSSL, err := p.common.KubeClient.APISIXClient.ApisixV2().ApisixTlses(ns).List(ctx, opts)
 				if err != nil {
 					log.Error(err.Error())
 					ctx.Done()
 				} else {
 					for _, s := range retSSL.Items {
-						ssl, err := c.translator.TranslateSSLV2(&s)
+						ssl, err := p.apisixTranslator.TranslateSSLV2(&s)
 						if err != nil {
 							log.Error(err.Error())
 							ctx.Done()
@@ -204,7 +204,7 @@ func (c *Controller) CompareResources(ctx context.Context) error {
 				}
 			default:
 				log.Errorw("failed to sync ApisixConsumer, unexpected version",
-					zap.String("version", c.cfg.Kubernetes.APIVersion),
+					zap.String("version", p.common.Config.Kubernetes.APIVersion),
 				)
 			}
 		}(key)
@@ -212,22 +212,22 @@ func (c *Controller) CompareResources(ctx context.Context) error {
 	wg.Wait()
 
 	// 2.get all cache routes
-	if err := c.listRouteCache(ctx, routeMapA6); err != nil {
+	if err := p.listRouteCache(ctx, routeMapA6); err != nil {
 		return err
 	}
-	if err := c.listStreamRouteCache(ctx, streamRouteMapA6); err != nil {
+	if err := p.listStreamRouteCache(ctx, streamRouteMapA6); err != nil {
 		return err
 	}
-	if err := c.listUpstreamCache(ctx, upstreamMapA6); err != nil {
+	if err := p.listUpstreamCache(ctx, upstreamMapA6); err != nil {
 		return err
 	}
-	if err := c.listSSLCache(ctx, sslMapA6); err != nil {
+	if err := p.listSSLCache(ctx, sslMapA6); err != nil {
 		return err
 	}
-	if err := c.listConsumerCache(ctx, consumerMapA6); err != nil {
+	if err := p.listConsumerCache(ctx, consumerMapA6); err != nil {
 		return err
 	}
-	if err := c.listPluginConfigCache(ctx, pluginConfigMapA6); err != nil {
+	if err := p.listPluginConfigCache(ctx, pluginConfigMapA6); err != nil {
 		return err
 	}
 	// 3.compare
@@ -267,8 +267,8 @@ func findRedundant(src map[string]string, dest *sync.Map) map[string]string {
 	return result
 }
 
-func (c *Controller) listRouteCache(ctx context.Context, routeMapA6 map[string]string) error {
-	routesInA6, err := c.apisix.Cluster(c.cfg.APISIX.DefaultClusterName).Route().List(ctx)
+func (p *apisixProvider) listRouteCache(ctx context.Context, routeMapA6 map[string]string) error {
+	routesInA6, err := p.common.APISIX.Cluster(p.common.Config.APISIX.DefaultClusterName).Route().List(ctx)
 	if err != nil {
 		return err
 	} else {
@@ -279,8 +279,8 @@ func (c *Controller) listRouteCache(ctx context.Context, routeMapA6 map[string]s
 	return nil
 }
 
-func (c *Controller) listStreamRouteCache(ctx context.Context, streamRouteMapA6 map[string]string) error {
-	streamRoutesInA6, err := c.apisix.Cluster(c.cfg.APISIX.DefaultClusterName).StreamRoute().List(ctx)
+func (p *apisixProvider) listStreamRouteCache(ctx context.Context, streamRouteMapA6 map[string]string) error {
+	streamRoutesInA6, err := p.common.APISIX.Cluster(p.common.Config.APISIX.DefaultClusterName).StreamRoute().List(ctx)
 	if err != nil {
 		return err
 	} else {
@@ -291,8 +291,8 @@ func (c *Controller) listStreamRouteCache(ctx context.Context, streamRouteMapA6
 	return nil
 }
 
-func (c *Controller) listUpstreamCache(ctx context.Context, upstreamMapA6 map[string]string) error {
-	upstreamsInA6, err := c.apisix.Cluster(c.cfg.APISIX.DefaultClusterName).Upstream().List(ctx)
+func (p *apisixProvider) listUpstreamCache(ctx context.Context, upstreamMapA6 map[string]string) error {
+	upstreamsInA6, err := p.common.APISIX.Cluster(p.common.Config.APISIX.DefaultClusterName).Upstream().List(ctx)
 	if err != nil {
 		return err
 	} else {
@@ -303,8 +303,8 @@ func (c *Controller) listUpstreamCache(ctx context.Context, upstreamMapA6 map[st
 	return nil
 }
 
-func (c *Controller) listSSLCache(ctx context.Context, sslMapA6 map[string]string) error {
-	sslInA6, err := c.apisix.Cluster(c.cfg.APISIX.DefaultClusterName).SSL().List(ctx)
+func (p *apisixProvider) listSSLCache(ctx context.Context, sslMapA6 map[string]string) error {
+	sslInA6, err := p.common.APISIX.Cluster(p.common.Config.APISIX.DefaultClusterName).SSL().List(ctx)
 	if err != nil {
 		return err
 	} else {
@@ -315,8 +315,8 @@ func (c *Controller) listSSLCache(ctx context.Context, sslMapA6 map[string]strin
 	return nil
 }
 
-func (c *Controller) listConsumerCache(ctx context.Context, consumerMapA6 map[string]string) error {
-	consumerInA6, err := c.apisix.Cluster(c.cfg.APISIX.DefaultClusterName).Consumer().List(ctx)
+func (p *apisixProvider) listConsumerCache(ctx context.Context, consumerMapA6 map[string]string) error {
+	consumerInA6, err := p.common.APISIX.Cluster(p.common.Config.APISIX.DefaultClusterName).Consumer().List(ctx)
 	if err != nil {
 		return err
 	} else {
@@ -327,8 +327,8 @@ func (c *Controller) listConsumerCache(ctx context.Context, consumerMapA6 map[st
 	return nil
 }
 
-func (c *Controller) listPluginConfigCache(ctx context.Context, pluginConfigMapA6 map[string]string) error {
-	pluginConfigInA6, err := c.apisix.Cluster(c.cfg.APISIX.DefaultClusterName).PluginConfig().List(ctx)
+func (p *apisixProvider) listPluginConfigCache(ctx context.Context, pluginConfigMapA6 map[string]string) error {
+	pluginConfigInA6, err := p.common.APISIX.Cluster(p.common.Config.APISIX.DefaultClusterName).PluginConfig().List(ctx)
 	if err != nil {
 		return err
 	} else {
diff --git a/pkg/kube/translation/global_rule.go b/pkg/providers/apisix/translation/apisix_cluster_config.go
similarity index 100%
rename from pkg/kube/translation/global_rule.go
rename to pkg/providers/apisix/translation/apisix_cluster_config.go
diff --git a/pkg/kube/translation/global_rule_test.go b/pkg/providers/apisix/translation/apisix_cluster_config_test.go
similarity index 100%
rename from pkg/kube/translation/global_rule_test.go
rename to pkg/providers/apisix/translation/apisix_cluster_config_test.go
diff --git a/pkg/kube/translation/apisix_consumer.go b/pkg/providers/apisix/translation/apisix_consumer.go
similarity index 100%
rename from pkg/kube/translation/apisix_consumer.go
rename to pkg/providers/apisix/translation/apisix_consumer.go
diff --git a/pkg/kube/translation/apisix_consumer_test.go b/pkg/providers/apisix/translation/apisix_consumer_test.go
similarity index 100%
rename from pkg/kube/translation/apisix_consumer_test.go
rename to pkg/providers/apisix/translation/apisix_consumer_test.go
diff --git a/pkg/kube/translation/plugin.go b/pkg/providers/apisix/translation/apisix_plugin.go
similarity index 97%
rename from pkg/kube/translation/plugin.go
rename to pkg/providers/apisix/translation/apisix_plugin.go
index f9236ae8..4c726ede 100644
--- a/pkg/kube/translation/plugin.go
+++ b/pkg/providers/apisix/translation/apisix_plugin.go
@@ -20,6 +20,7 @@ import (
 
 	configv2 "github.com/apache/apisix-ingress-controller/pkg/kube/apisix/apis/config/v2"
 	configv2beta3 "github.com/apache/apisix-ingress-controller/pkg/kube/apisix/apis/config/v2beta3"
+	"github.com/apache/apisix-ingress-controller/pkg/providers/translation"
 	apisixv1 "github.com/apache/apisix-ingress-controller/pkg/types/apisix/v1"
 )
 
@@ -38,24 +39,24 @@ var (
 	_hmacAuthMaxReqBodyDefaultValue          = int64(524288)
 )
 
-func (t *translator) translateTrafficSplitPlugin(ctx *TranslateContext, ns string, defaultBackendWeight int,
+func (t *translator) translateTrafficSplitPlugin(ctx *translation.TranslateContext, ns string, defaultBackendWeight int,
 	backends []configv2.ApisixRouteHTTPBackend) (*apisixv1.TrafficSplitConfig, error) {
 	var (
 		wups []apisixv1.TrafficSplitConfigRuleWeightedUpstream
 	)
 
 	for _, backend := range backends {
-		svcClusterIP, svcPort, err := t.getServiceClusterIPAndPort(&backend, ns)
+		svcClusterIP, svcPort, err := t.GetServiceClusterIPAndPort(&backend, ns)
 		if err != nil {
 			return nil, err
 		}
-		ups, err := t.translateUpstream(ns, backend.ServiceName, backend.Subset, backend.ResolveGranularity, svcClusterIP, svcPort)
+		ups, err := t.translateService(ns, backend.ServiceName, backend.Subset, backend.ResolveGranularity, svcClusterIP, svcPort)
 		if err != nil {
 			return nil, err
 		}
 		ctx.AddUpstream(ups)
 
-		weight := _defaultWeight
+		weight := translation.DefaultWeight
 		if backend.Weight != nil {
 			weight = *backend.Weight
 		}
diff --git a/pkg/kube/translation/plugin_test.go b/pkg/providers/apisix/translation/apisix_plugin_test.go
similarity index 95%
rename from pkg/kube/translation/plugin_test.go
rename to pkg/providers/apisix/translation/apisix_plugin_test.go
index 46c32dce..2c0b224c 100644
--- a/pkg/kube/translation/plugin_test.go
+++ b/pkg/providers/apisix/translation/apisix_plugin_test.go
@@ -33,6 +33,7 @@ import (
 	configv2beta3 "github.com/apache/apisix-ingress-controller/pkg/kube/apisix/apis/config/v2beta3"
 	apisixfake "github.com/apache/apisix-ingress-controller/pkg/kube/apisix/client/clientset/versioned/fake"
 	apisixinformers "github.com/apache/apisix-ingress-controller/pkg/kube/apisix/client/informers/externalversions"
+	"github.com/apache/apisix-ingress-controller/pkg/providers/translation"
 )
 
 func TestTranslateTrafficSplitPlugin(t *testing.T) {
@@ -176,13 +177,15 @@ func TestTranslateTrafficSplitPlugin(t *testing.T) {
 	}
 
 	tr := &translator{&TranslatorOptions{
+		ServiceLister: svcLister,
+	}, translation.NewTranslator(&translation.TranslatorOptions{
 		ServiceLister:        svcLister,
 		EndpointLister:       epLister,
 		ApisixUpstreamLister: auLister,
 		APIVersion:           config.DefaultAPIVersion,
-	}}
-	ctx := &TranslateContext{
-		upstreamMap: make(map[string]struct{}),
+	})}
+	ctx := &translation.TranslateContext{
+		UpstreamMap: make(map[string]struct{}),
 	}
 	cfg, err := tr.translateTrafficSplitPlugin(ctx, ar1.Namespace, 30, backends)
 	assert.Nil(t, err)
@@ -351,12 +354,16 @@ func TestTranslateTrafficSplitPluginWithSameUpstreams(t *testing.T) {
 	}
 
 	tr := &translator{&TranslatorOptions{
+		ServiceLister: svcLister,
+	}, translation.NewTranslator(&translation.TranslatorOptions{
 		ServiceLister:        svcLister,
 		EndpointLister:       epLister,
 		ApisixUpstreamLister: auLister,
 		APIVersion:           config.DefaultAPIVersion,
-	}}
-	ctx := &TranslateContext{upstreamMap: make(map[string]struct{})}
+	})}
+	ctx := &translation.TranslateContext{
+		UpstreamMap: make(map[string]struct{}),
+	}
 	cfg, err := tr.translateTrafficSplitPlugin(ctx, ar1.Namespace, 30, backends)
 	assert.Nil(t, err)
 
@@ -519,12 +526,14 @@ func TestTranslateTrafficSplitPluginBadCases(t *testing.T) {
 	}
 
 	tr := &translator{&TranslatorOptions{
+		ServiceLister: svcLister,
+	}, translation.NewTranslator(&translation.TranslatorOptions{
 		ServiceLister:        svcLister,
 		EndpointLister:       epLister,
 		ApisixUpstreamLister: auLister,
 		APIVersion:           config.DefaultAPIVersion,
-	}}
-	ctx := &TranslateContext{upstreamMap: make(map[string]struct{})}
+	})}
+	ctx := &translation.TranslateContext{UpstreamMap: make(map[string]struct{})}
 	cfg, err := tr.translateTrafficSplitPlugin(ctx, ar1.Namespace, 30, backends)
 	assert.Nil(t, cfg)
 	assert.Len(t, ctx.Upstreams, 0)
@@ -533,7 +542,7 @@ func TestTranslateTrafficSplitPluginBadCases(t *testing.T) {
 
 	backends[0].ServiceName = "svc-1"
 	backends[1].ServicePort.StrVal = "port-not-found"
-	ctx = &TranslateContext{upstreamMap: make(map[string]struct{})}
+	ctx = &translation.TranslateContext{UpstreamMap: make(map[string]struct{})}
 	cfg, err = tr.translateTrafficSplitPlugin(ctx, ar1.Namespace, 30, backends)
 	assert.Nil(t, cfg)
 	assert.NotNil(t, err)
@@ -541,7 +550,7 @@ func TestTranslateTrafficSplitPluginBadCases(t *testing.T) {
 
 	backends[1].ServicePort.StrVal = "port2"
 	backends[1].ResolveGranularity = "service"
-	ctx = &TranslateContext{upstreamMap: make(map[string]struct{})}
+	ctx = &translation.TranslateContext{UpstreamMap: make(map[string]struct{})}
 	cfg, err = tr.translateTrafficSplitPlugin(ctx, ar1.Namespace, 30, backends)
 	assert.Nil(t, cfg)
 	assert.NotNil(t, err)
@@ -582,11 +591,10 @@ func TestTranslateConsumerKeyAuthWithSecretRef(t *testing.T) {
 	})
 	go secretInformer.Run(stopCh)
 
-	tr := &translator{
-		&TranslatorOptions{
-			SecretLister: secretLister,
-		},
-	}
+	tr := &translator{&TranslatorOptions{
+		SecretLister: secretLister,
+	}, translation.NewTranslator(nil)}
+
 	_, err := client.CoreV1().Secrets("default").Create(context.Background(), sec, metav1.CreateOptions{})
 	assert.Nil(t, err)
 
@@ -656,11 +664,10 @@ func TestTranslateConsumerBasicAuthWithSecretRef(t *testing.T) {
 	})
 	go secretInformer.Run(stopCh)
 
-	tr := &translator{
-		&TranslatorOptions{
-			SecretLister: secretLister,
-		},
-	}
+	tr := &translator{&TranslatorOptions{
+		SecretLister: secretLister,
+	}, translation.NewTranslator(nil)}
+
 	_, err := client.CoreV1().Secrets("default").Create(context.Background(), sec, metav1.CreateOptions{})
 	assert.Nil(t, err)
 
@@ -769,11 +776,10 @@ func TestTranslateConsumerJwtAuthWithSecretRef(t *testing.T) {
 	})
 	go secretInformer.Run(stopCh)
 
-	tr := &translator{
-		&TranslatorOptions{
-			SecretLister: secretLister,
-		},
-	}
+	tr := &translator{&TranslatorOptions{
+		SecretLister: secretLister,
+	}, translation.NewTranslator(nil)}
+
 	_, err := client.CoreV1().Secrets("default").Create(context.Background(), sec, metav1.CreateOptions{})
 	assert.Nil(t, err)
 
@@ -898,11 +904,10 @@ func TestTranslateConsumerWolfRBACWithSecretRef(t *testing.T) {
 	})
 	go secretInformer.Run(stopCh)
 
-	tr := &translator{
-		&TranslatorOptions{
-			SecretLister: secretLister,
-		},
-	}
+	tr := &translator{&TranslatorOptions{
+		SecretLister: secretLister,
+	}, translation.NewTranslator(nil)}
+
 	_, err := client.CoreV1().Secrets("default").Create(context.Background(), sec, metav1.CreateOptions{})
 	assert.Nil(t, err)
 
@@ -995,11 +1000,10 @@ func TestTranslateConsumerHMACAuthPluginWithSecretRef(t *testing.T) {
 	})
 	go secretInformer.Run(stopCh)
 
-	tr := &translator{
-		&TranslatorOptions{
-			SecretLister: secretLister,
-		},
-	}
+	tr := &translator{&TranslatorOptions{
+		SecretLister: secretLister,
+	}, translation.NewTranslator(nil)}
+
 	_, err := client.CoreV1().Secrets("default").Create(context.Background(), sec, metav1.CreateOptions{})
 	assert.Nil(t, err)
 
diff --git a/pkg/kube/translation/apisix_pluginconfig.go b/pkg/providers/apisix/translation/apisix_pluginconfig.go
similarity index 86%
rename from pkg/kube/translation/apisix_pluginconfig.go
rename to pkg/providers/apisix/translation/apisix_pluginconfig.go
index 4bf35510..bea90ed4 100644
--- a/pkg/kube/translation/apisix_pluginconfig.go
+++ b/pkg/providers/apisix/translation/apisix_pluginconfig.go
@@ -21,11 +21,12 @@ import (
 	configv2 "github.com/apache/apisix-ingress-controller/pkg/kube/apisix/apis/config/v2"
 	configv2beta3 "github.com/apache/apisix-ingress-controller/pkg/kube/apisix/apis/config/v2beta3"
 	"github.com/apache/apisix-ingress-controller/pkg/log"
+	"github.com/apache/apisix-ingress-controller/pkg/providers/translation"
 	apisixv1 "github.com/apache/apisix-ingress-controller/pkg/types/apisix/v1"
 )
 
-func (t *translator) TranslatePluginConfigV2beta3(config *configv2beta3.ApisixPluginConfig) (*TranslateContext, error) {
-	ctx := DefaultEmptyTranslateContext()
+func (t *translator) TranslatePluginConfigV2beta3(config *configv2beta3.ApisixPluginConfig) (*translation.TranslateContext, error) {
+	ctx := translation.DefaultEmptyTranslateContext()
 	pluginMap := make(apisixv1.Plugins)
 	if len(config.Spec.Plugins) > 0 {
 		for _, plugin := range config.Spec.Plugins {
@@ -55,8 +56,8 @@ func (t *translator) TranslatePluginConfigV2beta3(config *configv2beta3.ApisixPl
 	return ctx, nil
 }
 
-func (t *translator) TranslatePluginConfigV2beta3NotStrictly(config *configv2beta3.ApisixPluginConfig) (*TranslateContext, error) {
-	ctx := DefaultEmptyTranslateContext()
+func (t *translator) TranslatePluginConfigV2beta3NotStrictly(config *configv2beta3.ApisixPluginConfig) (*translation.TranslateContext, error) {
+	ctx := translation.DefaultEmptyTranslateContext()
 	pc := apisixv1.NewDefaultPluginConfig()
 	pc.Name = apisixv1.ComposePluginConfigName(config.Namespace, config.Name)
 	pc.ID = id.GenID(pc.Name)
@@ -64,8 +65,8 @@ func (t *translator) TranslatePluginConfigV2beta3NotStrictly(config *configv2bet
 	return ctx, nil
 }
 
-func (t *translator) TranslatePluginConfigV2(config *configv2.ApisixPluginConfig) (*TranslateContext, error) {
-	ctx := DefaultEmptyTranslateContext()
+func (t *translator) TranslatePluginConfigV2(config *configv2.ApisixPluginConfig) (*translation.TranslateContext, error) {
+	ctx := translation.DefaultEmptyTranslateContext()
 	pluginMap := make(apisixv1.Plugins)
 	if len(config.Spec.Plugins) > 0 {
 		for _, plugin := range config.Spec.Plugins {
@@ -95,8 +96,8 @@ func (t *translator) TranslatePluginConfigV2(config *configv2.ApisixPluginConfig
 	return ctx, nil
 }
 
-func (t *translator) TranslatePluginConfigV2NotStrictly(config *configv2.ApisixPluginConfig) (*TranslateContext, error) {
-	ctx := DefaultEmptyTranslateContext()
+func (t *translator) TranslatePluginConfigV2NotStrictly(config *configv2.ApisixPluginConfig) (*translation.TranslateContext, error) {
+	ctx := translation.DefaultEmptyTranslateContext()
 	pc := apisixv1.NewDefaultPluginConfig()
 	pc.Name = apisixv1.ComposePluginConfigName(config.Namespace, config.Name)
 	pc.ID = id.GenID(pc.Name)
diff --git a/pkg/kube/translation/apisix_pluginconfig_test.go b/pkg/providers/apisix/translation/apisix_pluginconfig_test.go
similarity index 100%
rename from pkg/kube/translation/apisix_pluginconfig_test.go
rename to pkg/providers/apisix/translation/apisix_pluginconfig_test.go
diff --git a/pkg/kube/translation/apisix_route.go b/pkg/providers/apisix/translation/apisix_route.go
similarity index 74%
rename from pkg/kube/translation/apisix_route.go
rename to pkg/providers/apisix/translation/apisix_route.go
index e2bcf1e7..bf32f733 100644
--- a/pkg/kube/translation/apisix_route.go
+++ b/pkg/providers/apisix/translation/apisix_route.go
@@ -17,21 +17,26 @@ package translation
 import (
 	"context"
 	"errors"
+	"fmt"
 	"strings"
 
 	"go.uber.org/zap"
+	"k8s.io/apimachinery/pkg/util/intstr"
 
+	"github.com/apache/apisix-ingress-controller/pkg/config"
 	"github.com/apache/apisix-ingress-controller/pkg/id"
+	"github.com/apache/apisix-ingress-controller/pkg/kube"
 	configv2 "github.com/apache/apisix-ingress-controller/pkg/kube/apisix/apis/config/v2"
 	configv2beta2 "github.com/apache/apisix-ingress-controller/pkg/kube/apisix/apis/config/v2beta2"
 	configv2beta3 "github.com/apache/apisix-ingress-controller/pkg/kube/apisix/apis/config/v2beta3"
 	_const "github.com/apache/apisix-ingress-controller/pkg/kube/apisix/const"
 	"github.com/apache/apisix-ingress-controller/pkg/log"
+	"github.com/apache/apisix-ingress-controller/pkg/providers/translation"
 	apisixv1 "github.com/apache/apisix-ingress-controller/pkg/types/apisix/v1"
 )
 
-func (t *translator) TranslateRouteV2beta2(ar *configv2beta2.ApisixRoute) (*TranslateContext, error) {
-	ctx := DefaultEmptyTranslateContext()
+func (t *translator) TranslateRouteV2beta2(ar *configv2beta2.ApisixRoute) (*translation.TranslateContext, error) {
+	ctx := translation.DefaultEmptyTranslateContext()
 
 	if err := t.translateHTTPRouteV2beta2(ctx, ar); err != nil {
 		return nil, err
@@ -42,8 +47,8 @@ func (t *translator) TranslateRouteV2beta2(ar *configv2beta2.ApisixRoute) (*Tran
 	return ctx, nil
 }
 
-func (t *translator) TranslateRouteV2beta2NotStrictly(ar *configv2beta2.ApisixRoute) (*TranslateContext, error) {
-	ctx := DefaultEmptyTranslateContext()
+func (t *translator) TranslateRouteV2beta2NotStrictly(ar *configv2beta2.ApisixRoute) (*translation.TranslateContext, error) {
+	ctx := translation.DefaultEmptyTranslateContext()
 
 	if err := t.translateHTTPRouteV2beta2NotStrictly(ctx, ar); err != nil {
 		return nil, err
@@ -54,8 +59,8 @@ func (t *translator) TranslateRouteV2beta2NotStrictly(ar *configv2beta2.ApisixRo
 	return ctx, nil
 }
 
-func (t *translator) TranslateRouteV2beta3(ar *configv2beta3.ApisixRoute) (*TranslateContext, error) {
-	ctx := DefaultEmptyTranslateContext()
+func (t *translator) TranslateRouteV2beta3(ar *configv2beta3.ApisixRoute) (*translation.TranslateContext, error) {
+	ctx := translation.DefaultEmptyTranslateContext()
 
 	if err := t.translateHTTPRouteV2beta3(ctx, ar); err != nil {
 		return nil, err
@@ -66,8 +71,8 @@ func (t *translator) TranslateRouteV2beta3(ar *configv2beta3.ApisixRoute) (*Tran
 	return ctx, nil
 }
 
-func (t *translator) TranslateRouteV2beta3NotStrictly(ar *configv2beta3.ApisixRoute) (*TranslateContext, error) {
-	ctx := DefaultEmptyTranslateContext()
+func (t *translator) TranslateRouteV2beta3NotStrictly(ar *configv2beta3.ApisixRoute) (*translation.TranslateContext, error) {
+	ctx := translation.DefaultEmptyTranslateContext()
 
 	if err := t.translateHTTPRouteV2beta3NotStrictly(ctx, ar); err != nil {
 		return nil, err
@@ -78,8 +83,8 @@ func (t *translator) TranslateRouteV2beta3NotStrictly(ar *configv2beta3.ApisixRo
 	return ctx, nil
 }
 
-func (t *translator) TranslateRouteV2(ar *configv2.ApisixRoute) (*TranslateContext, error) {
-	ctx := DefaultEmptyTranslateContext()
+func (t *translator) TranslateRouteV2(ar *configv2.ApisixRoute) (*translation.TranslateContext, error) {
+	ctx := translation.DefaultEmptyTranslateContext()
 
 	if err := t.translateHTTPRouteV2(ctx, ar); err != nil {
 		return nil, err
@@ -90,8 +95,8 @@ func (t *translator) TranslateRouteV2(ar *configv2.ApisixRoute) (*TranslateConte
 	return ctx, nil
 }
 
-func (t *translator) TranslateRouteV2NotStrictly(ar *configv2.ApisixRoute) (*TranslateContext, error) {
-	ctx := DefaultEmptyTranslateContext()
+func (t *translator) TranslateRouteV2NotStrictly(ar *configv2.ApisixRoute) (*translation.TranslateContext, error) {
+	ctx := translation.DefaultEmptyTranslateContext()
 
 	if err := t.translateHTTPRouteV2NotStrictly(ctx, ar); err != nil {
 		return nil, err
@@ -102,7 +107,7 @@ func (t *translator) TranslateRouteV2NotStrictly(ar *configv2.ApisixRoute) (*Tra
 	return ctx, nil
 }
 
-func (t *translator) translateHTTPRouteV2beta2(ctx *TranslateContext, ar *configv2beta2.ApisixRoute) error {
+func (t *translator) translateHTTPRouteV2beta2(ctx *translation.TranslateContext, ar *configv2beta2.ApisixRoute) error {
 	ruleNameMap := make(map[string]struct{})
 	for _, part := range ar.Spec.HTTP {
 		if _, ok := ruleNameMap[part.Name]; ok {
@@ -115,7 +120,7 @@ func (t *translator) translateHTTPRouteV2beta2(ctx *TranslateContext, ar *config
 		backend := backends[0]
 		backends = backends[1:]
 
-		svcClusterIP, svcPort, err := t.getServiceClusterIPAndPort(&backend, ar.Namespace)
+		svcClusterIP, svcPort, err := t.GetServiceClusterIPAndPort(&backend, ar.Namespace)
 		if err != nil {
 			log.Errorw("failed to get service port in backend",
 				zap.Any("backend", backend),
@@ -152,7 +157,7 @@ func (t *translator) translateHTTPRouteV2beta2(ctx *TranslateContext, ar *config
 
 		var exprs [][]apisixv1.StringOrSlice
 		if part.Match.NginxVars != nil {
-			exprs, err = t.translateRouteMatchExprs(part.Match.NginxVars)
+			exprs, err = t.TranslateRouteMatchExprs(part.Match.NginxVars)
 			if err != nil {
 				log.Errorw("ApisixRoute with bad nginxVars",
 					zap.Error(err),
@@ -161,7 +166,7 @@ func (t *translator) translateHTTPRouteV2beta2(ctx *TranslateContext, ar *config
 				return err
 			}
 		}
-		if err := validateRemoteAddrs(part.Match.RemoteAddrs); err != nil {
+		if err := translation.ValidateRemoteAddrs(part.Match.RemoteAddrs); err != nil {
 			log.Errorw("ApisixRoute with invalid remote addrs",
 				zap.Error(err),
 				zap.Strings("remote_addrs", part.Match.RemoteAddrs),
@@ -185,7 +190,7 @@ func (t *translator) translateHTTPRouteV2beta2(ctx *TranslateContext, ar *config
 		route.Plugins = pluginMap
 
 		if len(backends) > 0 {
-			weight := _defaultWeight
+			weight := translation.DefaultWeight
 			if backend.Weight != nil {
 				weight = *backend.Weight
 			}
@@ -203,7 +208,7 @@ func (t *translator) translateHTTPRouteV2beta2(ctx *TranslateContext, ar *config
 		}
 		ctx.AddRoute(route)
 		if !ctx.CheckUpstreamExist(upstreamName) {
-			ups, err := t.translateUpstream(ar.Namespace, backend.ServiceName, backend.Subset, backend.ResolveGranularity, svcClusterIP, svcPort)
+			ups, err := t.translateService(ar.Namespace, backend.ServiceName, backend.Subset, backend.ResolveGranularity, svcClusterIP, svcPort)
 			if err != nil {
 				return err
 			}
@@ -213,7 +218,7 @@ func (t *translator) translateHTTPRouteV2beta2(ctx *TranslateContext, ar *config
 	return nil
 }
 
-func (t *translator) translateHTTPRouteV2beta3(ctx *TranslateContext, ar *configv2beta3.ApisixRoute) error {
+func (t *translator) translateHTTPRouteV2beta3(ctx *translation.TranslateContext, ar *configv2beta3.ApisixRoute) error {
 	ruleNameMap := make(map[string]struct{})
 	for _, part := range ar.Spec.HTTP {
 		if _, ok := ruleNameMap[part.Name]; ok {
@@ -226,7 +231,7 @@ func (t *translator) translateHTTPRouteV2beta3(ctx *TranslateContext, ar *config
 		backend := backends[0]
 		backends = backends[1:]
 
-		svcClusterIP, svcPort, err := t.getServiceClusterIPAndPort(&backend, ar.Namespace)
+		svcClusterIP, svcPort, err := t.GetServiceClusterIPAndPort(&backend, ar.Namespace)
 		if err != nil {
 			log.Errorw("failed to get service port in backend",
 				zap.Any("backend", backend),
@@ -286,7 +291,7 @@ func (t *translator) translateHTTPRouteV2beta3(ctx *TranslateContext, ar *config
 
 		var exprs [][]apisixv1.StringOrSlice
 		if part.Match.NginxVars != nil {
-			exprs, err = t.translateRouteMatchExprs(part.Match.NginxVars)
+			exprs, err = t.TranslateRouteMatchExprs(part.Match.NginxVars)
 			if err != nil {
 				log.Errorw("ApisixRoute with bad nginxVars",
 					zap.Error(err),
@@ -295,7 +300,7 @@ func (t *translator) translateHTTPRouteV2beta3(ctx *TranslateContext, ar *config
 				return err
 			}
 		}
-		if err := validateRemoteAddrs(part.Match.RemoteAddrs); err != nil {
+		if err := translation.ValidateRemoteAddrs(part.Match.RemoteAddrs); err != nil {
 			log.Errorw("ApisixRoute with invalid remote addrs",
 				zap.Error(err),
 				zap.Strings("remote_addrs", part.Match.RemoteAddrs),
@@ -323,7 +328,7 @@ func (t *translator) translateHTTPRouteV2beta3(ctx *TranslateContext, ar *config
 		}
 
 		if len(backends) > 0 {
-			weight := _defaultWeight
+			weight := translation.DefaultWeight
 			if backend.Weight != nil {
 				weight = *backend.Weight
 			}
@@ -339,7 +344,7 @@ func (t *translator) translateHTTPRouteV2beta3(ctx *TranslateContext, ar *config
 		}
 		ctx.AddRoute(route)
 		if !ctx.CheckUpstreamExist(upstreamName) {
-			ups, err := t.translateUpstream(ar.Namespace, backend.ServiceName, backend.Subset, backend.ResolveGranularity, svcClusterIP, svcPort)
+			ups, err := t.translateService(ar.Namespace, backend.ServiceName, backend.Subset, backend.ResolveGranularity, svcClusterIP, svcPort)
 			if err != nil {
 				return err
 			}
@@ -349,7 +354,7 @@ func (t *translator) translateHTTPRouteV2beta3(ctx *TranslateContext, ar *config
 	return nil
 }
 
-func (t *translator) translateHTTPRouteV2(ctx *TranslateContext, ar *configv2.ApisixRoute) error {
+func (t *translator) translateHTTPRouteV2(ctx *translation.TranslateContext, ar *configv2.ApisixRoute) error {
 	ruleNameMap := make(map[string]struct{})
 	for _, part := range ar.Spec.HTTP {
 		if _, ok := ruleNameMap[part.Name]; ok {
@@ -362,7 +367,7 @@ func (t *translator) translateHTTPRouteV2(ctx *TranslateContext, ar *configv2.Ap
 		backend := backends[0]
 		backends = backends[1:]
 
-		svcClusterIP, svcPort, err := t.getServiceClusterIPAndPort(&backend, ar.Namespace)
+		svcClusterIP, svcPort, err := t.GetServiceClusterIPAndPort(&backend, ar.Namespace)
 		if err != nil {
 			log.Errorw("failed to get service port in backend",
 				zap.Any("backend", backend),
@@ -422,7 +427,7 @@ func (t *translator) translateHTTPRouteV2(ctx *TranslateContext, ar *configv2.Ap
 
 		var exprs [][]apisixv1.StringOrSlice
 		if part.Match.NginxVars != nil {
-			exprs, err = t.translateRouteMatchExprs(part.Match.NginxVars)
+			exprs, err = t.TranslateRouteMatchExprs(part.Match.NginxVars)
 			if err != nil {
 				log.Errorw("ApisixRoute with bad nginxVars",
 					zap.Error(err),
@@ -431,7 +436,7 @@ func (t *translator) translateHTTPRouteV2(ctx *TranslateContext, ar *configv2.Ap
 				return err
 			}
 		}
-		if err := validateRemoteAddrs(part.Match.RemoteAddrs); err != nil {
+		if err := translation.ValidateRemoteAddrs(part.Match.RemoteAddrs); err != nil {
 			log.Errorw("ApisixRoute with invalid remote addrs",
 				zap.Error(err),
 				zap.Strings("remote_addrs", part.Match.RemoteAddrs),
@@ -459,7 +464,7 @@ func (t *translator) translateHTTPRouteV2(ctx *TranslateContext, ar *configv2.Ap
 		}
 
 		if len(backends) > 0 {
-			weight := _defaultWeight
+			weight := translation.DefaultWeight
 			if backend.Weight != nil {
 				weight = *backend.Weight
 			}
@@ -475,7 +480,7 @@ func (t *translator) translateHTTPRouteV2(ctx *TranslateContext, ar *configv2.Ap
 		}
 		ctx.AddRoute(route)
 		if !ctx.CheckUpstreamExist(upstreamName) {
-			ups, err := t.translateUpstream(ar.Namespace, backend.ServiceName, backend.Subset, backend.ResolveGranularity, svcClusterIP, svcPort)
+			ups, err := t.translateService(ar.Namespace, backend.ServiceName, backend.Subset, backend.ResolveGranularity, svcClusterIP, svcPort)
 			if err != nil {
 				return err
 			}
@@ -485,7 +490,7 @@ func (t *translator) translateHTTPRouteV2(ctx *TranslateContext, ar *configv2.Ap
 	return nil
 }
 
-func (t *translator) translateRouteMatchExprs(nginxVars []configv2.ApisixRouteHTTPMatchExpr) ([][]apisixv1.StringOrSlice, error) {
+func (t *translator) TranslateRouteMatchExprs(nginxVars []configv2.ApisixRouteHTTPMatchExpr) ([][]apisixv1.StringOrSlice, error) {
 	var (
 		vars [][]apisixv1.StringOrSlice
 		op   string
@@ -586,7 +591,7 @@ func (t *translator) translateRouteMatchExprs(nginxVars []configv2.ApisixRouteHT
 }
 
 // translateHTTPRouteV2beta2NotStrictly translates http route with a loose way, only generate ID and Name for delete Event.
-func (t *translator) translateHTTPRouteV2beta2NotStrictly(ctx *TranslateContext, ar *configv2beta2.ApisixRoute) error {
+func (t *translator) translateHTTPRouteV2beta2NotStrictly(ctx *translation.TranslateContext, ar *configv2beta2.ApisixRoute) error {
 	for _, part := range ar.Spec.HTTP {
 		backends := part.Backends
 		// Use the first backend as the default backend in Route,
@@ -609,7 +614,7 @@ func (t *translator) translateHTTPRouteV2beta2NotStrictly(ctx *TranslateContext,
 }
 
 // translateHTTPRouteV2beta3NotStrictly translates http route with a loose way, only generate ID and Name for delete Event.
-func (t *translator) translateHTTPRouteV2beta3NotStrictly(ctx *TranslateContext, ar *configv2beta3.ApisixRoute) error {
+func (t *translator) translateHTTPRouteV2beta3NotStrictly(ctx *translation.TranslateContext, ar *configv2beta3.ApisixRoute) error {
 	for _, part := range ar.Spec.HTTP {
 		backends := part.Backends
 		// Use the first backend as the default backend in Route,
@@ -668,7 +673,7 @@ func (t *translator) translateHTTPRouteV2beta3NotStrictly(ctx *TranslateContext,
 }
 
 // translateHTTPRouteV2NotStrictly translates http route with a loose way, only generate ID and Name for delete Event.
-func (t *translator) translateHTTPRouteV2NotStrictly(ctx *TranslateContext, ar *configv2.ApisixRoute) error {
+func (t *translator) translateHTTPRouteV2NotStrictly(ctx *translation.TranslateContext, ar *configv2.ApisixRoute) error {
 	for _, part := range ar.Spec.HTTP {
 		backends := part.Backends
 		// Use the first backend as the default backend in Route,
@@ -726,7 +731,7 @@ func (t *translator) translateHTTPRouteV2NotStrictly(ctx *TranslateContext, ar *
 	return nil
 }
 
-func (t *translator) translateStreamRouteV2beta2(ctx *TranslateContext, ar *configv2beta2.ApisixRoute) error {
+func (t *translator) translateStreamRouteV2beta2(ctx *translation.TranslateContext, ar *configv2beta2.ApisixRoute) error {
 	ruleNameMap := make(map[string]struct{})
 	for _, part := range ar.Spec.Stream {
 		if _, ok := ruleNameMap[part.Name]; ok {
@@ -747,7 +752,7 @@ func (t *translator) translateStreamRouteV2beta2(ctx *TranslateContext, ar *conf
 		name := apisixv1.ComposeStreamRouteName(ar.Namespace, ar.Name, part.Name)
 		sr.ID = id.GenID(name)
 		sr.ServerPort = part.Match.IngressPort
-		ups, err := t.translateUpstream(ar.Namespace, backend.ServiceName, backend.Subset, backend.ResolveGranularity, svcClusterIP, svcPort)
+		ups, err := t.translateService(ar.Namespace, backend.ServiceName, backend.Subset, backend.ResolveGranularity, svcClusterIP, svcPort)
 		if err != nil {
 			return err
 		}
@@ -761,7 +766,7 @@ func (t *translator) translateStreamRouteV2beta2(ctx *TranslateContext, ar *conf
 	return nil
 }
 
-func (t *translator) translateStreamRouteV2beta3(ctx *TranslateContext, ar *configv2beta3.ApisixRoute) error {
+func (t *translator) translateStreamRouteV2beta3(ctx *translation.TranslateContext, ar *configv2beta3.ApisixRoute) error {
 	ruleNameMap := make(map[string]struct{})
 	for _, part := range ar.Spec.Stream {
 		if _, ok := ruleNameMap[part.Name]; ok {
@@ -782,7 +787,7 @@ func (t *translator) translateStreamRouteV2beta3(ctx *TranslateContext, ar *conf
 		name := apisixv1.ComposeStreamRouteName(ar.Namespace, ar.Name, part.Name)
 		sr.ID = id.GenID(name)
 		sr.ServerPort = part.Match.IngressPort
-		ups, err := t.translateUpstream(ar.Namespace, backend.ServiceName, backend.Subset, backend.ResolveGranularity, svcClusterIP, svcPort)
+		ups, err := t.translateService(ar.Namespace, backend.ServiceName, backend.Subset, backend.ResolveGranularity, svcClusterIP, svcPort)
 		if err != nil {
 			return err
 		}
@@ -796,7 +801,7 @@ func (t *translator) translateStreamRouteV2beta3(ctx *TranslateContext, ar *conf
 	return nil
 }
 
-func (t *translator) translateStreamRouteV2(ctx *TranslateContext, ar *configv2.ApisixRoute) error {
+func (t *translator) translateStreamRouteV2(ctx *translation.TranslateContext, ar *configv2.ApisixRoute) error {
 	ruleNameMap := make(map[string]struct{})
 	for _, part := range ar.Spec.Stream {
 		if _, ok := ruleNameMap[part.Name]; ok {
@@ -817,7 +822,7 @@ func (t *translator) translateStreamRouteV2(ctx *TranslateContext, ar *configv2.
 		name := apisixv1.ComposeStreamRouteName(ar.Namespace, ar.Name, part.Name)
 		sr.ID = id.GenID(name)
 		sr.ServerPort = part.Match.IngressPort
-		ups, err := t.translateUpstream(ar.Namespace, backend.ServiceName, backend.Subset, backend.ResolveGranularity, svcClusterIP, svcPort)
+		ups, err := t.translateService(ar.Namespace, backend.ServiceName, backend.Subset, backend.ResolveGranularity, svcClusterIP, svcPort)
 		if err != nil {
 			return err
 		}
@@ -832,7 +837,7 @@ func (t *translator) translateStreamRouteV2(ctx *TranslateContext, ar *configv2.
 }
 
 // translateStreamRouteNotStrictlyV2beta2 translates tcp route with a loose way, only generate ID and Name for delete Event.
-func (t *translator) translateStreamRouteNotStrictlyV2beta2(ctx *TranslateContext, ar *configv2beta2.ApisixRoute) error {
+func (t *translator) translateStreamRouteNotStrictlyV2beta2(ctx *translation.TranslateContext, ar *configv2beta2.ApisixRoute) error {
 	for _, part := range ar.Spec.Stream {
 		backend := &part.Backend
 		sr := apisixv1.NewDefaultStreamRoute()
@@ -853,7 +858,7 @@ func (t *translator) translateStreamRouteNotStrictlyV2beta2(ctx *TranslateContex
 }
 
 // translateStreamRouteNotStrictlyV2beta3 translates tcp route with a loose way, only generate ID and Name for delete Event.
-func (t *translator) translateStreamRouteNotStrictlyV2beta3(ctx *TranslateContext, ar *configv2beta3.ApisixRoute) error {
+func (t *translator) translateStreamRouteNotStrictlyV2beta3(ctx *translation.TranslateContext, ar *configv2beta3.ApisixRoute) error {
 	for _, part := range ar.Spec.Stream {
 		backend := &part.Backend
 		sr := apisixv1.NewDefaultStreamRoute()
@@ -874,7 +879,7 @@ func (t *translator) translateStreamRouteNotStrictlyV2beta3(ctx *TranslateContex
 }
 
 // translateStreamRouteNotStrictlyV2 translates tcp route with a loose way, only generate ID and Name for delete Event.
-func (t *translator) translateStreamRouteNotStrictlyV2(ctx *TranslateContext, ar *configv2.ApisixRoute) error {
+func (t *translator) translateStreamRouteNotStrictlyV2(ctx *translation.TranslateContext, ar *configv2.ApisixRoute) error {
 	for _, part := range ar.Spec.Stream {
 		backend := &part.Backend
 		sr := apisixv1.NewDefaultStreamRoute()
@@ -894,8 +899,180 @@ func (t *translator) translateStreamRouteNotStrictlyV2(ctx *TranslateContext, ar
 	return nil
 }
 
-func (t *translator) translateOldRouteV2(ar *configv2.ApisixRoute) (*TranslateContext, error) {
-	oldCtx := DefaultEmptyTranslateContext()
+func (t *translator) GetServiceClusterIPAndPort(backend *configv2.ApisixRouteHTTPBackend, ns string) (string, int32, error) {
+	svc, err := t.ServiceLister.Services(ns).Get(backend.ServiceName)
+	if err != nil {
+		return "", 0, err
+	}
+	svcPort := int32(-1)
+	if backend.ResolveGranularity == "service" && svc.Spec.ClusterIP == "" {
+		log.Errorw("ApisixRoute refers to a headless service but want to use the service level resolve granularity",
+			zap.Any("namespace", ns),
+			zap.Any("service", svc),
+		)
+		return "", 0, errors.New("conflict headless service and backend resolve granularity")
+	}
+loop:
+	for _, port := range svc.Spec.Ports {
+		switch backend.ServicePort.Type {
+		case intstr.Int:
+			if backend.ServicePort.IntVal == port.Port {
+				svcPort = port.Port
+				break loop
+			}
+		case intstr.String:
+			if backend.ServicePort.StrVal == port.Name {
+				svcPort = port.Port
+				break loop
+			}
+		}
+	}
+	if svcPort == -1 {
+		log.Errorw("ApisixRoute refers to non-existent Service port",
+			zap.String("namespace", ns),
+			zap.String("port", backend.ServicePort.String()),
+		)
+		return "", 0, err
+	}
+
+	return svc.Spec.ClusterIP, svcPort, nil
+}
+
+// getStreamServiceClusterIPAndPortV2beta2 is for v2beta2 streamRoute
+func (t *translator) getStreamServiceClusterIPAndPortV2beta2(backend configv2beta2.ApisixRouteStreamBackend, ns string) (string, int32, error) {
+	svc, err := t.ServiceLister.Services(ns).Get(backend.ServiceName)
+	if err != nil {
+		return "", 0, err
+	}
+	svcPort := int32(-1)
+	if backend.ResolveGranularity == "service" && svc.Spec.ClusterIP == "" {
+		log.Errorw("ApisixRoute refers to a headless service but want to use the service level resolve granularity",
+			zap.String("ApisixRoute namespace", ns),
+			zap.Any("service", svc),
+		)
+		return "", 0, errors.New("conflict headless service and backend resolve granularity")
+	}
+loop:
+	for _, port := range svc.Spec.Ports {
+		switch backend.ServicePort.Type {
+		case intstr.Int:
+			if backend.ServicePort.IntVal == port.Port {
+				svcPort = port.Port
+				break loop
+			}
+		case intstr.String:
+			if backend.ServicePort.StrVal == port.Name {
+				svcPort = port.Port
+				break loop
+			}
+		}
+	}
+	if svcPort == -1 {
+		log.Errorw("ApisixRoute refers to non-existent Service port",
+			zap.String("ApisixRoute namespace", ns),
+			zap.String("port", backend.ServicePort.String()),
+		)
+		return "", 0, err
+	}
+
+	return svc.Spec.ClusterIP, svcPort, nil
+}
+
+// getStreamServiceClusterIPAndPortV2beta3 is for v2beta3 streamRoute
+func (t *translator) getStreamServiceClusterIPAndPortV2beta3(backend configv2beta3.ApisixRouteStreamBackend, ns string) (string, int32, error) {
+	svc, err := t.ServiceLister.Services(ns).Get(backend.ServiceName)
+	if err != nil {
+		return "", 0, err
+	}
+	svcPort := int32(-1)
+	if backend.ResolveGranularity == "service" && svc.Spec.ClusterIP == "" {
+		log.Errorw("ApisixRoute refers to a headless service but want to use the service level resolve granularity",
+			zap.String("ApisixRoute namespace", ns),
+			zap.Any("service", svc),
+		)
+		return "", 0, errors.New("conflict headless service and backend resolve granularity")
+	}
+loop:
+	for _, port := range svc.Spec.Ports {
+		switch backend.ServicePort.Type {
+		case intstr.Int:
+			if backend.ServicePort.IntVal == port.Port {
+				svcPort = port.Port
+				break loop
+			}
+		case intstr.String:
+			if backend.ServicePort.StrVal == port.Name {
+				svcPort = port.Port
+				break loop
+			}
+		}
+	}
+	if svcPort == -1 {
+		log.Errorw("ApisixRoute refers to non-existent Service port",
+			zap.String("ApisixRoute namespace", ns),
+			zap.String("port", backend.ServicePort.String()),
+		)
+		return "", 0, err
+	}
+
+	return svc.Spec.ClusterIP, svcPort, nil
+}
+
+// getStreamServiceClusterIPAndPortV2 is for v2 streamRoute
+func (t *translator) getStreamServiceClusterIPAndPortV2(backend configv2.ApisixRouteStreamBackend, ns string) (string, int32, error) {
+	svc, err := t.ServiceLister.Services(ns).Get(backend.ServiceName)
+	if err != nil {
+		return "", 0, err
+	}
+	svcPort := int32(-1)
+	if backend.ResolveGranularity == "service" && svc.Spec.ClusterIP == "" {
+		log.Errorw("ApisixRoute refers to a headless service but want to use the service level resolve granularity",
+			zap.String("ApisixRoute namespace", ns),
+			zap.Any("service", svc),
+		)
+		return "", 0, errors.New("conflict headless service and backend resolve granularity")
+	}
+loop:
+	for _, port := range svc.Spec.Ports {
+		switch backend.ServicePort.Type {
+		case intstr.Int:
+			if backend.ServicePort.IntVal == port.Port {
+				svcPort = port.Port
+				break loop
+			}
+		case intstr.String:
+			if backend.ServicePort.StrVal == port.Name {
+				svcPort = port.Port
+				break loop
+			}
+		}
+	}
+	if svcPort == -1 {
+		log.Errorw("ApisixRoute refers to non-existent Service port",
+			zap.String("ApisixRoute namespace", ns),
+			zap.String("port", backend.ServicePort.String()),
+		)
+		return "", 0, err
+	}
+
+	return svc.Spec.ClusterIP, svcPort, nil
+}
+
+func (t *translator) TranslateOldRoute(ar kube.ApisixRoute) (*translation.TranslateContext, error) {
+	switch ar.GroupVersion() {
+	case config.ApisixV2:
+		return t.translateOldRouteV2(ar.V2())
+	case config.ApisixV2beta3:
+		return t.translateOldRouteV2beta3(ar.V2beta3())
+	case config.ApisixV2beta2:
+		return translation.DefaultEmptyTranslateContext(), nil
+	default:
+		return nil, fmt.Errorf("translator: source group version not supported: %s", ar.GroupVersion())
+	}
+}
+
+func (t *translator) translateOldRouteV2(ar *configv2.ApisixRoute) (*translation.TranslateContext, error) {
+	oldCtx := translation.DefaultEmptyTranslateContext()
 
 	for _, part := range ar.Spec.Stream {
 		name := apisixv1.ComposeStreamRouteName(ar.Namespace, ar.Name, part.Name)
@@ -931,8 +1108,8 @@ func (t *translator) translateOldRouteV2(ar *configv2.ApisixRoute) (*TranslateCo
 	return oldCtx, nil
 }
 
-func (t *translator) translateOldRouteV2beta3(ar *configv2beta3.ApisixRoute) (*TranslateContext, error) {
-	oldCtx := DefaultEmptyTranslateContext()
+func (t *translator) translateOldRouteV2beta3(ar *configv2beta3.ApisixRoute) (*translation.TranslateContext, error) {
+	oldCtx := translation.DefaultEmptyTranslateContext()
 
 	for _, part := range ar.Spec.Stream {
 		name := apisixv1.ComposeStreamRouteName(ar.Namespace, ar.Name, part.Name)
diff --git a/pkg/kube/translation/apisix_route_test.go b/pkg/providers/apisix/translation/apisix_route_test.go
similarity index 98%
rename from pkg/kube/translation/apisix_route_test.go
rename to pkg/providers/apisix/translation/apisix_route_test.go
index cc6af876..57f8e8ed 100644
--- a/pkg/kube/translation/apisix_route_test.go
+++ b/pkg/providers/apisix/translation/apisix_route_test.go
@@ -35,6 +35,7 @@ import (
 	fakeapisix "github.com/apache/apisix-ingress-controller/pkg/kube/apisix/client/clientset/versioned/fake"
 	apisixinformers "github.com/apache/apisix-ingress-controller/pkg/kube/apisix/client/informers/externalversions"
 	_const "github.com/apache/apisix-ingress-controller/pkg/kube/apisix/const"
+	"github.com/apache/apisix-ingress-controller/pkg/providers/translation"
 	apisixv1 "github.com/apache/apisix-ingress-controller/pkg/types/apisix/v1"
 )
 
@@ -131,7 +132,7 @@ func TestRouteMatchExpr(t *testing.T) {
 			},
 		},
 	}
-	results, err := tr.translateRouteMatchExprs(exprs)
+	results, err := tr.TranslateRouteMatchExprs(exprs)
 	assert.Nil(t, err)
 	assert.Len(t, results, 10)
 
@@ -257,14 +258,17 @@ func mockTranslatorV2beta3(t *testing.T) (*translator, <-chan struct{}) {
 
 	tr := &translator{
 		&TranslatorOptions{
-			EndpointLister: epLister,
+			ServiceLister: svcLister,
+		},
+		translation.NewTranslator(&translation.TranslatorOptions{
 			ServiceLister:  svcLister,
+			EndpointLister: epLister,
 			ApisixUpstreamLister: kube.NewApisixUpstreamLister(
 				apisixInformersFactory.Apisix().V2beta3().ApisixUpstreams().Lister(),
 				apisixInformersFactory.Apisix().V2().ApisixUpstreams().Lister(),
 			),
 			APIVersion: config.ApisixV2beta3,
-		},
+		}),
 	}
 
 	processCh := make(chan struct{}, 2)
@@ -418,6 +422,7 @@ func TestTranslateApisixRouteV2beta3WithEmptyPluginConfigName(t *testing.T) {
 func TestTranslateApisixRouteV2beta3NotStrictly(t *testing.T) {
 	tr := &translator{
 		&TranslatorOptions{},
+		translation.NewTranslator(nil),
 	}
 	ar := &configv2beta3.ApisixRoute{
 		ObjectMeta: metav1.ObjectMeta{
diff --git a/pkg/kube/translation/apisix_ssl.go b/pkg/providers/apisix/translation/apisix_ssl.go
similarity index 65%
rename from pkg/kube/translation/apisix_ssl.go
rename to pkg/providers/apisix/translation/apisix_ssl.go
index 60450fa0..6eea68b3 100644
--- a/pkg/kube/translation/apisix_ssl.go
+++ b/pkg/providers/apisix/translation/apisix_ssl.go
@@ -15,31 +15,19 @@
 package translation
 
 import (
-	"errors"
-
-	v1 "k8s.io/api/core/v1"
-
 	"github.com/apache/apisix-ingress-controller/pkg/id"
 	configv2 "github.com/apache/apisix-ingress-controller/pkg/kube/apisix/apis/config/v2"
 	configv2beta3 "github.com/apache/apisix-ingress-controller/pkg/kube/apisix/apis/config/v2beta3"
+	"github.com/apache/apisix-ingress-controller/pkg/providers/translation"
 	apisixv1 "github.com/apache/apisix-ingress-controller/pkg/types/apisix/v1"
 )
 
-var (
-	// ErrUnknownSecretFormat means the secret doesn't contain required fields
-	ErrUnknownSecretFormat = errors.New("unknown secret format")
-	// ErrEmptyCert means the cert field in Kubernetes Secret is not found.
-	ErrEmptyCert = errors.New("missing cert field")
-	// ErrEmptyPrivKey means the key field in Kubernetes Secret is not found.
-	ErrEmptyPrivKey = errors.New("missing key field")
-)
-
 func (t *translator) TranslateSSLV2Beta3(tls *configv2beta3.ApisixTls) (*apisixv1.Ssl, error) {
 	s, err := t.SecretLister.Secrets(tls.Spec.Secret.Namespace).Get(tls.Spec.Secret.Name)
 	if err != nil {
 		return nil, err
 	}
-	cert, key, err := t.ExtractKeyPair(s, true)
+	cert, key, err := translation.ExtractKeyPair(s, true)
 	if err != nil {
 		return nil, err
 	}
@@ -63,7 +51,7 @@ func (t *translator) TranslateSSLV2Beta3(tls *configv2beta3.ApisixTls) (*apisixv
 		if err != nil {
 			return nil, err
 		}
-		ca, _, err := t.ExtractKeyPair(caSecret, false)
+		ca, _, err := translation.ExtractKeyPair(caSecret, false)
 		if err != nil {
 			return nil, err
 		}
@@ -81,7 +69,7 @@ func (t *translator) TranslateSSLV2(tls *configv2.ApisixTls) (*apisixv1.Ssl, err
 	if err != nil {
 		return nil, err
 	}
-	cert, key, err := t.ExtractKeyPair(s, true)
+	cert, key, err := translation.ExtractKeyPair(s, true)
 	if err != nil {
 		return nil, err
 	}
@@ -105,7 +93,7 @@ func (t *translator) TranslateSSLV2(tls *configv2.ApisixTls) (*apisixv1.Ssl, err
 		if err != nil {
 			return nil, err
 		}
-		ca, _, err := t.ExtractKeyPair(caSecret, false)
+		ca, _, err := translation.ExtractKeyPair(caSecret, false)
 		if err != nil {
 			return nil, err
 		}
@@ -117,45 +105,3 @@ func (t *translator) TranslateSSLV2(tls *configv2.ApisixTls) (*apisixv1.Ssl, err
 
 	return ssl, nil
 }
-
-func (t *translator) ExtractKeyPair(s *v1.Secret, hasPrivateKey bool) ([]byte, []byte, error) {
-	if _, ok := s.Data["cert"]; ok {
-		return t.extractApisixSecretKeyPair(s, hasPrivateKey)
-	} else if _, ok := s.Data[v1.TLSCertKey]; ok {
-		return t.extractKubeSecretKeyPair(s, hasPrivateKey)
-	} else {
-		return nil, nil, ErrUnknownSecretFormat
-	}
-}
-
-func (t *translator) extractApisixSecretKeyPair(s *v1.Secret, hasPrivateKey bool) (cert []byte, key []byte, err error) {
-	var ok bool
-	cert, ok = s.Data["cert"]
-	if !ok {
-		return nil, nil, ErrEmptyCert
-	}
-
-	if hasPrivateKey {
-		key, ok = s.Data["key"]
-		if !ok {
-			return nil, nil, ErrEmptyPrivKey
-		}
-	}
-	return
-}
-
-func (t *translator) extractKubeSecretKeyPair(s *v1.Secret, hasPrivateKey bool) (cert []byte, key []byte, err error) {
-	var ok bool
-	cert, ok = s.Data[v1.TLSCertKey]
-	if !ok {
-		return nil, nil, ErrEmptyCert
-	}
-
-	if hasPrivateKey {
-		key, ok = s.Data[v1.TLSPrivateKeyKey]
-		if !ok {
-			return nil, nil, ErrEmptyPrivKey
-		}
-	}
-	return
-}
diff --git a/pkg/providers/apisix/translation/apisix_upstream.go b/pkg/providers/apisix/translation/apisix_upstream.go
new file mode 100644
index 00000000..ac1e901b
--- /dev/null
+++ b/pkg/providers/apisix/translation/apisix_upstream.go
@@ -0,0 +1,48 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package translation
+
+import (
+	"github.com/apache/apisix-ingress-controller/pkg/id"
+	"github.com/apache/apisix-ingress-controller/pkg/providers/translation"
+	apisixv1 "github.com/apache/apisix-ingress-controller/pkg/types/apisix/v1"
+)
+
+// translateUpstreamNotStrictly translates Upstream nodes with a loose way, only generate ID and Name for delete Event.
+func (t *translator) translateUpstreamNotStrictly(namespace, svcName, subset string, svcPort int32) (*apisixv1.Upstream, error) {
+	ups := &apisixv1.Upstream{}
+	ups.Name = apisixv1.ComposeUpstreamName(namespace, svcName, subset, svcPort)
+	ups.ID = id.GenID(ups.Name)
+	return ups, nil
+}
+
+func (t *translator) translateService(namespace, svcName, subset, svcResolveGranularity, svcClusterIP string, svcPort int32) (*apisixv1.Upstream, error) {
+	ups, err := t.TranslateService(namespace, svcName, subset, svcPort)
+	if err != nil {
+		return nil, err
+	}
+	if svcResolveGranularity == "service" {
+		ups.Nodes = apisixv1.UpstreamNodes{
+			{
+				Host:   svcClusterIP,
+				Port:   int(svcPort),
+				Weight: translation.DefaultWeight,
+			},
+		}
+	}
+	ups.Name = apisixv1.ComposeUpstreamName(namespace, svcName, subset, svcPort)
+	ups.ID = id.GenID(ups.Name)
+	return ups, nil
+}
diff --git a/pkg/providers/apisix/translation/translator.go b/pkg/providers/apisix/translation/translator.go
new file mode 100644
index 00000000..ec4817c2
--- /dev/null
+++ b/pkg/providers/apisix/translation/translator.go
@@ -0,0 +1,106 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+package translation
+
+import (
+	listerscorev1 "k8s.io/client-go/listers/core/v1"
+
+	"github.com/apache/apisix-ingress-controller/pkg/apisix"
+	"github.com/apache/apisix-ingress-controller/pkg/kube"
+	configv2 "github.com/apache/apisix-ingress-controller/pkg/kube/apisix/apis/config/v2"
+	configv2beta2 "github.com/apache/apisix-ingress-controller/pkg/kube/apisix/apis/config/v2beta2"
+	configv2beta3 "github.com/apache/apisix-ingress-controller/pkg/kube/apisix/apis/config/v2beta3"
+	"github.com/apache/apisix-ingress-controller/pkg/providers/translation"
+	apisixv1 "github.com/apache/apisix-ingress-controller/pkg/types/apisix/v1"
+)
+
+type TranslatorOptions struct {
+	Apisix      apisix.APISIX
+	ClusterName string
+
+	ServiceLister listerscorev1.ServiceLister
+	SecretLister  listerscorev1.SecretLister
+}
+
+type translator struct {
+	*TranslatorOptions
+	translation.Translator
+}
+
+type ApisixTranslator interface {
+	translation.Translator
+
+	// TranslateRouteV2beta2 translates the configv2beta2.ApisixRoute object into several Route,
+	// and Upstream resources.
+	TranslateRouteV2beta2(*configv2beta2.ApisixRoute) (*translation.TranslateContext, error)
+	// TranslateRouteV2beta2NotStrictly translates the configv2beta2.ApisixRoute object into several Route,
+	// and Upstream  resources not strictly, only used for delete event.
+	TranslateRouteV2beta2NotStrictly(*configv2beta2.ApisixRoute) (*translation.TranslateContext, error)
+	// TranslateRouteV2beta3 translates the configv2beta3.ApisixRoute object into several Route,
+	// Upstream and PluginConfig resources.
+	TranslateRouteV2beta3(*configv2beta3.ApisixRoute) (*translation.TranslateContext, error)
+	// TranslateRouteV2beta3NotStrictly translates the configv2beta3.ApisixRoute object into several Route,
+	// Upstream and PluginConfig resources not strictly, only used for delete event.
+	TranslateRouteV2beta3NotStrictly(*configv2beta3.ApisixRoute) (*translation.TranslateContext, error)
+	// TranslateRouteV2 translates the configv2.ApisixRoute object into several Route,
+	// Upstream and PluginConfig resources.
+	TranslateRouteV2(*configv2.ApisixRoute) (*translation.TranslateContext, error)
+	// TranslateRouteV2NotStrictly translates the configv2.ApisixRoute object into several Route,
+	// Upstream and PluginConfig resources not strictly, only used for delete event.
+	TranslateRouteV2NotStrictly(*configv2.ApisixRoute) (*translation.TranslateContext, error)
+	// TranslateOldRoute get route and stream_route objects from cache
+	// Build upstream and plugin_config through route and stream_route
+	TranslateOldRoute(kube.ApisixRoute) (*translation.TranslateContext, error)
+	// TranslateSSLV2Beta3 translates the configv2beta3.ApisixTls object into the APISIX SSL resource.
+	TranslateSSLV2Beta3(*configv2beta3.ApisixTls) (*apisixv1.Ssl, error)
+	// TranslateSSLV2 translates the configv2.ApisixTls object into the APISIX SSL resource.
+	TranslateSSLV2(*configv2.ApisixTls) (*apisixv1.Ssl, error)
+	// TranslateClusterConfig translates the configv2beta3.ApisixClusterConfig object into the APISIX
+	// Global Rule resource.
+	TranslateClusterConfigV2beta3(*configv2beta3.ApisixClusterConfig) (*apisixv1.GlobalRule, error)
+	// TranslateClusterConfigV2 translates the configv2.ApisixClusterConfig object into the APISIX
+	// Global Rule resource.
+	TranslateClusterConfigV2(*configv2.ApisixClusterConfig) (*apisixv1.GlobalRule, error)
+	// TranslateApisixConsumer translates the configv2beta3.APisixConsumer object into the APISIX Consumer
+	// resource.
+	TranslateApisixConsumerV2beta3(*configv2beta3.ApisixConsumer) (*apisixv1.Consumer, error)
+	// TranslateApisixConsumerV2 translates the configv2beta3.APisixConsumer object into the APISIX Consumer
+	// resource.
+	TranslateApisixConsumerV2(ac *configv2.ApisixConsumer) (*apisixv1.Consumer, error)
+	// TranslatePluginConfigV2beta3 translates the configv2.ApisixPluginConfig object into several PluginConfig
+	// resources.
+	TranslatePluginConfigV2beta3(*configv2beta3.ApisixPluginConfig) (*translation.TranslateContext, error)
+	// TranslatePluginConfigV2beta3NotStrictly translates the configv2beta3.ApisixPluginConfig object into several PluginConfig
+	// resources not strictly, only used for delete event.
+	TranslatePluginConfigV2beta3NotStrictly(*configv2beta3.ApisixPluginConfig) (*translation.TranslateContext, error)
+	// TranslatePluginConfigV2 translates the configv2.ApisixPluginConfig object into several PluginConfig
+	// resources.
+	TranslatePluginConfigV2(*configv2.ApisixPluginConfig) (*translation.TranslateContext, error)
+	// TranslatePluginConfigV2NotStrictly translates the configv2.ApisixPluginConfig object into several PluginConfig
+	// resources not strictly, only used for delete event.
+	TranslatePluginConfigV2NotStrictly(*configv2.ApisixPluginConfig) (*translation.TranslateContext, error)
+
+	TranslateRouteMatchExprs(nginxVars []configv2.ApisixRouteHTTPMatchExpr) ([][]apisixv1.StringOrSlice, error)
+}
+
+func NewApisixTranslator(opts *TranslatorOptions, t translation.Translator) ApisixTranslator {
+	return &translator{
+		TranslatorOptions: opts,
+		Translator:        t,
+	}
+}
diff --git a/pkg/providers/controller.go b/pkg/providers/controller.go
new file mode 100644
index 00000000..1897253d
--- /dev/null
+++ b/pkg/providers/controller.go
@@ -0,0 +1,500 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package providers
+
+import (
+	"context"
+	"fmt"
+	"os"
+	"time"
+
+	"go.uber.org/zap"
+	v1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
+	"k8s.io/client-go/kubernetes/scheme"
+	typedcorev1 "k8s.io/client-go/kubernetes/typed/core/v1"
+	"k8s.io/client-go/tools/cache"
+	"k8s.io/client-go/tools/leaderelection"
+	"k8s.io/client-go/tools/leaderelection/resourcelock"
+	"k8s.io/client-go/tools/record"
+
+	"github.com/apache/apisix-ingress-controller/pkg/api"
+	"github.com/apache/apisix-ingress-controller/pkg/apisix"
+	"github.com/apache/apisix-ingress-controller/pkg/config"
+	"github.com/apache/apisix-ingress-controller/pkg/kube"
+	apisixscheme "github.com/apache/apisix-ingress-controller/pkg/kube/apisix/client/clientset/versioned/scheme"
+	"github.com/apache/apisix-ingress-controller/pkg/log"
+	"github.com/apache/apisix-ingress-controller/pkg/metrics"
+	apisixprovider "github.com/apache/apisix-ingress-controller/pkg/providers/apisix"
+	apisixtranslation "github.com/apache/apisix-ingress-controller/pkg/providers/apisix/translation"
+	"github.com/apache/apisix-ingress-controller/pkg/providers/gateway"
+	ingressprovider "github.com/apache/apisix-ingress-controller/pkg/providers/ingress"
+	"github.com/apache/apisix-ingress-controller/pkg/providers/k8s"
+	"github.com/apache/apisix-ingress-controller/pkg/providers/k8s/namespace"
+	"github.com/apache/apisix-ingress-controller/pkg/providers/k8s/pod"
+	"github.com/apache/apisix-ingress-controller/pkg/providers/translation"
+	providertypes "github.com/apache/apisix-ingress-controller/pkg/providers/types"
+	"github.com/apache/apisix-ingress-controller/pkg/providers/utils"
+)
+
+const (
+	// _component is used for event component
+	_component = "ApisixIngress"
+	// minimum interval for ingress sync to APISIX
+	_mininumApisixResourceSyncInterval = 60 * time.Second
+)
+
+// Controller is the ingress apisix controller object.
+type Controller struct {
+	name             string
+	namespace        string
+	cfg              *config.Config
+	apisix           apisix.APISIX
+	apiServer        *api.Server
+	MetricsCollector metrics.Collector
+	kubeClient       *kube.KubeClient
+	// recorder event
+	recorder record.EventRecorder
+
+	// leaderContextCancelFunc will be called when apisix-ingress-controller
+	// decides to give up its leader role.
+	leaderContextCancelFunc context.CancelFunc
+
+	translator       translation.Translator
+	apisixTranslator apisixtranslation.ApisixTranslator
+
+	informers *providertypes.ListerInformer
+
+	namespaceProvider namespace.WatchingNamespaceProvider
+	podProvider       pod.Provider
+	kubeProvider      k8s.Provider
+	gatewayProvider   *gateway.Provider
+	apisixProvider    apisixprovider.Provider
+	ingressProvider   ingressprovider.Provider
+}
+
+// NewController creates an ingress apisix controller object.
+func NewController(cfg *config.Config) (*Controller, error) {
+	podName := os.Getenv("POD_NAME")
+	podNamespace := os.Getenv("POD_NAMESPACE")
+	if podNamespace == "" {
+		podNamespace = "default"
+	}
+	client, err := apisix.NewClient()
+	if err != nil {
+		return nil, err
+	}
+
+	kubeClient, err := kube.NewKubeClient(cfg)
+	if err != nil {
+		return nil, err
+	}
+
+	apiSrv, err := api.NewServer(cfg)
+	if err != nil {
+		return nil, err
+	}
+
+	// recorder
+	utilruntime.Must(apisixscheme.AddToScheme(scheme.Scheme))
+	eventBroadcaster := record.NewBroadcaster()
+	eventBroadcaster.StartRecordingToSink(&typedcorev1.EventSinkImpl{Interface: kubeClient.Client.CoreV1().Events("")})
+
+	c := &Controller{
+		name:             podName,
+		namespace:        podNamespace,
+		cfg:              cfg,
+		apiServer:        apiSrv,
+		apisix:           client,
+		MetricsCollector: metrics.NewPrometheusCollector(),
+		kubeClient:       kubeClient,
+		recorder:         eventBroadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: _component}),
+	}
+	return c, nil
+}
+
+// Eventf implements the resourcelock.EventRecorder interface.
+func (c *Controller) Eventf(_ runtime.Object, eventType string, reason string, message string, _ ...interface{}) {
+	log.Infow(reason, zap.String("message", message), zap.String("event_type", eventType))
+}
+
+// Run launches the controller.
+func (c *Controller) Run(stop chan struct{}) error {
+	rootCtx, rootCancel := context.WithCancel(context.Background())
+	defer rootCancel()
+	go func() {
+		<-stop
+		rootCancel()
+	}()
+	c.MetricsCollector.ResetLeader(false)
+
+	go func() {
+		if err := c.apiServer.Run(rootCtx.Done()); err != nil {
+			log.Errorf("failed to launch API Server: %s", err)
+		}
+	}()
+
+	lock := &resourcelock.LeaseLock{
+		LeaseMeta: metav1.ObjectMeta{
+			Namespace: c.namespace,
+			Name:      c.cfg.Kubernetes.ElectionID,
+		},
+		Client: c.kubeClient.Client.CoordinationV1(),
+		LockConfig: resourcelock.ResourceLockConfig{
+			Identity:      c.name,
+			EventRecorder: c,
+		},
+	}
+	cfg := leaderelection.LeaderElectionConfig{
+		Lock:          lock,
+		LeaseDuration: 15 * time.Second,
+		RenewDeadline: 5 * time.Second,
+		RetryPeriod:   2 * time.Second,
+		Callbacks: leaderelection.LeaderCallbacks{
+			OnStartedLeading: c.run,
+			OnNewLeader: func(identity string) {
+				log.Warnf("found a new leader %s", identity)
+				if identity != c.name {
+					log.Infow("controller now is running as a candidate",
+						zap.String("namespace", c.namespace),
+						zap.String("pod", c.name),
+					)
+					c.MetricsCollector.ResetLeader(false)
+					// delete the old APISIX cluster, so that the cached state
+					// like synchronization won't be used next time the candidate
+					// becomes the leader again.
+					c.apisix.DeleteCluster(c.cfg.APISIX.DefaultClusterName)
+				}
+			},
+			OnStoppedLeading: func() {
+				log.Infow("controller now is running as a candidate",
+					zap.String("namespace", c.namespace),
+					zap.String("pod", c.name),
+				)
+				c.MetricsCollector.ResetLeader(false)
+				// delete the old APISIX cluster, so that the cached state
+				// like synchronization won't be used next time the candidate
+				// becomes the leader again.
+				c.apisix.DeleteCluster(c.cfg.APISIX.DefaultClusterName)
+			},
+		},
+		ReleaseOnCancel: true,
+		Name:            "ingress-apisix",
+	}
+	elector, err := leaderelection.NewLeaderElector(cfg)
+	if err != nil {
+		log.Errorf("failed to create leader elector: %s", err.Error())
+		return err
+	}
+
+election:
+	curCtx, cancel := context.WithCancel(rootCtx)
+	c.leaderContextCancelFunc = cancel
+	elector.Run(curCtx)
+	select {
+	case <-rootCtx.Done():
+		return nil
+	default:
+		goto election
+	}
+}
+
+func (c *Controller) initSharedInformers() *providertypes.ListerInformer {
+	kubeFactory := c.kubeClient.NewSharedIndexInformerFactory()
+	apisixFactory := c.kubeClient.NewAPISIXSharedIndexInformerFactory()
+
+	epLister, epInformer := kube.NewEndpointListerAndInformer(kubeFactory, c.cfg.Kubernetes.WatchEndpointSlices)
+	svcInformer := kubeFactory.Core().V1().Services().Informer()
+	svcLister := kubeFactory.Core().V1().Services().Lister()
+
+	var (
+		apisixUpstreamInformer cache.SharedIndexInformer
+		apisixTlsInformer      cache.SharedIndexInformer
+	)
+	switch c.cfg.Kubernetes.APIVersion {
+	case config.ApisixV2beta3:
+		apisixUpstreamInformer = apisixFactory.Apisix().V2beta3().ApisixUpstreams().Informer()
+		apisixTlsInformer = apisixFactory.Apisix().V2beta3().ApisixTlses().Informer()
+	case config.ApisixV2:
+		apisixUpstreamInformer = apisixFactory.Apisix().V2().ApisixUpstreams().Informer()
+		apisixTlsInformer = apisixFactory.Apisix().V2().ApisixTlses().Informer()
+	default:
+		panic(fmt.Errorf("unsupported API version %v", c.cfg.Kubernetes.APIVersion))
+	}
+
+	apisixUpstreamLister := kube.NewApisixUpstreamLister(
+		apisixFactory.Apisix().V2beta3().ApisixUpstreams().Lister(),
+		apisixFactory.Apisix().V2().ApisixUpstreams().Lister(),
+	)
+	apisixTlsLister := kube.NewApisixTlsLister(
+		apisixFactory.Apisix().V2beta3().ApisixTlses().Lister(),
+		apisixFactory.Apisix().V2().ApisixTlses().Lister(),
+	)
+
+	podInformer := kubeFactory.Core().V1().Pods().Informer()
+	podLister := kubeFactory.Core().V1().Pods().Lister()
+
+	secretInformer := kubeFactory.Core().V1().Secrets().Informer()
+	secretLister := kubeFactory.Core().V1().Secrets().Lister()
+
+	listerInformer := &providertypes.ListerInformer{
+		EpLister:               epLister,
+		EpInformer:             epInformer,
+		SvcLister:              svcLister,
+		SvcInformer:            svcInformer,
+		SecretLister:           secretLister,
+		SecretInformer:         secretInformer,
+		PodLister:              podLister,
+		PodInformer:            podInformer,
+		ApisixUpstreamLister:   apisixUpstreamLister,
+		ApisixUpstreamInformer: apisixUpstreamInformer,
+		ApisixTlsLister:        apisixTlsLister,
+		ApisixTlsInformer:      apisixTlsInformer,
+	}
+
+	return listerInformer
+}
+
+func (c *Controller) run(ctx context.Context) {
+	log.Infow("controller tries to leading ...",
+		zap.String("namespace", c.namespace),
+		zap.String("pod", c.name),
+	)
+
+	var cancelFunc context.CancelFunc
+	ctx, cancelFunc = context.WithCancel(ctx)
+	defer cancelFunc()
+
+	// give up leader
+	defer c.leaderContextCancelFunc()
+
+	clusterOpts := &apisix.ClusterOptions{
+		Name:             c.cfg.APISIX.DefaultClusterName,
+		AdminKey:         c.cfg.APISIX.DefaultClusterAdminKey,
+		BaseURL:          c.cfg.APISIX.DefaultClusterBaseURL,
+		MetricsCollector: c.MetricsCollector,
+	}
+	err := c.apisix.AddCluster(ctx, clusterOpts)
+	if err != nil && err != apisix.ErrDuplicatedCluster {
+		// TODO give up the leader role
+		log.Errorf("failed to add default cluster: %s", err)
+		return
+	}
+
+	if err := c.apisix.Cluster(c.cfg.APISIX.DefaultClusterName).HasSynced(ctx); err != nil {
+		// TODO give up the leader role
+		log.Errorf("failed to wait the default cluster to be ready: %s", err)
+
+		// re-create apisix cluster, used in next c.run
+		if err = c.apisix.UpdateCluster(ctx, clusterOpts); err != nil {
+			log.Errorf("failed to update default cluster: %s", err)
+			return
+		}
+		return
+	}
+
+	// Creation Phase
+
+	c.informers = c.initSharedInformers()
+	common := &providertypes.Common{
+		ListerInformer:   c.informers,
+		Config:           c.cfg,
+		APISIX:           c.apisix,
+		KubeClient:       c.kubeClient,
+		MetricsCollector: c.MetricsCollector,
+		Recorder:         c.recorder,
+	}
+
+	c.namespaceProvider, err = namespace.NewWatchingNamespaceProvider(ctx, c.kubeClient, c.cfg)
+	if err != nil {
+		ctx.Done()
+		return
+	}
+
+	c.podProvider, err = pod.NewProvider(common, c.namespaceProvider)
+	if err != nil {
+		ctx.Done()
+		return
+	}
+
+	c.translator = translation.NewTranslator(&translation.TranslatorOptions{
+		APIVersion:           c.cfg.Kubernetes.APIVersion,
+		EndpointLister:       c.informers.EpLister,
+		ServiceLister:        c.informers.SvcLister,
+		SecretLister:         c.informers.SecretLister,
+		PodLister:            c.informers.PodLister,
+		ApisixUpstreamLister: c.informers.ApisixUpstreamLister,
+		PodProvider:          c.podProvider,
+	})
+
+	c.apisixProvider, c.apisixTranslator, err = apisixprovider.NewProvider(common, c.namespaceProvider, c.translator)
+	if err != nil {
+		ctx.Done()
+		return
+	}
+
+	c.kubeProvider, err = k8s.NewProvider(common, c.translator, c.namespaceProvider, c.apisixProvider)
+	if err != nil {
+		ctx.Done()
+		return
+	}
+
+	c.ingressProvider, err = ingressprovider.NewProvider(common, c.namespaceProvider, c.translator, c.apisixTranslator)
+	if err != nil {
+		ctx.Done()
+		return
+	}
+
+	if c.cfg.Kubernetes.EnableGatewayAPI {
+		c.gatewayProvider, err = gateway.NewGatewayProvider(&gateway.ProviderOptions{
+			Cfg:               c.cfg,
+			APISIX:            c.apisix,
+			APISIXClusterName: c.cfg.APISIX.DefaultClusterName,
+			KubeTranslator:    c.translator,
+			RestConfig:        nil,
+			KubeClient:        c.kubeClient.Client,
+			MetricsCollector:  c.MetricsCollector,
+			NamespaceProvider: c.namespaceProvider,
+		})
+		if err != nil {
+			ctx.Done()
+			return
+		}
+	}
+
+	// Init Phase
+
+	if err = c.namespaceProvider.Init(ctx); err != nil {
+		ctx.Done()
+		return
+	}
+	if err = c.apisixProvider.Init(ctx); err != nil {
+		ctx.Done()
+		return
+	}
+
+	// Run Phase
+
+	e := utils.ParallelExecutor{}
+
+	e.Add(func() {
+		c.checkClusterHealth(ctx, cancelFunc)
+	})
+
+	e.Add(func() {
+		c.informers.Run(ctx)
+	})
+
+	e.Add(func() {
+		c.namespaceProvider.Run(ctx)
+	})
+
+	e.Add(func() {
+		c.kubeProvider.Run(ctx)
+	})
+
+	e.Add(func() {
+		c.apisixProvider.Run(ctx)
+	})
+
+	e.Add(func() {
+		c.ingressProvider.Run(ctx)
+	})
+
+	if c.cfg.Kubernetes.EnableGatewayAPI {
+		e.Add(func() {
+			c.gatewayProvider.Run(ctx)
+		})
+	}
+
+	e.Add(func() {
+		c.resourceSyncLoop(ctx, c.cfg.ApisixResourceSyncInterval.Duration)
+	})
+	c.MetricsCollector.ResetLeader(true)
+
+	log.Infow("controller now is running as leader",
+		zap.String("namespace", c.namespace),
+		zap.String("pod", c.name),
+	)
+
+	<-ctx.Done()
+	e.Wait()
+
+	for _, execErr := range e.Errors() {
+		log.Error(execErr.Error())
+	}
+	if len(e.Errors()) > 0 {
+		log.Error("Start failed, abort...")
+		cancelFunc()
+	}
+}
+
+func (c *Controller) checkClusterHealth(ctx context.Context, cancelFunc context.CancelFunc) {
+	defer cancelFunc()
+	t := time.NewTicker(5 * time.Second)
+	defer t.Stop()
+	for {
+		select {
+		case <-ctx.Done():
+			return
+		case <-t.C:
+		}
+
+		err := c.apisix.Cluster(c.cfg.APISIX.DefaultClusterName).HealthCheck(ctx)
+		if err != nil {
+			// Finally failed health check, then give up leader.
+			log.Warnf("failed to check health for default cluster: %s, give up leader", err)
+			c.apiServer.HealthState.Lock()
+			defer c.apiServer.HealthState.Unlock()
+
+			c.apiServer.HealthState.Err = err
+			return
+		}
+		log.Debugf("success check health for default cluster")
+		c.MetricsCollector.IncrCheckClusterHealth(c.name)
+	}
+}
+
+func (c *Controller) syncAllResources() {
+	e := utils.ParallelExecutor{}
+
+	e.Add(c.ingressProvider.ResourceSync)
+	e.Add(c.apisixProvider.ResourceSync)
+
+	e.Wait()
+}
+
+func (c *Controller) resourceSyncLoop(ctx context.Context, interval time.Duration) {
+	// The interval shall not be less than 60 seconds.
+	if interval < _mininumApisixResourceSyncInterval {
+		log.Warnw("The apisix-resource-sync-interval shall not be less than 60 seconds.",
+			zap.String("apisix-resource-sync-interval", interval.String()),
+		)
+		interval = _mininumApisixResourceSyncInterval
+	}
+	ticker := time.NewTicker(interval)
+	defer ticker.Stop()
+	for {
+		select {
+		case <-ticker.C:
+			c.syncAllResources()
+			continue
+		case <-ctx.Done():
+			return
+		}
+	}
+}
diff --git a/pkg/ingress/gateway/gateway.go b/pkg/providers/gateway/gateway.go
similarity index 99%
rename from pkg/ingress/gateway/gateway.go
rename to pkg/providers/gateway/gateway.go
index 171ea14a..f6fd345b 100644
--- a/pkg/ingress/gateway/gateway.go
+++ b/pkg/providers/gateway/gateway.go
@@ -27,8 +27,8 @@ import (
 	"k8s.io/client-go/util/workqueue"
 	gatewayv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
 
-	"github.com/apache/apisix-ingress-controller/pkg/ingress/utils"
 	"github.com/apache/apisix-ingress-controller/pkg/log"
+	"github.com/apache/apisix-ingress-controller/pkg/providers/utils"
 	"github.com/apache/apisix-ingress-controller/pkg/types"
 )
 
diff --git a/pkg/ingress/gateway/gateway_class.go b/pkg/providers/gateway/gateway_class.go
similarity index 100%
rename from pkg/ingress/gateway/gateway_class.go
rename to pkg/providers/gateway/gateway_class.go
diff --git a/pkg/ingress/gateway/gateway_httproute.go b/pkg/providers/gateway/gateway_httproute.go
similarity index 97%
rename from pkg/ingress/gateway/gateway_httproute.go
rename to pkg/providers/gateway/gateway_httproute.go
index 3fb60ba5..92d06090 100644
--- a/pkg/ingress/gateway/gateway_httproute.go
+++ b/pkg/providers/gateway/gateway_httproute.go
@@ -24,9 +24,9 @@ import (
 	"k8s.io/client-go/util/workqueue"
 	gatewayv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
 
-	"github.com/apache/apisix-ingress-controller/pkg/ingress/utils"
-	"github.com/apache/apisix-ingress-controller/pkg/kube/translation"
 	"github.com/apache/apisix-ingress-controller/pkg/log"
+	"github.com/apache/apisix-ingress-controller/pkg/providers/translation"
+	"github.com/apache/apisix-ingress-controller/pkg/providers/utils"
 	"github.com/apache/apisix-ingress-controller/pkg/types"
 )
 
diff --git a/pkg/ingress/gateway/gateway_tlsroute.go b/pkg/providers/gateway/gateway_tlsroute.go
similarity index 98%
rename from pkg/ingress/gateway/gateway_tlsroute.go
rename to pkg/providers/gateway/gateway_tlsroute.go
index 52f6af05..f70da715 100644
--- a/pkg/ingress/gateway/gateway_tlsroute.go
+++ b/pkg/providers/gateway/gateway_tlsroute.go
@@ -41,9 +41,9 @@ import (
 	"k8s.io/client-go/util/workqueue"
 	gatewayv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
 
-	"github.com/apache/apisix-ingress-controller/pkg/ingress/utils"
-	"github.com/apache/apisix-ingress-controller/pkg/kube/translation"
 	"github.com/apache/apisix-ingress-controller/pkg/log"
+	"github.com/apache/apisix-ingress-controller/pkg/providers/translation"
+	"github.com/apache/apisix-ingress-controller/pkg/providers/utils"
 	"github.com/apache/apisix-ingress-controller/pkg/types"
 )
 
diff --git a/pkg/ingress/gateway/provider.go b/pkg/providers/gateway/provider.go
similarity index 94%
rename from pkg/ingress/gateway/provider.go
rename to pkg/providers/gateway/provider.go
index ee3cb1b0..c8305753 100644
--- a/pkg/ingress/gateway/provider.go
+++ b/pkg/providers/gateway/provider.go
@@ -32,13 +32,13 @@ import (
 
 	"github.com/apache/apisix-ingress-controller/pkg/apisix"
 	"github.com/apache/apisix-ingress-controller/pkg/config"
-	gatewaytranslation "github.com/apache/apisix-ingress-controller/pkg/ingress/gateway/translation"
-	"github.com/apache/apisix-ingress-controller/pkg/ingress/gateway/types"
-	"github.com/apache/apisix-ingress-controller/pkg/ingress/namespace"
-	"github.com/apache/apisix-ingress-controller/pkg/ingress/utils"
 	"github.com/apache/apisix-ingress-controller/pkg/kube"
-	"github.com/apache/apisix-ingress-controller/pkg/kube/translation"
 	"github.com/apache/apisix-ingress-controller/pkg/metrics"
+	gatewaytranslation "github.com/apache/apisix-ingress-controller/pkg/providers/gateway/translation"
+	"github.com/apache/apisix-ingress-controller/pkg/providers/gateway/types"
+	"github.com/apache/apisix-ingress-controller/pkg/providers/k8s/namespace"
+	"github.com/apache/apisix-ingress-controller/pkg/providers/translation"
+	"github.com/apache/apisix-ingress-controller/pkg/providers/utils"
 )
 
 const (
@@ -87,7 +87,7 @@ type ProviderOptions struct {
 	RestConfig        *rest.Config
 	KubeClient        kubernetes.Interface
 	MetricsCollector  metrics.Collector
-	NamespaceProvider namespace.WatchingProvider
+	NamespaceProvider namespace.WatchingNamespaceProvider
 }
 
 func NewGatewayProvider(opts *ProviderOptions) (*Provider, error) {
@@ -154,15 +154,12 @@ func (p *Provider) Run(ctx context.Context) {
 	e.Add(func() {
 		p.gatewayInformer.Run(ctx.Done())
 	})
-
 	e.Add(func() {
 		p.gatewayClassInformer.Run(ctx.Done())
 	})
-
 	e.Add(func() {
 		p.gatewayHTTPRouteInformer.Run(ctx.Done())
 	})
-
 	e.Add(func() {
 		p.gatewayTLSRouteInformer.Run(ctx.Done())
 	})
@@ -170,15 +167,12 @@ func (p *Provider) Run(ctx context.Context) {
 	e.Add(func() {
 		p.gatewayController.run(ctx)
 	})
-
 	e.Add(func() {
 		p.gatewayClassController.run(ctx)
 	})
-
 	e.Add(func() {
 		p.gatewayHTTPRouteController.run(ctx)
 	})
-
 	e.Add(func() {
 		p.gatewayTLSRouteController.run(ctx)
 	})
diff --git a/pkg/ingress/gateway/translation/gateway.go b/pkg/providers/gateway/translation/gateway.go
similarity index 98%
rename from pkg/ingress/gateway/translation/gateway.go
rename to pkg/providers/gateway/translation/gateway.go
index edc79854..d8a70da4 100644
--- a/pkg/ingress/gateway/translation/gateway.go
+++ b/pkg/providers/gateway/translation/gateway.go
@@ -23,8 +23,8 @@ import (
 	"go.uber.org/zap"
 	gatewayv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
 
-	"github.com/apache/apisix-ingress-controller/pkg/ingress/gateway/types"
 	"github.com/apache/apisix-ingress-controller/pkg/log"
+	"github.com/apache/apisix-ingress-controller/pkg/providers/gateway/types"
 )
 
 const (
diff --git a/pkg/ingress/gateway/translation/gateway_httproute.go b/pkg/providers/gateway/translation/gateway_httproute.go
similarity index 96%
rename from pkg/ingress/gateway/translation/gateway_httproute.go
rename to pkg/providers/gateway/translation/gateway_httproute.go
index ed73a993..e1eaa657 100644
--- a/pkg/ingress/gateway/translation/gateway_httproute.go
+++ b/pkg/providers/gateway/translation/gateway_httproute.go
@@ -26,9 +26,9 @@ import (
 	gatewayv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
 
 	"github.com/apache/apisix-ingress-controller/pkg/id"
-	"github.com/apache/apisix-ingress-controller/pkg/ingress/utils"
-	"github.com/apache/apisix-ingress-controller/pkg/kube/translation"
 	"github.com/apache/apisix-ingress-controller/pkg/log"
+	"github.com/apache/apisix-ingress-controller/pkg/providers/translation"
+	"github.com/apache/apisix-ingress-controller/pkg/providers/utils"
 	apisixv1 "github.com/apache/apisix-ingress-controller/pkg/types/apisix/v1"
 )
 
@@ -92,7 +92,7 @@ func (t *translator) TranslateGatewayHTTPRouteV1Alpha2(httpRoute *gatewayv1alpha
 				continue
 			}
 
-			ups, err := t.KubeTranslator.TranslateUpstream(ns, string(backend.Name), "", int32(*backend.Port))
+			ups, err := t.KubeTranslator.TranslateService(ns, string(backend.Name), "", int32(*backend.Port))
 			if err != nil {
 				return nil, errors.Wrap(err, fmt.Sprintf("failed to translate Rules[%v].BackendRefs[%v]", i, j))
 			}
diff --git a/pkg/ingress/gateway/translation/gateway_httproute_test.go b/pkg/providers/gateway/translation/gateway_httproute_test.go
similarity index 99%
rename from pkg/ingress/gateway/translation/gateway_httproute_test.go
rename to pkg/providers/gateway/translation/gateway_httproute_test.go
index eec7643c..0df243d0 100644
--- a/pkg/ingress/gateway/translation/gateway_httproute_test.go
+++ b/pkg/providers/gateway/translation/gateway_httproute_test.go
@@ -31,7 +31,7 @@ import (
 	"github.com/apache/apisix-ingress-controller/pkg/kube"
 	fakeapisix "github.com/apache/apisix-ingress-controller/pkg/kube/apisix/client/clientset/versioned/fake"
 	apisixinformers "github.com/apache/apisix-ingress-controller/pkg/kube/apisix/client/informers/externalversions"
-	"github.com/apache/apisix-ingress-controller/pkg/kube/translation"
+	"github.com/apache/apisix-ingress-controller/pkg/providers/translation"
 	v1 "github.com/apache/apisix-ingress-controller/pkg/types/apisix/v1"
 )
 
diff --git a/pkg/ingress/gateway/translation/gateway_tlsroute.go b/pkg/providers/gateway/translation/gateway_tlsroute.go
similarity index 94%
rename from pkg/ingress/gateway/translation/gateway_tlsroute.go
rename to pkg/providers/gateway/translation/gateway_tlsroute.go
index 085cb92b..138cc40c 100644
--- a/pkg/ingress/gateway/translation/gateway_tlsroute.go
+++ b/pkg/providers/gateway/translation/gateway_tlsroute.go
@@ -26,9 +26,9 @@ import (
 	gatewayv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
 
 	"github.com/apache/apisix-ingress-controller/pkg/id"
-	"github.com/apache/apisix-ingress-controller/pkg/ingress/utils"
-	"github.com/apache/apisix-ingress-controller/pkg/kube/translation"
 	"github.com/apache/apisix-ingress-controller/pkg/log"
+	"github.com/apache/apisix-ingress-controller/pkg/providers/translation"
+	"github.com/apache/apisix-ingress-controller/pkg/providers/utils"
 	apisixv1 "github.com/apache/apisix-ingress-controller/pkg/types/apisix/v1"
 )
 
@@ -86,7 +86,7 @@ func (t *translator) TranslateGatewayTLSRouteV1Alpha2(tlsRoute *gatewayv1alpha2.
 				continue
 			}
 
-			ups, err := t.KubeTranslator.TranslateUpstream(ns, string(backend.Name), "", int32(*backend.Port))
+			ups, err := t.KubeTranslator.TranslateService(ns, string(backend.Name), "", int32(*backend.Port))
 			if err != nil {
 				return nil, errors.Wrap(err, fmt.Sprintf("failed to translate Rules[%v].BackendRefs[%v]", i, j))
 			}
diff --git a/pkg/ingress/gateway/translation/translator.go b/pkg/providers/gateway/translation/translator.go
similarity index 92%
rename from pkg/ingress/gateway/translation/translator.go
rename to pkg/providers/gateway/translation/translator.go
index 4ef17e9e..c0af96f7 100644
--- a/pkg/ingress/gateway/translation/translator.go
+++ b/pkg/providers/gateway/translation/translator.go
@@ -20,8 +20,8 @@ package gateway_translation
 import (
 	gatewayv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
 
-	"github.com/apache/apisix-ingress-controller/pkg/ingress/gateway/types"
-	"github.com/apache/apisix-ingress-controller/pkg/kube/translation"
+	"github.com/apache/apisix-ingress-controller/pkg/providers/gateway/types"
+	"github.com/apache/apisix-ingress-controller/pkg/providers/translation"
 )
 
 type TranslatorOptions struct {
diff --git a/pkg/ingress/gateway/types/types.go b/pkg/providers/gateway/types/types.go
similarity index 100%
rename from pkg/ingress/gateway/types/types.go
rename to pkg/providers/gateway/types/types.go
diff --git a/pkg/ingress/ingress.go b/pkg/providers/ingress/ingress.go
similarity index 63%
rename from pkg/ingress/ingress.go
rename to pkg/providers/ingress/ingress.go
index aea2b990..689dd0b4 100644
--- a/pkg/ingress/ingress.go
+++ b/pkg/providers/ingress/ingress.go
@@ -20,14 +20,19 @@ import (
 	"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"
 	k8serrors "k8s.io/apimachinery/pkg/api/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/client-go/tools/cache"
 	"k8s.io/client-go/util/workqueue"
 
-	"github.com/apache/apisix-ingress-controller/pkg/ingress/utils"
 	"github.com/apache/apisix-ingress-controller/pkg/kube"
 	"github.com/apache/apisix-ingress-controller/pkg/log"
+	"github.com/apache/apisix-ingress-controller/pkg/providers/utils"
 	"github.com/apache/apisix-ingress-controller/pkg/types"
 )
 
@@ -36,24 +41,32 @@ const (
 )
 
 type ingressController struct {
-	controller *Controller
-	workqueue  workqueue.RateLimitingInterface
-	workers    int
+	*ingressCommon
+
+	workqueue workqueue.RateLimitingInterface
+	workers   int
+
+	ingressLister   kube.IngressLister
+	ingressInformer cache.SharedIndexInformer
 }
 
-func (c *Controller) newIngressController() *ingressController {
-	ctl := &ingressController{
-		controller: c,
-		workqueue:  workqueue.NewNamedRateLimitingQueue(workqueue.NewItemFastSlowRateLimiter(1*time.Second, 60*time.Second, 5), "ingress"),
-		workers:    1,
+func newIngressController(common *ingressCommon, ingressLister kube.IngressLister, ingressInformer cache.SharedIndexInformer) *ingressController {
+	c := &ingressController{
+		ingressCommon: common,
+
+		workqueue: workqueue.NewNamedRateLimitingQueue(workqueue.NewItemFastSlowRateLimiter(1*time.Second, 60*time.Second, 5), "ingress"),
+		workers:   1,
+
+		ingressLister:   ingressLister,
+		ingressInformer: ingressInformer,
 	}
 
 	c.ingressInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
-		AddFunc:    ctl.onAdd,
-		UpdateFunc: ctl.onUpdate,
-		DeleteFunc: ctl.OnDelete,
+		AddFunc:    c.onAdd,
+		UpdateFunc: c.onUpdate,
+		DeleteFunc: c.OnDelete,
 	})
-	return ctl
+	return c
 }
 
 func (c *ingressController) run(ctx context.Context) {
@@ -61,7 +74,7 @@ func (c *ingressController) run(ctx context.Context) {
 	defer log.Infof("ingress controller exited")
 	defer c.workqueue.ShutDown()
 
-	if !cache.WaitForCacheSync(ctx.Done(), c.controller.ingressInformer.HasSynced) {
+	if !cache.WaitForCacheSync(ctx.Done(), c.ingressInformer.HasSynced) {
 		log.Errorf("cache sync failed")
 		return
 	}
@@ -94,11 +107,11 @@ func (c *ingressController) sync(ctx context.Context, ev *types.Event) error {
 	var ing kube.Ingress
 	switch ingEv.GroupVersion {
 	case kube.IngressV1:
-		ing, err = c.controller.ingressLister.V1(namespace, name)
+		ing, err = c.ingressLister.V1(namespace, name)
 	case kube.IngressV1beta1:
-		ing, err = c.controller.ingressLister.V1beta1(namespace, name)
+		ing, err = c.ingressLister.V1beta1(namespace, name)
 	case kube.IngressExtensionsV1beta1:
-		ing, err = c.controller.ingressLister.ExtensionsV1beta1(namespace, name)
+		ing, err = c.ingressLister.ExtensionsV1beta1(namespace, name)
 	default:
 		err = fmt.Errorf("unsupported group version %s, one of (%s/%s/%s) is expected", ingEv.GroupVersion,
 			kube.IngressV1, kube.IngressV1beta1, kube.IngressExtensionsV1beta1)
@@ -127,7 +140,7 @@ func (c *ingressController) sync(ctx context.Context, ev *types.Event) error {
 		ing = ev.Tombstone.(kube.Ingress)
 	}
 
-	tctx, err := c.controller.translator.TranslateIngress(ing)
+	tctx, err := c.translator.TranslateIngress(ing)
 	if err != nil {
 		log.Errorw("failed to translate ingress",
 			zap.Error(err),
@@ -162,7 +175,7 @@ func (c *ingressController) sync(ctx context.Context, ev *types.Event) error {
 	} else if ev.Type == types.EventAdd {
 		added = m
 	} else {
-		oldCtx, err := c.controller.translator.TranslateOldIngress(ingEv.OldObject)
+		oldCtx, err := c.translator.TranslateOldIngress(ingEv.OldObject)
 		if err != nil {
 			log.Errorw("failed to translate ingress",
 				zap.String("event", "update"),
@@ -179,7 +192,7 @@ func (c *ingressController) sync(ctx context.Context, ev *types.Event) error {
 		}
 		added, updated, deleted = m.Diff(om)
 	}
-	if err := c.controller.syncManifests(ctx, added, updated, deleted); err != nil {
+	if err := c.SyncManifests(ctx, added, updated, deleted); err != nil {
 		log.Errorw("failed to sync ingress artifacts",
 			zap.Error(err),
 		)
@@ -210,11 +223,11 @@ func (c *ingressController) handleSyncErr(obj interface{}, err error) {
 	var ing kube.Ingress
 	switch event.GroupVersion {
 	case kube.IngressV1:
-		ing, errLocal = c.controller.ingressLister.V1(namespace, name)
+		ing, errLocal = c.ingressLister.V1(namespace, name)
 	case kube.IngressV1beta1:
-		ing, errLocal = c.controller.ingressLister.V1beta1(namespace, name)
+		ing, errLocal = c.ingressLister.V1beta1(namespace, name)
 	case kube.IngressExtensionsV1beta1:
-		ing, errLocal = c.controller.ingressLister.ExtensionsV1beta1(namespace, name)
+		ing, errLocal = c.ingressLister.ExtensionsV1beta1(namespace, name)
 	}
 
 	if err == nil {
@@ -223,11 +236,11 @@ func (c *ingressController) handleSyncErr(obj interface{}, err error) {
 			if errLocal == nil {
 				switch ing.GroupVersion() {
 				case kube.IngressV1:
-					c.controller.recordStatus(ing.V1(), _resourceSynced, nil, metav1.ConditionTrue, ing.V1().GetGeneration())
+					c.recordStatus(ing.V1(), utils.ResourceSynced, nil, metav1.ConditionTrue, ing.V1().GetGeneration())
 				case kube.IngressV1beta1:
-					c.controller.recordStatus(ing.V1beta1(), _resourceSynced, nil, metav1.ConditionTrue, ing.V1beta1().GetGeneration())
+					c.recordStatus(ing.V1beta1(), utils.ResourceSynced, nil, metav1.ConditionTrue, ing.V1beta1().GetGeneration())
 				case kube.IngressExtensionsV1beta1:
-					c.controller.recordStatus(ing.ExtensionsV1beta1(), _resourceSynced, nil, metav1.ConditionTrue, ing.ExtensionsV1beta1().GetGeneration())
+					c.recordStatus(ing.ExtensionsV1beta1(), utils.ResourceSynced, nil, metav1.ConditionTrue, ing.ExtensionsV1beta1().GetGeneration())
 				}
 			} else {
 				log.Errorw("failed to list ingress resource",
@@ -236,7 +249,7 @@ func (c *ingressController) handleSyncErr(obj interface{}, err error) {
 			}
 		}
 		c.workqueue.Forget(obj)
-		c.controller.MetricsCollector.IncrSyncOperation("ingress", "success")
+		c.MetricsCollector.IncrSyncOperation("ingress", "success")
 		return
 	}
 	log.Warnw("sync ingress failed, will retry",
@@ -247,11 +260,11 @@ func (c *ingressController) handleSyncErr(obj interface{}, err error) {
 	if errLocal == nil {
 		switch ing.GroupVersion() {
 		case kube.IngressV1:
-			c.controller.recordStatus(ing.V1(), _resourceSyncAborted, err, metav1.ConditionTrue, ing.V1().GetGeneration())
+			c.recordStatus(ing.V1(), utils.ResourceSyncAborted, err, metav1.ConditionTrue, ing.V1().GetGeneration())
 		case kube.IngressV1beta1:
-			c.controller.recordStatus(ing.V1beta1(), _resourceSyncAborted, err, metav1.ConditionTrue, ing.V1beta1().GetGeneration())
+			c.recordStatus(ing.V1beta1(), utils.ResourceSyncAborted, err, metav1.ConditionTrue, ing.V1beta1().GetGeneration())
 		case kube.IngressExtensionsV1beta1:
-			c.controller.recordStatus(ing.ExtensionsV1beta1(), _resourceSyncAborted, err, metav1.ConditionTrue, ing.ExtensionsV1beta1().GetGeneration())
+			c.recordStatus(ing.ExtensionsV1beta1(), utils.ResourceSyncAborted, err, metav1.ConditionTrue, ing.ExtensionsV1beta1().GetGeneration())
 		}
 	} else {
 		log.Errorw("failed to list ingress resource",
@@ -259,7 +272,7 @@ func (c *ingressController) handleSyncErr(obj interface{}, err error) {
 		)
 	}
 	c.workqueue.AddRateLimited(obj)
-	c.controller.MetricsCollector.IncrSyncOperation("ingress", "failure")
+	c.MetricsCollector.IncrSyncOperation("ingress", "failure")
 }
 
 func (c *ingressController) onAdd(obj interface{}) {
@@ -268,7 +281,7 @@ func (c *ingressController) onAdd(obj interface{}) {
 		log.Errorf("found ingress resource with bad meta namespace key: %s", err)
 		return
 	}
-	if !c.controller.isWatchingNamespace(key) {
+	if !c.namespaceProvider.IsWatchingNamespace(key) {
 		return
 	}
 
@@ -293,7 +306,7 @@ func (c *ingressController) onAdd(obj interface{}) {
 		},
 	})
 
-	c.controller.MetricsCollector.IncrEvents("ingress", "add")
+	c.MetricsCollector.IncrEvents("ingress", "add")
 }
 
 func (c *ingressController) onUpdate(oldObj, newObj interface{}) {
@@ -308,7 +321,7 @@ func (c *ingressController) onUpdate(oldObj, newObj interface{}) {
 		log.Errorf("found ingress resource with bad meta namespace key: %s", err)
 		return
 	}
-	if !c.controller.isWatchingNamespace(key) {
+	if !c.namespaceProvider.IsWatchingNamespace(key) {
 		return
 	}
 	valid := c.isIngressEffective(curr)
@@ -334,7 +347,7 @@ func (c *ingressController) onUpdate(oldObj, newObj interface{}) {
 		},
 	})
 
-	c.controller.MetricsCollector.IncrEvents("ingress", "update")
+	c.MetricsCollector.IncrEvents("ingress", "update")
 }
 
 func (c *ingressController) OnDelete(obj interface{}) {
@@ -352,7 +365,7 @@ func (c *ingressController) OnDelete(obj interface{}) {
 		log.Errorf("found ingress resource with bad meta namespace key: %s", err)
 		return
 	}
-	if !c.controller.isWatchingNamespace(key) {
+	if !c.namespaceProvider.IsWatchingNamespace(key) {
 		return
 	}
 	valid := c.isIngressEffective(ing)
@@ -375,7 +388,7 @@ func (c *ingressController) OnDelete(obj interface{}) {
 		Tombstone: ing,
 	})
 
-	c.controller.MetricsCollector.IncrEvents("ingress", "delete")
+	c.MetricsCollector.IncrEvents("ingress", "delete")
 }
 
 func (c *ingressController) isIngressEffective(ing kube.Ingress) bool {
@@ -396,23 +409,23 @@ func (c *ingressController) isIngressEffective(ing kube.Ingress) bool {
 
 	// kubernetes.io/ingress.class takes the precedence.
 	if ica != "" {
-		return ica == c.controller.cfg.Kubernetes.IngressClass
+		return ica == c.Kubernetes.IngressClass
 	}
 	if ic != nil {
-		return *ic == c.controller.cfg.Kubernetes.IngressClass
+		return *ic == c.Kubernetes.IngressClass
 	}
 	return false
 }
 
 func (c *ingressController) ResourceSync() {
-	objs := c.controller.ingressInformer.GetIndexer().List()
+	objs := c.ingressInformer.GetIndexer().List()
 	for _, obj := range objs {
 		key, err := cache.MetaNamespaceKeyFunc(obj)
 		if err != nil {
 			log.Errorw("found Ingress resource with bad meta namespace key", zap.String("error", err.Error()))
 			continue
 		}
-		if !c.controller.isWatchingNamespace(key) {
+		if !c.namespaceProvider.IsWatchingNamespace(key) {
 			continue
 		}
 		ing := kube.MustNewIngress(obj)
@@ -425,3 +438,81 @@ func (c *ingressController) ResourceSync() {
 		})
 	}
 }
+
+// recordStatus record resources status
+func (c *ingressController) recordStatus(at interface{}, reason string, err error, status metav1.ConditionStatus, generation int64) {
+	client := c.KubeClient.Client
+
+	if kubeObj, ok := at.(runtime.Object); ok {
+		at = kubeObj.DeepCopyObject()
+	}
+
+	switch v := at.(type) {
+	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.ObjectMeta.Generation = generation
+		v.Status.LoadBalancer.Ingress = lbips
+		if _, errRecord := client.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.ObjectMeta.Generation = generation
+		v.Status.LoadBalancer.Ingress = lbips
+		if _, errRecord := client.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.ObjectMeta.Generation = generation
+		v.Status.LoadBalancer.Ingress = lbips
+		if _, errRecord := client.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)
+	}
+}
+
+// ingressLBStatusIPs organizes the available addresses
+func (c *ingressController) ingressLBStatusIPs() ([]apiv1.LoadBalancerIngress, error) {
+	return utils.IngressLBStatusIPs(c.IngressPublishService, c.IngressStatusAddress, c.KubeClient.Client)
+}
diff --git a/pkg/ingress/ingress_test.go b/pkg/providers/ingress/ingress_test.go
similarity index 95%
rename from pkg/ingress/ingress_test.go
rename to pkg/providers/ingress/ingress_test.go
index d83e422a..fecd3a58 100644
--- a/pkg/ingress/ingress_test.go
+++ b/pkg/providers/ingress/ingress_test.go
@@ -25,12 +25,15 @@ import (
 
 	"github.com/apache/apisix-ingress-controller/pkg/config"
 	"github.com/apache/apisix-ingress-controller/pkg/kube"
+	providertypes "github.com/apache/apisix-ingress-controller/pkg/providers/types"
 )
 
 func TestIsIngressEffective(t *testing.T) {
 	c := &ingressController{
-		controller: &Controller{
-			cfg: config.NewDefaultConfig(),
+		ingressCommon: &ingressCommon{
+			Common: &providertypes.Common{
+				Config: config.NewDefaultConfig(),
+			},
 		},
 	}
 	cn := "ingress"
diff --git a/pkg/providers/ingress/provider.go b/pkg/providers/ingress/provider.go
new file mode 100644
index 00000000..b0ec707f
--- /dev/null
+++ b/pkg/providers/ingress/provider.go
@@ -0,0 +1,118 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+package ingress
+
+import (
+	"context"
+
+	"k8s.io/client-go/tools/cache"
+
+	"github.com/apache/apisix-ingress-controller/pkg/config"
+	"github.com/apache/apisix-ingress-controller/pkg/kube"
+	apisixtranslation "github.com/apache/apisix-ingress-controller/pkg/providers/apisix/translation"
+	ingresstranslation "github.com/apache/apisix-ingress-controller/pkg/providers/ingress/translation"
+	"github.com/apache/apisix-ingress-controller/pkg/providers/k8s/namespace"
+	"github.com/apache/apisix-ingress-controller/pkg/providers/translation"
+	providertypes "github.com/apache/apisix-ingress-controller/pkg/providers/types"
+	"github.com/apache/apisix-ingress-controller/pkg/providers/utils"
+)
+
+const (
+	ProviderName = "Ingress"
+)
+
+type ingressCommon struct {
+	*providertypes.Common
+
+	namespaceProvider namespace.WatchingNamespaceProvider
+	translator        ingresstranslation.IngressTranslator
+}
+
+var _ Provider = (*ingressProvider)(nil)
+
+type Provider interface {
+	providertypes.Provider
+
+	ResourceSync()
+}
+
+type ingressProvider struct {
+	name string
+
+	ingressController *ingressController
+
+	ingressInformer cache.SharedIndexInformer
+}
+
+func NewProvider(common *providertypes.Common, namespaceProvider namespace.WatchingNamespaceProvider,
+	translator translation.Translator, apisixTranslator apisixtranslation.ApisixTranslator) (Provider, error) {
+	p := &ingressProvider{
+		name: ProviderName,
+	}
+
+	kubeFactory := common.KubeClient.NewSharedIndexInformerFactory()
+	switch common.Config.Kubernetes.IngressVersion {
+	case config.IngressNetworkingV1:
+		p.ingressInformer = kubeFactory.Networking().V1().Ingresses().Informer()
+	case config.IngressNetworkingV1beta1:
+		p.ingressInformer = kubeFactory.Networking().V1beta1().Ingresses().Informer()
+	default:
+		p.ingressInformer = kubeFactory.Extensions().V1beta1().Ingresses().Informer()
+	}
+	ingressLister := kube.NewIngressLister(
+		kubeFactory.Networking().V1().Ingresses().Lister(),
+		kubeFactory.Networking().V1beta1().Ingresses().Lister(),
+		kubeFactory.Extensions().V1beta1().Ingresses().Lister(),
+	)
+
+	c := &ingressCommon{
+		Common:            common,
+		namespaceProvider: namespaceProvider,
+		translator: ingresstranslation.NewIngressTranslator(&ingresstranslation.TranslatorOptions{
+			Apisix:        common.APISIX,
+			ClusterName:   common.Config.APISIX.DefaultClusterName,
+			ServiceLister: common.SvcLister,
+		}, translator, apisixTranslator),
+	}
+
+	p.ingressController = newIngressController(c, ingressLister, p.ingressInformer)
+
+	return p, nil
+}
+
+func (p *ingressProvider) Run(ctx context.Context) {
+	e := utils.ParallelExecutor{}
+
+	e.Add(func() {
+		p.ingressInformer.Run(ctx.Done())
+	})
+
+	e.Add(func() {
+		p.ingressController.run(ctx)
+	})
+
+	e.Wait()
+}
+
+func (p *ingressProvider) ResourceSync() {
+	e := utils.ParallelExecutor{}
+
+	e.Add(p.ingressController.ResourceSync)
+
+	e.Wait()
+}
diff --git a/pkg/kube/translation/annotations.go b/pkg/providers/ingress/translation/annotations.go
similarity index 91%
rename from pkg/kube/translation/annotations.go
rename to pkg/providers/ingress/translation/annotations.go
index 434ff260..6b9d3d81 100644
--- a/pkg/kube/translation/annotations.go
+++ b/pkg/providers/ingress/translation/annotations.go
@@ -17,8 +17,8 @@ package translation
 import (
 	"go.uber.org/zap"
 
-	"github.com/apache/apisix-ingress-controller/pkg/kube/translation/annotations"
 	"github.com/apache/apisix-ingress-controller/pkg/log"
+	"github.com/apache/apisix-ingress-controller/pkg/providers/ingress/translation/annotations"
 	apisix "github.com/apache/apisix-ingress-controller/pkg/types/apisix/v1"
 )
 
@@ -35,7 +35,7 @@ var (
 	}
 )
 
-func (t *translator) translateAnnotations(anno map[string]string) apisix.Plugins {
+func (t *translator) TranslateAnnotations(anno map[string]string) apisix.Plugins {
 	extractor := annotations.NewExtractor(anno)
 	plugins := make(apisix.Plugins)
 	for _, handler := range _handlers {
diff --git a/pkg/kube/translation/annotations/authorization.go b/pkg/providers/ingress/translation/annotations/authorization.go
similarity index 100%
rename from pkg/kube/translation/annotations/authorization.go
rename to pkg/providers/ingress/translation/annotations/authorization.go
diff --git a/pkg/kube/translation/annotations/cors.go b/pkg/providers/ingress/translation/annotations/cors.go
similarity index 100%
rename from pkg/kube/translation/annotations/cors.go
rename to pkg/providers/ingress/translation/annotations/cors.go
diff --git a/pkg/kube/translation/annotations/cors_test.go b/pkg/providers/ingress/translation/annotations/cors_test.go
similarity index 100%
rename from pkg/kube/translation/annotations/cors_test.go
rename to pkg/providers/ingress/translation/annotations/cors_test.go
diff --git a/pkg/kube/translation/annotations/csrf.go b/pkg/providers/ingress/translation/annotations/csrf.go
similarity index 100%
rename from pkg/kube/translation/annotations/csrf.go
rename to pkg/providers/ingress/translation/annotations/csrf.go
diff --git a/pkg/kube/translation/annotations/forward_auth.go b/pkg/providers/ingress/translation/annotations/forward_auth.go
similarity index 100%
rename from pkg/kube/translation/annotations/forward_auth.go
rename to pkg/providers/ingress/translation/annotations/forward_auth.go
diff --git a/pkg/kube/translation/annotations/forward_auth_test.go b/pkg/providers/ingress/translation/annotations/forward_auth_test.go
similarity index 100%
rename from pkg/kube/translation/annotations/forward_auth_test.go
rename to pkg/providers/ingress/translation/annotations/forward_auth_test.go
diff --git a/pkg/kube/translation/annotations/iprestriction.go b/pkg/providers/ingress/translation/annotations/iprestriction.go
similarity index 100%
rename from pkg/kube/translation/annotations/iprestriction.go
rename to pkg/providers/ingress/translation/annotations/iprestriction.go
diff --git a/pkg/kube/translation/annotations/iprestriction_test.go b/pkg/providers/ingress/translation/annotations/iprestriction_test.go
similarity index 100%
rename from pkg/kube/translation/annotations/iprestriction_test.go
rename to pkg/providers/ingress/translation/annotations/iprestriction_test.go
diff --git a/pkg/kube/translation/annotations/redirect.go b/pkg/providers/ingress/translation/annotations/redirect.go
similarity index 100%
rename from pkg/kube/translation/annotations/redirect.go
rename to pkg/providers/ingress/translation/annotations/redirect.go
diff --git a/pkg/kube/translation/annotations/rewrite.go b/pkg/providers/ingress/translation/annotations/rewrite.go
similarity index 100%
rename from pkg/kube/translation/annotations/rewrite.go
rename to pkg/providers/ingress/translation/annotations/rewrite.go
diff --git a/pkg/kube/translation/annotations/types.go b/pkg/providers/ingress/translation/annotations/types.go
similarity index 100%
rename from pkg/kube/translation/annotations/types.go
rename to pkg/providers/ingress/translation/annotations/types.go
diff --git a/pkg/kube/translation/ingress.go b/pkg/providers/ingress/translation/translator.go
similarity index 78%
rename from pkg/kube/translation/ingress.go
rename to pkg/providers/ingress/translation/translator.go
index 3c360b0b..48b2a87d 100644
--- a/pkg/kube/translation/ingress.go
+++ b/pkg/providers/ingress/translation/translator.go
@@ -1,17 +1,20 @@
-// Licensed to the Apache Software Foundation (ASF) under one or more
-// contributor license agreements.  See the NOTICE file distributed with
-// this work for additional information regarding copyright ownership.
-// The ASF licenses this file to You under the Apache License, Version 2.0
-// (the "License"); you may not use this file except in compliance with
-// the License.  You may obtain a copy of the License at
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
 //
-//     http://www.apache.org/licenses/LICENSE-2.0
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
 //
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
 package translation
 
 import (
@@ -26,24 +29,80 @@ import (
 	networkingv1beta1 "k8s.io/api/networking/v1beta1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/util/intstr"
+	listerscorev1 "k8s.io/client-go/listers/core/v1"
 
+	"github.com/apache/apisix-ingress-controller/pkg/apisix"
 	"github.com/apache/apisix-ingress-controller/pkg/id"
+	"github.com/apache/apisix-ingress-controller/pkg/kube"
 	configv2 "github.com/apache/apisix-ingress-controller/pkg/kube/apisix/apis/config/v2"
 	kubev2 "github.com/apache/apisix-ingress-controller/pkg/kube/apisix/apis/config/v2"
 	kubev2beta3 "github.com/apache/apisix-ingress-controller/pkg/kube/apisix/apis/config/v2beta3"
 	apisixconst "github.com/apache/apisix-ingress-controller/pkg/kube/apisix/const"
-	"github.com/apache/apisix-ingress-controller/pkg/kube/translation/annotations"
 	"github.com/apache/apisix-ingress-controller/pkg/log"
+	apisixtranslation "github.com/apache/apisix-ingress-controller/pkg/providers/apisix/translation"
+	"github.com/apache/apisix-ingress-controller/pkg/providers/ingress/translation/annotations"
+	"github.com/apache/apisix-ingress-controller/pkg/providers/translation"
 	apisixv1 "github.com/apache/apisix-ingress-controller/pkg/types/apisix/v1"
 )
 
+type TranslatorOptions struct {
+	Apisix      apisix.APISIX
+	ClusterName string
+
+	ServiceLister listerscorev1.ServiceLister
+}
+
+type translator struct {
+	*TranslatorOptions
+	translation.Translator
+	ApisixTranslator apisixtranslation.ApisixTranslator
+}
+
+type IngressTranslator interface {
+	// TranslateIngress composes a couple of APISIX Routes and upstreams according
+	// to the given Ingress resource.
+	// For old objects, you cannot use TranslateIngress to build. Because it needs to parse the latest service, which will cause data inconsistency.
+	TranslateIngress(ing kube.Ingress, args ...bool) (*translation.TranslateContext, error)
+	// TranslateOldIngress get route objects from cache
+	// Build upstream and plugin_config through route
+	TranslateOldIngress(kube.Ingress) (*translation.TranslateContext, error)
+}
+
+func NewIngressTranslator(opts *TranslatorOptions,
+	commonTranslator translation.Translator, apisixTranslator apisixtranslation.ApisixTranslator) IngressTranslator {
+	t := &translator{
+		TranslatorOptions: opts,
+		Translator:        commonTranslator,
+		ApisixTranslator:  apisixTranslator,
+	}
+
+	return t
+}
+
+func (t *translator) TranslateIngress(ing kube.Ingress, args ...bool) (*translation.TranslateContext, error) {
+	var skipVerify = false
+	if len(args) != 0 {
+		skipVerify = args[0]
+	}
+	switch ing.GroupVersion() {
+	case kube.IngressV1:
+		return t.translateIngressV1(ing.V1(), skipVerify)
+	case kube.IngressV1beta1:
+		return t.translateIngressV1beta1(ing.V1beta1(), skipVerify)
+	case kube.IngressExtensionsV1beta1:
+		return t.translateIngressExtensionsV1beta1(ing.ExtensionsV1beta1(), skipVerify)
+	default:
+		return nil, fmt.Errorf("translator: source group version not supported: %s", ing.GroupVersion())
+	}
+}
+
 const (
 	_regexPriority = 100
 )
 
-func (t *translator) translateIngressV1(ing *networkingv1.Ingress, skipVerify bool) (*TranslateContext, error) {
-	ctx := DefaultEmptyTranslateContext()
-	plugins := t.translateAnnotations(ing.Annotations)
+func (t *translator) translateIngressV1(ing *networkingv1.Ingress, skipVerify bool) (*translation.TranslateContext, error) {
+	ctx := translation.DefaultEmptyTranslateContext()
+	plugins := t.TranslateAnnotations(ing.Annotations)
 	annoExtractor := annotations.NewExtractor(ing.Annotations)
 	useRegex := annoExtractor.GetBoolAnnotation(annotations.AnnotationsPrefix + "use-regex")
 	enableWebsocket := annoExtractor.GetBoolAnnotation(annotations.AnnotationsPrefix + "enable-websocket")
@@ -69,7 +128,7 @@ func (t *translator) translateIngressV1(ing *networkingv1.Ingress, skipVerify bo
 			Name:      tls.SecretName,
 			Namespace: ing.Namespace,
 		}
-		ssl, err := t.TranslateSSLV2(&apisixTls)
+		ssl, err := t.ApisixTranslator.TranslateSSLV2(&apisixTls)
 		if err != nil {
 			log.Errorw("failed to translate ingress tls to apisix tls",
 				zap.Error(err),
@@ -138,7 +197,7 @@ func (t *translator) translateIngressV1(ing *networkingv1.Ingress, skipVerify bo
 			route.Uris = uris
 			route.EnableWebsocket = enableWebsocket
 			if len(nginxVars) > 0 {
-				routeVars, err := t.translateRouteMatchExprs(nginxVars)
+				routeVars, err := t.ApisixTranslator.TranslateRouteMatchExprs(nginxVars)
 				if err != nil {
 					return nil, err
 				}
@@ -161,9 +220,9 @@ func (t *translator) translateIngressV1(ing *networkingv1.Ingress, skipVerify bo
 	return ctx, nil
 }
 
-func (t *translator) translateIngressV1beta1(ing *networkingv1beta1.Ingress, skipVerify bool) (*TranslateContext, error) {
-	ctx := DefaultEmptyTranslateContext()
-	plugins := t.translateAnnotations(ing.Annotations)
+func (t *translator) translateIngressV1beta1(ing *networkingv1beta1.Ingress, skipVerify bool) (*translation.TranslateContext, error) {
+	ctx := translation.DefaultEmptyTranslateContext()
+	plugins := t.TranslateAnnotations(ing.Annotations)
 	annoExtractor := annotations.NewExtractor(ing.Annotations)
 	useRegex := annoExtractor.GetBoolAnnotation(annotations.AnnotationsPrefix + "use-regex")
 	enableWebsocket := annoExtractor.GetBoolAnnotation(annotations.AnnotationsPrefix + "enable-websocket")
@@ -189,7 +248,7 @@ func (t *translator) translateIngressV1beta1(ing *networkingv1beta1.Ingress, ski
 			Name:      tls.SecretName,
 			Namespace: ing.Namespace,
 		}
-		ssl, err := t.TranslateSSLV2Beta3(&apisixTls)
+		ssl, err := t.ApisixTranslator.TranslateSSLV2Beta3(&apisixTls)
 		if err != nil {
 			log.Errorw("failed to translate ingress tls to apisix tls",
 				zap.Error(err),
@@ -258,7 +317,7 @@ func (t *translator) translateIngressV1beta1(ing *networkingv1beta1.Ingress, ski
 			route.Uris = uris
 			route.EnableWebsocket = enableWebsocket
 			if len(nginxVars) > 0 {
-				routeVars, err := t.translateRouteMatchExprs(nginxVars)
+				routeVars, err := t.ApisixTranslator.TranslateRouteMatchExprs(nginxVars)
 				if err != nil {
 					return nil, err
 				}
@@ -318,15 +377,15 @@ func (t *translator) translateUpstreamFromIngressV1(namespace string, backend *n
 			}
 		}
 		if svcPort == 0 {
-			return nil, &translateError{
-				field:  "service",
-				reason: "port not found",
+			return nil, &translation.TranslateError{
+				Field:  "service",
+				Reason: "port not found",
 			}
 		}
 	} else {
 		svcPort = backend.Port.Number
 	}
-	ups, err := t.TranslateUpstream(namespace, backend.Name, "", svcPort)
+	ups, err := t.TranslateService(namespace, backend.Name, "", svcPort)
 	if err != nil {
 		return nil, err
 	}
@@ -335,9 +394,9 @@ func (t *translator) translateUpstreamFromIngressV1(namespace string, backend *n
 	return ups, nil
 }
 
-func (t *translator) translateIngressExtensionsV1beta1(ing *extensionsv1beta1.Ingress, skipVerify bool) (*TranslateContext, error) {
-	ctx := DefaultEmptyTranslateContext()
-	plugins := t.translateAnnotations(ing.Annotations)
+func (t *translator) translateIngressExtensionsV1beta1(ing *extensionsv1beta1.Ingress, skipVerify bool) (*translation.TranslateContext, error) {
+	ctx := translation.DefaultEmptyTranslateContext()
+	plugins := t.TranslateAnnotations(ing.Annotations)
 	annoExtractor := annotations.NewExtractor(ing.Annotations)
 	useRegex := annoExtractor.GetBoolAnnotation(annotations.AnnotationsPrefix + "use-regex")
 	enableWebsocket := annoExtractor.GetBoolAnnotation(annotations.AnnotationsPrefix + "enable-websocket")
@@ -403,7 +462,7 @@ func (t *translator) translateIngressExtensionsV1beta1(ing *extensionsv1beta1.In
 			route.Uris = uris
 			route.EnableWebsocket = enableWebsocket
 			if len(nginxVars) > 0 {
-				routeVars, err := t.translateRouteMatchExprs(nginxVars)
+				routeVars, err := t.ApisixTranslator.TranslateRouteMatchExprs(nginxVars)
 				if err != nil {
 					return nil, err
 				}
@@ -464,15 +523,15 @@ func (t *translator) translateUpstreamFromIngressV1beta1(namespace string, svcNa
 			}
 		}
 		if portNumber == 0 {
-			return nil, &translateError{
-				field:  "service",
-				reason: "port not found",
+			return nil, &translation.TranslateError{
+				Field:  "service",
+				Reason: "port not found",
 			}
 		}
 	} else {
 		portNumber = svcPort.IntVal
 	}
-	ups, err := t.TranslateUpstream(namespace, svcName, "", portNumber)
+	ups, err := t.TranslateService(namespace, svcName, "", portNumber)
 	if err != nil {
 		return nil, err
 	}
@@ -481,8 +540,21 @@ func (t *translator) translateUpstreamFromIngressV1beta1(namespace string, svcNa
 	return ups, nil
 }
 
-func (t *translator) translateOldIngressV1(ing *networkingv1.Ingress) (*TranslateContext, error) {
-	oldCtx := DefaultEmptyTranslateContext()
+func (t *translator) TranslateOldIngress(ing kube.Ingress) (*translation.TranslateContext, error) {
+	switch ing.GroupVersion() {
+	case kube.IngressV1:
+		return t.translateOldIngressV1(ing.V1())
+	case kube.IngressV1beta1:
+		return t.translateOldIngressV1beta1(ing.V1beta1())
+	case kube.IngressExtensionsV1beta1:
+		return t.translateOldIngressExtensionsv1beta1(ing.ExtensionsV1beta1())
+	default:
+		return nil, fmt.Errorf("translator: source group version not supported: %s", ing.GroupVersion())
+	}
+}
+
+func (t *translator) translateOldIngressV1(ing *networkingv1.Ingress) (*translation.TranslateContext, error) {
+	oldCtx := translation.DefaultEmptyTranslateContext()
 
 	for _, tls := range ing.Spec.TLS {
 		apisixTls := configv2.ApisixTls{
@@ -503,7 +575,7 @@ func (t *translator) translateOldIngressV1(ing *networkingv1.Ingress) (*Translat
 			Name:      tls.SecretName,
 			Namespace: ing.Namespace,
 		}
-		ssl, err := t.TranslateSSLV2(&apisixTls)
+		ssl, err := t.ApisixTranslator.TranslateSSLV2(&apisixTls)
 		if err != nil {
 			log.Debugw("failed to translate ingress tls to apisix tls",
 				zap.Error(err),
@@ -536,8 +608,8 @@ func (t *translator) translateOldIngressV1(ing *networkingv1.Ingress) (*Translat
 	return oldCtx, nil
 }
 
-func (t *translator) translateOldIngressV1beta1(ing *networkingv1beta1.Ingress) (*TranslateContext, error) {
-	oldCtx := DefaultEmptyTranslateContext()
+func (t *translator) translateOldIngressV1beta1(ing *networkingv1beta1.Ingress) (*translation.TranslateContext, error) {
+	oldCtx := translation.DefaultEmptyTranslateContext()
 
 	for _, tls := range ing.Spec.TLS {
 		apisixTls := configv2.ApisixTls{
@@ -558,7 +630,7 @@ func (t *translator) translateOldIngressV1beta1(ing *networkingv1beta1.Ingress)
 			Name:      tls.SecretName,
 			Namespace: ing.Namespace,
 		}
-		ssl, err := t.TranslateSSLV2(&apisixTls)
+		ssl, err := t.ApisixTranslator.TranslateSSLV2(&apisixTls)
 		if err != nil {
 			continue
 		}
@@ -587,8 +659,8 @@ func (t *translator) translateOldIngressV1beta1(ing *networkingv1beta1.Ingress)
 	return oldCtx, nil
 }
 
-func (t *translator) translateOldIngressExtensionsv1beta1(ing *extensionsv1beta1.Ingress) (*TranslateContext, error) {
-	oldCtx := DefaultEmptyTranslateContext()
+func (t *translator) translateOldIngressExtensionsv1beta1(ing *extensionsv1beta1.Ingress) (*translation.TranslateContext, error) {
+	oldCtx := translation.DefaultEmptyTranslateContext()
 
 	for _, tls := range ing.Spec.TLS {
 		apisixTls := configv2.ApisixTls{
@@ -609,7 +681,7 @@ func (t *translator) translateOldIngressExtensionsv1beta1(ing *extensionsv1beta1
 			Name:      tls.SecretName,
 			Namespace: ing.Namespace,
 		}
-		ssl, err := t.TranslateSSLV2(&apisixTls)
+		ssl, err := t.ApisixTranslator.TranslateSSLV2(&apisixTls)
 		if err != nil {
 			continue
 		}
diff --git a/pkg/providers/k8s/endpoint/base.go b/pkg/providers/k8s/endpoint/base.go
new file mode 100644
index 00000000..ac67aa87
--- /dev/null
+++ b/pkg/providers/k8s/endpoint/base.go
@@ -0,0 +1,133 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+package endpoint
+
+import (
+	"context"
+	"fmt"
+
+	"go.uber.org/zap"
+	k8serrors "k8s.io/apimachinery/pkg/api/errors"
+	listerscorev1 "k8s.io/client-go/listers/core/v1"
+
+	"github.com/apache/apisix-ingress-controller/pkg/config"
+	"github.com/apache/apisix-ingress-controller/pkg/kube"
+	configv2 "github.com/apache/apisix-ingress-controller/pkg/kube/apisix/apis/config/v2"
+	configv2beta3 "github.com/apache/apisix-ingress-controller/pkg/kube/apisix/apis/config/v2beta3"
+	"github.com/apache/apisix-ingress-controller/pkg/log"
+	"github.com/apache/apisix-ingress-controller/pkg/providers/translation"
+	providertypes "github.com/apache/apisix-ingress-controller/pkg/providers/types"
+	apisixv1 "github.com/apache/apisix-ingress-controller/pkg/types/apisix/v1"
+)
+
+type baseEndpointController struct {
+	*providertypes.Common
+	translator translation.Translator
+
+	apisixUpstreamLister kube.ApisixUpstreamLister
+	svcLister            listerscorev1.ServiceLister
+}
+
+func (c *baseEndpointController) syncEndpoint(ctx context.Context, ep kube.Endpoint) error {
+	log.Debugw("endpoint controller syncing endpoint",
+		zap.Any("endpoint", ep),
+	)
+
+	namespace, err := ep.Namespace()
+	if err != nil {
+		return err
+	}
+	svcName := ep.ServiceName()
+	svc, err := c.svcLister.Services(namespace).Get(svcName)
+	if err != nil {
+		if k8serrors.IsNotFound(err) {
+			log.Infof("service %s/%s not found", namespace, svcName)
+			return nil
+		}
+		log.Errorf("failed to get service %s/%s: %s", namespace, svcName, err)
+		return err
+	}
+
+	switch c.Kubernetes.APIVersion {
+	case config.ApisixV2beta3:
+		var subsets []configv2beta3.ApisixUpstreamSubset
+		subsets = append(subsets, configv2beta3.ApisixUpstreamSubset{})
+		auKube, err := c.apisixUpstreamLister.V2beta3(namespace, svcName)
+		if err != nil {
+			if !k8serrors.IsNotFound(err) {
+				log.Errorf("failed to get ApisixUpstream %s/%s: %s", namespace, svcName, err)
+				return err
+			}
+		} else if auKube.V2beta3().Spec != nil && len(auKube.V2beta3().Spec.Subsets) > 0 {
+			subsets = append(subsets, auKube.V2beta3().Spec.Subsets...)
+		}
+		clusters := c.APISIX.ListClusters()
+		for _, port := range svc.Spec.Ports {
+			for _, subset := range subsets {
+				nodes, err := c.translator.TranslateEndpoint(ep, port.Port, subset.Labels)
+				if err != nil {
+					log.Errorw("failed to translate upstream nodes",
+						zap.Error(err),
+						zap.Any("endpoints", ep),
+						zap.Int32("port", port.Port),
+					)
+				}
+				name := apisixv1.ComposeUpstreamName(namespace, svcName, subset.Name, port.Port)
+				for _, cluster := range clusters {
+					if err := c.SyncUpstreamNodesChangeToCluster(ctx, cluster, nodes, name); err != nil {
+						return err
+					}
+				}
+			}
+		}
+	case config.ApisixV2:
+		var subsets []configv2.ApisixUpstreamSubset
+		subsets = append(subsets, configv2.ApisixUpstreamSubset{})
+		auKube, err := c.apisixUpstreamLister.V2(namespace, svcName)
+		if err != nil {
+			if !k8serrors.IsNotFound(err) {
+				log.Errorf("failed to get ApisixUpstream %s/%s: %s", namespace, svcName, err)
+				return err
+			}
+		} else if auKube.V2().Spec != nil && len(auKube.V2().Spec.Subsets) > 0 {
+			subsets = append(subsets, auKube.V2().Spec.Subsets...)
+		}
+		clusters := c.APISIX.ListClusters()
+		for _, port := range svc.Spec.Ports {
+			for _, subset := range subsets {
+				nodes, err := c.translator.TranslateEndpoint(ep, port.Port, subset.Labels)
+				if err != nil {
+					log.Errorw("failed to translate upstream nodes",
+						zap.Error(err),
+						zap.Any("endpoints", ep),
+						zap.Int32("port", port.Port),
+					)
+				}
+				name := apisixv1.ComposeUpstreamName(namespace, svcName, subset.Name, port.Port)
+				for _, cluster := range clusters {
+					if err := c.SyncUpstreamNodesChangeToCluster(ctx, cluster, nodes, name); err != nil {
+						return err
+					}
+				}
+			}
+		}
+	default:
+		panic(fmt.Errorf("unsupported ApisixUpstream version %v", c.Kubernetes.APIVersion))
+	}
+	return nil
+}
diff --git a/pkg/ingress/endpoint.go b/pkg/providers/k8s/endpoint/endpoint.go
similarity index 75%
rename from pkg/ingress/endpoint.go
rename to pkg/providers/k8s/endpoint/endpoint.go
index a3c62ff0..9b14bf12 100644
--- a/pkg/ingress/endpoint.go
+++ b/pkg/providers/k8s/endpoint/endpoint.go
@@ -12,7 +12,7 @@
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 // See the License for the specific language governing permissions and
 // limitations under the License.
-package ingress
+package endpoint
 
 import (
 	"context"
@@ -26,24 +26,37 @@ import (
 
 	"github.com/apache/apisix-ingress-controller/pkg/kube"
 	"github.com/apache/apisix-ingress-controller/pkg/log"
+	"github.com/apache/apisix-ingress-controller/pkg/providers/k8s/namespace"
 	"github.com/apache/apisix-ingress-controller/pkg/types"
 	v1 "github.com/apache/apisix-ingress-controller/pkg/types/apisix/v1"
 )
 
 type endpointsController struct {
-	controller *Controller
-	workqueue  workqueue.RateLimitingInterface
-	workers    int
+	*baseEndpointController
+
+	workqueue workqueue.RateLimitingInterface
+	workers   int
+
+	namespaceProvider namespace.WatchingNamespaceProvider
+
+	epInformer cache.SharedIndexInformer
+	epLister   kube.EndpointLister
 }
 
-func (c *Controller) newEndpointsController() *endpointsController {
+func newEndpointsController(base *baseEndpointController, namespaceProvider namespace.WatchingNamespaceProvider) *endpointsController {
 	ctl := &endpointsController{
-		controller: c,
-		workqueue:  workqueue.NewNamedRateLimitingQueue(workqueue.NewItemFastSlowRateLimiter(1*time.Second, 60*time.Second, 5), "endpoints"),
-		workers:    1,
+		baseEndpointController: base,
+
+		workqueue: workqueue.NewNamedRateLimitingQueue(workqueue.NewItemFastSlowRateLimiter(1*time.Second, 60*time.Second, 5), "endpoints"),
+		workers:   1,
+
+		namespaceProvider: namespaceProvider,
+
+		epLister:   base.EpLister,
+		epInformer: base.EpInformer,
 	}
 
-	ctl.controller.epInformer.AddEventHandler(
+	ctl.epInformer.AddEventHandler(
 		cache.ResourceEventHandlerFuncs{
 			AddFunc:    ctl.onAdd,
 			UpdateFunc: ctl.onUpdate,
@@ -59,7 +72,7 @@ func (c *endpointsController) run(ctx context.Context) {
 	defer log.Info("endpoints controller exited")
 	defer c.workqueue.ShutDown()
 
-	if ok := cache.WaitForCacheSync(ctx.Done(), c.controller.epInformer.HasSynced); !ok {
+	if ok := cache.WaitForCacheSync(ctx.Done(), c.epInformer.HasSynced); !ok {
 		log.Error("informers sync failed")
 		return
 	}
@@ -91,8 +104,8 @@ func (c *endpointsController) sync(ctx context.Context, ev *types.Event) error {
 		return err
 	}
 	if ev.Type == types.EventDelete {
-		clusterName := c.controller.cfg.APISIX.DefaultClusterName
-		err = c.controller.apisix.Cluster(clusterName).UpstreamServiceRelation().Delete(ctx,
+		clusterName := c.Config.APISIX.DefaultClusterName
+		err = c.APISIX.Cluster(clusterName).UpstreamServiceRelation().Delete(ctx,
 			&v1.UpstreamServiceRelation{
 				ServiceName: ns + "_" + ep.ServiceName(),
 			})
@@ -100,20 +113,20 @@ func (c *endpointsController) sync(ctx context.Context, ev *types.Event) error {
 			return err
 		}
 	}
-	newestEp, err := c.controller.epLister.GetEndpoint(ns, ep.ServiceName())
+	newestEp, err := c.epLister.GetEndpoint(ns, ep.ServiceName())
 	if err != nil {
 		if !errors.IsNotFound(err) {
 			return err
 		}
 		newestEp = ep
 	}
-	return c.controller.syncEndpoint(ctx, newestEp)
+	return c.syncEndpoint(ctx, newestEp)
 }
 
 func (c *endpointsController) handleSyncErr(obj interface{}, err error) {
 	if err == nil {
 		c.workqueue.Forget(obj)
-		c.controller.MetricsCollector.IncrSyncOperation("endpoints", "success")
+		c.MetricsCollector.IncrSyncOperation("endpoints", "success")
 		return
 	}
 	event := obj.(*types.Event)
@@ -129,7 +142,7 @@ func (c *endpointsController) handleSyncErr(obj interface{}, err error) {
 		zap.Any("object", obj),
 	)
 	c.workqueue.AddRateLimited(obj)
-	c.controller.MetricsCollector.IncrSyncOperation("endpoints", "failure")
+	c.MetricsCollector.IncrSyncOperation("endpoints", "failure")
 }
 
 func (c *endpointsController) onAdd(obj interface{}) {
@@ -138,7 +151,7 @@ func (c *endpointsController) onAdd(obj interface{}) {
 		log.Errorf("found endpoints object with bad namespace/name: %s, ignore it", err)
 		return
 	}
-	if !c.controller.isWatchingNamespace(key) {
+	if !c.namespaceProvider.IsWatchingNamespace(key) {
 		return
 	}
 	log.Debugw("endpoints add event arrived",
@@ -150,7 +163,7 @@ func (c *endpointsController) onAdd(obj interface{}) {
 		Object: kube.NewEndpoint(obj.(*corev1.Endpoints)),
 	})
 
-	c.controller.MetricsCollector.IncrEvents("endpoints", "add")
+	c.MetricsCollector.IncrEvents("endpoints", "add")
 }
 
 func (c *endpointsController) onUpdate(prev, curr interface{}) {
@@ -165,7 +178,7 @@ func (c *endpointsController) onUpdate(prev, curr interface{}) {
 		log.Errorf("found endpoints object with bad namespace/name: %s, ignore it", err)
 		return
 	}
-	if !c.controller.isWatchingNamespace(key) {
+	if !c.namespaceProvider.IsWatchingNamespace(key) {
 		return
 	}
 	log.Debugw("endpoints update event arrived",
@@ -178,7 +191,7 @@ func (c *endpointsController) onUpdate(prev, curr interface{}) {
 		Object: kube.NewEndpoint(currEp),
 	})
 
-	c.controller.MetricsCollector.IncrEvents("endpoints", "update")
+	c.MetricsCollector.IncrEvents("endpoints", "update")
 }
 
 func (c *endpointsController) onDelete(obj interface{}) {
@@ -195,7 +208,7 @@ func (c *endpointsController) onDelete(obj interface{}) {
 	// FIXME Refactor Controller.isWatchingNamespace to just use
 	// namespace after all controllers use the same way to fetch
 	// the object.
-	if !c.controller.isWatchingNamespace(ep.Namespace + "/" + ep.Name) {
+	if !c.namespaceProvider.IsWatchingNamespace(ep.Namespace + "/" + ep.Name) {
 		return
 	}
 	log.Debugw("endpoints delete event arrived",
@@ -206,5 +219,5 @@ func (c *endpointsController) onDelete(obj interface{}) {
 		Object: kube.NewEndpoint(ep),
 	})
 
-	c.controller.MetricsCollector.IncrEvents("endpoints", "delete")
+	c.MetricsCollector.IncrEvents("endpoints", "delete")
 }
diff --git a/pkg/ingress/endpointslice.go b/pkg/providers/k8s/endpoint/endpointslice.go
similarity index 76%
rename from pkg/ingress/endpointslice.go
rename to pkg/providers/k8s/endpoint/endpointslice.go
index 63bd05d3..56faffbf 100644
--- a/pkg/ingress/endpointslice.go
+++ b/pkg/providers/k8s/endpoint/endpointslice.go
@@ -12,7 +12,7 @@
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 // See the License for the specific language governing permissions and
 // limitations under the License.
-package ingress
+package endpoint
 
 import (
 	"context"
@@ -24,7 +24,9 @@ import (
 	"k8s.io/client-go/tools/cache"
 	"k8s.io/client-go/util/workqueue"
 
+	"github.com/apache/apisix-ingress-controller/pkg/kube"
 	"github.com/apache/apisix-ingress-controller/pkg/log"
+	"github.com/apache/apisix-ingress-controller/pkg/providers/k8s/namespace"
 	"github.com/apache/apisix-ingress-controller/pkg/types"
 	v1 "github.com/apache/apisix-ingress-controller/pkg/types/apisix/v1"
 )
@@ -39,27 +41,39 @@ type endpointSliceEvent struct {
 }
 
 type endpointSliceController struct {
-	controller *Controller
-	workqueue  workqueue.RateLimitingInterface
-	workers    int
+	*baseEndpointController
+
+	workqueue workqueue.RateLimitingInterface
+	workers   int
+
+	namespaceProvider namespace.WatchingNamespaceProvider
+
+	epInformer cache.SharedIndexInformer
+	epLister   kube.EndpointLister
 }
 
-func (c *Controller) newEndpointSliceController() *endpointSliceController {
-	ctl := &endpointSliceController{
-		controller: c,
-		workqueue:  workqueue.NewNamedRateLimitingQueue(workqueue.NewItemFastSlowRateLimiter(time.Second, 60*time.Second, 5), "endpointSlice"),
-		workers:    1,
+func newEndpointSliceController(base *baseEndpointController, namespaceProvider namespace.WatchingNamespaceProvider) *endpointSliceController {
+	c := &endpointSliceController{
+		baseEndpointController: base,
+
+		workqueue: workqueue.NewNamedRateLimitingQueue(workqueue.NewItemFastSlowRateLimiter(time.Second, 60*time.Second, 5), "endpointSlice"),
+		workers:   1,
+
+		namespaceProvider: namespaceProvider,
+
+		epLister:   base.EpLister,
+		epInformer: base.EpInformer,
 	}
 
-	ctl.controller.epInformer.AddEventHandler(
+	c.epInformer.AddEventHandler(
 		cache.ResourceEventHandlerFuncs{
-			AddFunc:    ctl.onAdd,
-			UpdateFunc: ctl.onUpdate,
-			DeleteFunc: ctl.onDelete,
+			AddFunc:    c.onAdd,
+			UpdateFunc: c.onUpdate,
+			DeleteFunc: c.onDelete,
 		},
 	)
 
-	return ctl
+	return c
 }
 
 func (c *endpointSliceController) run(ctx context.Context) {
@@ -67,7 +81,7 @@ func (c *endpointSliceController) run(ctx context.Context) {
 	defer log.Info("endpointSlice controller exited")
 	defer c.workqueue.ShutDown()
 
-	if ok := cache.WaitForCacheSync(ctx.Done(), c.controller.epInformer.HasSynced); !ok {
+	if ok := cache.WaitForCacheSync(ctx.Done(), c.epInformer.HasSynced); !ok {
 		log.Error("informers sync failed")
 		return
 	}
@@ -93,6 +107,9 @@ func (c *endpointSliceController) run(ctx context.Context) {
 }
 
 func (c *endpointSliceController) sync(ctx context.Context, ev *types.Event) error {
+	log.Debugw("process endpoint slice event",
+		zap.Any("event", ev),
+	)
 	epEvent := ev.Object.(endpointSliceEvent)
 	namespace, _, err := cache.SplitMetaNamespaceKey(epEvent.Key)
 	if err != nil {
@@ -102,8 +119,8 @@ func (c *endpointSliceController) sync(ctx context.Context, ev *types.Event) err
 	if ev.Type == types.EventDelete {
 		log.Debugw("endpointsplice upstream serviece sync",
 			zap.String("service_name", epEvent.ServiceName))
-		clusterName := c.controller.cfg.APISIX.DefaultClusterName
-		err = c.controller.apisix.Cluster(clusterName).UpstreamServiceRelation().Delete(ctx,
+		clusterName := c.Config.APISIX.DefaultClusterName
+		err = c.APISIX.Cluster(clusterName).UpstreamServiceRelation().Delete(ctx,
 			&v1.UpstreamServiceRelation{
 				ServiceName: namespace + "_" + epEvent.ServiceName,
 			})
@@ -111,19 +128,19 @@ func (c *endpointSliceController) sync(ctx context.Context, ev *types.Event) err
 			return err
 		}
 	}
-	ep, err := c.controller.epLister.GetEndpoint(namespace, epEvent.ServiceName)
+	ep, err := c.epLister.GetEndpoint(namespace, epEvent.ServiceName)
 	if err != nil {
 		log.Errorf("failed to get all endpointSlices for service %s: %s",
 			epEvent.ServiceName, err)
 		return err
 	}
-	return c.controller.syncEndpoint(ctx, ep)
+	return c.syncEndpoint(ctx, ep)
 }
 
 func (c *endpointSliceController) handleSyncErr(obj interface{}, err error) {
 	if err == nil {
 		c.workqueue.Forget(obj)
-		c.controller.MetricsCollector.IncrSyncOperation("endpointSlice", "success")
+		c.MetricsCollector.IncrSyncOperation("endpointSlice", "success")
 		return
 	}
 	event := obj.(*types.Event)
@@ -139,7 +156,7 @@ func (c *endpointSliceController) handleSyncErr(obj interface{}, err error) {
 		zap.Any("object", obj),
 	)
 	c.workqueue.AddRateLimited(obj)
-	c.controller.MetricsCollector.IncrSyncOperation("endpointSlice", "failure")
+	c.MetricsCollector.IncrSyncOperation("endpointSlice", "failure")
 }
 
 func (c *endpointSliceController) onAdd(obj interface{}) {
@@ -147,7 +164,7 @@ func (c *endpointSliceController) onAdd(obj interface{}) {
 	if err != nil {
 		log.Errorf("found endpointSlice object with bad namespace")
 	}
-	if !c.controller.isWatchingNamespace(key) {
+	if !c.namespaceProvider.IsWatchingNamespace(key) {
 		return
 	}
 	ep := obj.(*discoveryv1.EndpointSlice)
@@ -173,7 +190,7 @@ func (c *endpointSliceController) onAdd(obj interface{}) {
 		},
 	})
 
-	c.controller.MetricsCollector.IncrEvents("endpointSlice", "add")
+	c.MetricsCollector.IncrEvents("endpointSlice", "add")
 }
 
 func (c *endpointSliceController) onUpdate(prev, curr interface{}) {
@@ -188,7 +205,7 @@ func (c *endpointSliceController) onUpdate(prev, curr interface{}) {
 		log.Errorf("found endpointSlice object with bad namespace/name: %s, ignore it", err)
 		return
 	}
-	if !c.controller.isWatchingNamespace(key) {
+	if !c.namespaceProvider.IsWatchingNamespace(key) {
 		return
 	}
 	if currEp.Labels[discoveryv1.LabelManagedBy] != _endpointSlicesManagedBy {
@@ -214,7 +231,7 @@ func (c *endpointSliceController) onUpdate(prev, curr interface{}) {
 		},
 	})
 
-	c.controller.MetricsCollector.IncrEvents("endpointSlice", "update")
+	c.MetricsCollector.IncrEvents("endpointSlice", "update")
 }
 
 func (c *endpointSliceController) onDelete(obj interface{}) {
@@ -232,7 +249,7 @@ func (c *endpointSliceController) onDelete(obj interface{}) {
 		log.Errorf("found endpointSlice object with bad namespace/name: %s, ignore it", err)
 		return
 	}
-	if !c.controller.isWatchingNamespace(key) {
+	if !c.namespaceProvider.IsWatchingNamespace(key) {
 		return
 	}
 	if ep.Labels[discoveryv1.LabelManagedBy] != _endpointSlicesManagedBy {
@@ -252,5 +269,5 @@ func (c *endpointSliceController) onDelete(obj interface{}) {
 		},
 	})
 
-	c.controller.MetricsCollector.IncrEvents("endpointSlice", "delete")
+	c.MetricsCollector.IncrEvents("endpointSlice", "delete")
 }
diff --git a/pkg/providers/k8s/endpoint/provider.go b/pkg/providers/k8s/endpoint/provider.go
new file mode 100644
index 00000000..8e5333e6
--- /dev/null
+++ b/pkg/providers/k8s/endpoint/provider.go
@@ -0,0 +1,77 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+package endpoint
+
+import (
+	"context"
+
+	"github.com/apache/apisix-ingress-controller/pkg/config"
+	"github.com/apache/apisix-ingress-controller/pkg/providers/k8s/namespace"
+	"github.com/apache/apisix-ingress-controller/pkg/providers/translation"
+	providertypes "github.com/apache/apisix-ingress-controller/pkg/providers/types"
+	"github.com/apache/apisix-ingress-controller/pkg/providers/utils"
+)
+
+var _ Provider = (*endpointProvider)(nil)
+
+type Provider interface {
+	providertypes.Provider
+}
+
+type endpointProvider struct {
+	cfg *config.Config
+
+	endpointsController     *endpointsController
+	endpointSliceController *endpointSliceController
+}
+
+func NewProvider(common *providertypes.Common, translator translation.Translator, namespaceProvider namespace.WatchingNamespaceProvider) (Provider, error) {
+	p := &endpointProvider{
+		cfg: common.Config,
+	}
+
+	base := &baseEndpointController{
+		Common:     common,
+		translator: translator,
+
+		svcLister:            common.SvcLister,
+		apisixUpstreamLister: common.ApisixUpstreamLister,
+	}
+
+	if common.Kubernetes.WatchEndpointSlices {
+		p.endpointSliceController = newEndpointSliceController(base, namespaceProvider)
+	} else {
+		p.endpointsController = newEndpointsController(base, namespaceProvider)
+	}
+
+	return p, nil
+}
+
+func (p *endpointProvider) Run(ctx context.Context) {
+	e := utils.ParallelExecutor{}
+
+	e.Add(func() {
+		if p.cfg.Kubernetes.WatchEndpointSlices {
+			p.endpointSliceController.run(ctx)
+		} else {
+			p.endpointsController.run(ctx)
+		}
+	})
+
+	e.Wait()
+}
diff --git a/pkg/ingress/namespace/namespace.go b/pkg/providers/k8s/namespace/namespace.go
similarity index 100%
rename from pkg/ingress/namespace/namespace.go
rename to pkg/providers/k8s/namespace/namespace.go
diff --git a/pkg/ingress/namespace/provider.go b/pkg/providers/k8s/namespace/namespace_provider.go
similarity index 90%
rename from pkg/ingress/namespace/provider.go
rename to pkg/providers/k8s/namespace/namespace_provider.go
index e8f442dd..b4a69e92 100644
--- a/pkg/ingress/namespace/provider.go
+++ b/pkg/providers/k8s/namespace/namespace_provider.go
@@ -31,19 +31,23 @@ import (
 
 	"github.com/apache/apisix-ingress-controller/pkg/api/validation"
 	"github.com/apache/apisix-ingress-controller/pkg/config"
-	"github.com/apache/apisix-ingress-controller/pkg/ingress/utils"
 	"github.com/apache/apisix-ingress-controller/pkg/kube"
 	"github.com/apache/apisix-ingress-controller/pkg/log"
+	provider "github.com/apache/apisix-ingress-controller/pkg/providers/types"
+	"github.com/apache/apisix-ingress-controller/pkg/providers/utils"
 	"github.com/apache/apisix-ingress-controller/pkg/types"
 )
 
-type WatchingProvider interface {
-	Run(ctx context.Context)
+type WatchingNamespaceProvider interface {
+	provider.Provider
+
+	Init(ctx context.Context) error
+
 	IsWatchingNamespace(key string) bool
 	WatchingNamespaces() []string
 }
 
-func NewWatchingProvider(ctx context.Context, kube *kube.KubeClient, cfg *config.Config) (WatchingProvider, error) {
+func NewWatchingNamespaceProvider(ctx context.Context, kube *kube.KubeClient, cfg *config.Config) (WatchingNamespaceProvider, error) {
 	var (
 		watchingNamespaces = new(sync.Map)
 		watchingLabels     = make(map[string]string)
@@ -90,10 +94,6 @@ func NewWatchingProvider(ctx context.Context, kube *kube.KubeClient, cfg *config
 
 	c.controller = newNamespaceController(c)
 
-	err := c.initWatchingNamespacesByLabels(ctx)
-	if err != nil {
-		return nil, err
-	}
 	return c, nil
 }
 
@@ -110,6 +110,14 @@ type watchingProvider struct {
 	controller *namespaceController
 }
 
+func (c *watchingProvider) Init(ctx context.Context) error {
+	err := c.initWatchingNamespacesByLabels(ctx)
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
 func (c *watchingProvider) initWatchingNamespacesByLabels(ctx context.Context) error {
 	labelSelector := metav1.LabelSelector{MatchLabels: c.watchingLabels}
 	opts := metav1.ListOptions{
diff --git a/pkg/ingress/namespace/provider_mock.go b/pkg/providers/k8s/namespace/namespace_provider_mock.go
similarity index 89%
rename from pkg/ingress/namespace/provider_mock.go
rename to pkg/providers/k8s/namespace/namespace_provider_mock.go
index e211d400..45641cf6 100644
--- a/pkg/ingress/namespace/provider_mock.go
+++ b/pkg/providers/k8s/namespace/namespace_provider_mock.go
@@ -23,7 +23,7 @@ import (
 	"k8s.io/client-go/tools/cache"
 )
 
-func NewMockWatchingProvider(namespaces []string) WatchingProvider {
+func NewMockWatchingNamespaceProvider(namespaces []string) WatchingNamespaceProvider {
 	return &mockWatchingProvider{
 		namespaces: namespaces,
 	}
@@ -33,6 +33,10 @@ type mockWatchingProvider struct {
 	namespaces []string
 }
 
+func (c *mockWatchingProvider) Init(ctx context.Context) error {
+	return nil
+}
+
 func (c *mockWatchingProvider) Run(ctx context.Context) {
 }
 
diff --git a/pkg/ingress/pod.go b/pkg/providers/k8s/pod/pod.go
similarity index 70%
rename from pkg/ingress/pod.go
rename to pkg/providers/k8s/pod/pod.go
index fb780678..f0a36c75 100644
--- a/pkg/ingress/pod.go
+++ b/pkg/providers/k8s/pod/pod.go
@@ -12,7 +12,7 @@
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 // See the License for the specific language governing permissions and
 // limitations under the License.
-package ingress
+package pod
 
 import (
 	"context"
@@ -22,18 +22,31 @@ import (
 	"k8s.io/client-go/tools/cache"
 
 	"github.com/apache/apisix-ingress-controller/pkg/log"
+	"github.com/apache/apisix-ingress-controller/pkg/providers/k8s/namespace"
+	providertypes "github.com/apache/apisix-ingress-controller/pkg/providers/types"
 	"github.com/apache/apisix-ingress-controller/pkg/types"
 )
 
 type podController struct {
-	controller *Controller
+	*providertypes.Common
+
+	namespaceProvider namespace.WatchingNamespaceProvider
+	podInformer       cache.SharedIndexInformer
+
+	podCache types.PodCache
 }
 
-func (c *Controller) newPodController() *podController {
+func newPodController(common *providertypes.Common, nsProvider namespace.WatchingNamespaceProvider,
+	podInformer cache.SharedIndexInformer) *podController {
 	ctl := &podController{
-		controller: c,
+		Common: common,
+
+		namespaceProvider: nsProvider,
+		podInformer:       podInformer,
+
+		podCache: types.NewPodCache(),
 	}
-	ctl.controller.podInformer.AddEventHandler(
+	ctl.podInformer.AddEventHandler(
 		cache.ResourceEventHandlerFuncs{
 			AddFunc:    ctl.onAdd,
 			UpdateFunc: ctl.onUpdate,
@@ -47,7 +60,7 @@ func (c *podController) run(ctx context.Context) {
 	log.Info("pod controller started")
 	defer log.Info("pod controller exited")
 
-	if ok := cache.WaitForCacheSync(ctx.Done(), c.controller.podInformer.HasSynced); !ok {
+	if ok := cache.WaitForCacheSync(ctx.Done(), c.podInformer.HasSynced); !ok {
 		log.Error("informers sync failed")
 		return
 	}
@@ -61,14 +74,14 @@ func (c *podController) onAdd(obj interface{}) {
 		log.Errorf("found pod with bad namespace/name: %s, ignore it", err)
 		return
 	}
-	if !c.controller.isWatchingNamespace(key) {
+	if !c.namespaceProvider.IsWatchingNamespace(key) {
 		return
 	}
 	log.Debugw("pod add event arrived",
 		zap.String("obj.key", key),
 	)
 	pod := obj.(*corev1.Pod)
-	if err := c.controller.podCache.Add(pod); err != nil {
+	if err := c.podCache.Add(pod); err != nil {
 		if err == types.ErrPodNoAssignedIP {
 			log.Debugw("pod no assigned ip, postpone the adding in subsequent update event",
 				zap.Any("pod", pod),
@@ -81,7 +94,7 @@ func (c *podController) onAdd(obj interface{}) {
 		}
 	}
 
-	c.controller.MetricsCollector.IncrEvents("pod", "add")
+	c.MetricsCollector.IncrEvents("pod", "add")
 }
 
 func (c *podController) onUpdate(oldObj, newObj interface{}) {
@@ -91,7 +104,7 @@ func (c *podController) onUpdate(oldObj, newObj interface{}) {
 		return
 	}
 
-	if !c.controller.isWatchingNamespace(curr.Namespace + "/" + curr.Name) {
+	if !c.namespaceProvider.IsWatchingNamespace(curr.Namespace + "/" + curr.Name) {
 		return
 	}
 	log.Debugw("pod update event arrived",
@@ -99,7 +112,7 @@ func (c *podController) onUpdate(oldObj, newObj interface{}) {
 		zap.Any("pod name", curr.Name),
 	)
 	if curr.DeletionTimestamp != nil {
-		if err := c.controller.podCache.Delete(curr); err != nil {
+		if err := c.podCache.Delete(curr); err != nil {
 			log.Errorw("failed to delete pod from cache",
 				zap.Error(err),
 				zap.Any("pod", curr),
@@ -107,7 +120,7 @@ func (c *podController) onUpdate(oldObj, newObj interface{}) {
 		}
 	}
 	if curr.Status.PodIP != "" {
-		if err := c.controller.podCache.Add(curr); err != nil {
+		if err := c.podCache.Add(curr); err != nil {
 			log.Errorw("failed to add pod to cache",
 				zap.Error(err),
 				zap.Any("pod", curr),
@@ -115,7 +128,7 @@ func (c *podController) onUpdate(oldObj, newObj interface{}) {
 		}
 	}
 
-	c.controller.MetricsCollector.IncrEvents("pod", "update")
+	c.MetricsCollector.IncrEvents("pod", "update")
 }
 
 func (c *podController) onDelete(obj interface{}) {
@@ -129,18 +142,18 @@ func (c *podController) onDelete(obj interface{}) {
 		pod = tombstone.Obj.(*corev1.Pod)
 	}
 
-	if !c.controller.isWatchingNamespace(pod.Namespace + "/" + pod.Name) {
+	if !c.namespaceProvider.IsWatchingNamespace(pod.Namespace + "/" + pod.Name) {
 		return
 	}
 	log.Debugw("pod delete event arrived",
 		zap.Any("final state", pod),
 	)
-	if err := c.controller.podCache.Delete(pod); err != nil {
+	if err := c.podCache.Delete(pod); err != nil {
 		log.Errorw("failed to delete pod from cache",
 			zap.Error(err),
 			zap.Any("pod", pod),
 		)
 	}
 
-	c.controller.MetricsCollector.IncrEvents("pod", "delete")
+	c.MetricsCollector.IncrEvents("pod", "delete")
 }
diff --git a/pkg/ingress/pod_test.go b/pkg/providers/k8s/pod/pod_test.go
similarity index 68%
rename from pkg/ingress/pod_test.go
rename to pkg/providers/k8s/pod/pod_test.go
index 4235703c..090c4a9a 100644
--- a/pkg/ingress/pod_test.go
+++ b/pkg/providers/k8s/pod/pod_test.go
@@ -12,7 +12,7 @@
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 // See the License for the specific language governing permissions and
 // limitations under the License.
-package ingress
+package pod
 
 import (
 	"testing"
@@ -22,19 +22,24 @@ import (
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 
-	"github.com/apache/apisix-ingress-controller/pkg/ingress/namespace"
 	"github.com/apache/apisix-ingress-controller/pkg/metrics"
+	"github.com/apache/apisix-ingress-controller/pkg/providers/k8s/namespace"
+	providertypes "github.com/apache/apisix-ingress-controller/pkg/providers/types"
 	"github.com/apache/apisix-ingress-controller/pkg/types"
 )
 
-func TestPodOnAdd(t *testing.T) {
-	ctl := &podController{
-		controller: &Controller{
-			namespaceProvider: namespace.NewMockWatchingProvider([]string{"default"}),
-			podCache:          types.NewPodCache(),
-			MetricsCollector:  metrics.NewPrometheusCollector(),
+func mockController() *podController {
+	return &podController{
+		podCache:          types.NewPodCache(),
+		namespaceProvider: namespace.NewMockWatchingNamespaceProvider([]string{"default"}),
+		Common: &providertypes.Common{
+			MetricsCollector: metrics.NewPrometheusCollector(),
 		},
 	}
+}
+
+func TestPodOnAdd(t *testing.T) {
+	ctl := mockController()
 
 	pod := &corev1.Pod{
 		ObjectMeta: metav1.ObjectMeta{
@@ -47,7 +52,7 @@ func TestPodOnAdd(t *testing.T) {
 		},
 	}
 	ctl.onAdd(pod)
-	name, err := ctl.controller.podCache.GetNameByIP("10.0.5.12")
+	name, err := ctl.podCache.GetNameByIP("10.0.5.12")
 	assert.Nil(t, err)
 	assert.Equal(t, "nginx", name)
 
@@ -62,19 +67,13 @@ func TestPodOnAdd(t *testing.T) {
 		},
 	}
 	ctl.onAdd(pod2)
-	name, err = ctl.controller.podCache.GetNameByIP("10.0.5.13")
+	name, err = ctl.podCache.GetNameByIP("10.0.5.13")
 	assert.Empty(t, name)
 	assert.Equal(t, types.ErrPodNotFound, err)
 }
 
 func TestPodOnDelete(t *testing.T) {
-	ctl := &podController{
-		controller: &Controller{
-			namespaceProvider: namespace.NewMockWatchingProvider([]string{"default"}),
-			podCache:          types.NewPodCache(),
-			MetricsCollector:  metrics.NewPrometheusCollector(),
-		},
-	}
+	ctl := mockController()
 
 	pod := &corev1.Pod{
 		ObjectMeta: metav1.ObjectMeta{
@@ -86,10 +85,10 @@ func TestPodOnDelete(t *testing.T) {
 			PodIP: "10.0.5.12",
 		},
 	}
-	assert.Nil(t, ctl.controller.podCache.Add(pod), "adding pod")
+	assert.Nil(t, ctl.podCache.Add(pod), "adding pod")
 
 	ctl.onDelete(pod)
-	name, err := ctl.controller.podCache.GetNameByIP("10.0.5.12")
+	name, err := ctl.podCache.GetNameByIP("10.0.5.12")
 	assert.Empty(t, name)
 	assert.Equal(t, types.ErrPodNotFound, err)
 
@@ -103,21 +102,15 @@ func TestPodOnDelete(t *testing.T) {
 			PodIP: "10.0.5.13",
 		},
 	}
-	assert.Nil(t, ctl.controller.podCache.Add(pod2), "adding pod")
+	assert.Nil(t, ctl.podCache.Add(pod2), "adding pod")
 	ctl.onDelete(pod2)
-	name, err = ctl.controller.podCache.GetNameByIP("10.0.5.13")
+	name, err = ctl.podCache.GetNameByIP("10.0.5.13")
 	assert.Equal(t, "abc", name)
 	assert.Nil(t, err)
 }
 
 func TestPodOnUpdate(t *testing.T) {
-	ctl := &podController{
-		controller: &Controller{
-			namespaceProvider: namespace.NewMockWatchingProvider([]string{"default"}),
-			podCache:          types.NewPodCache(),
-			MetricsCollector:  metrics.NewPrometheusCollector(),
-		},
-	}
+	ctl := mockController()
 
 	pod0 := &corev1.Pod{
 		ObjectMeta: metav1.ObjectMeta{
@@ -136,12 +129,12 @@ func TestPodOnUpdate(t *testing.T) {
 	pod1.SetResourceVersion("1")
 
 	ctl.onUpdate(pod1, pod0)
-	name, err := ctl.controller.podCache.GetNameByIP("10.0.5.12")
+	name, err := ctl.podCache.GetNameByIP("10.0.5.12")
 	assert.Equal(t, "", name)
 	assert.Equal(t, types.ErrPodNotFound, err)
 
 	ctl.onUpdate(pod0, pod1)
-	name, err = ctl.controller.podCache.GetNameByIP("10.0.5.12")
+	name, err = ctl.podCache.GetNameByIP("10.0.5.12")
 	assert.Equal(t, "nginx", name)
 	assert.Equal(t, nil, err)
 
@@ -156,9 +149,9 @@ func TestPodOnUpdate(t *testing.T) {
 			PodIP: "10.0.5.13",
 		},
 	}
-	assert.Nil(t, ctl.controller.podCache.Add(pod2), "adding pod")
+	assert.Nil(t, ctl.podCache.Add(pod2), "adding pod")
 	ctl.onUpdate(pod1, pod2)
-	name, err = ctl.controller.podCache.GetNameByIP("10.0.5.13")
+	name, err = ctl.podCache.GetNameByIP("10.0.5.13")
 	assert.Equal(t, "abc", name)
 	assert.Nil(t, err)
 }
diff --git a/pkg/providers/k8s/pod/provider.go b/pkg/providers/k8s/pod/provider.go
new file mode 100644
index 00000000..50ceef5c
... 2930 lines suppressed ...