Repository: apache/apisix-ingress-controller

Issue:
proposal: restructure `pkg/ingress`
As support for the Gateway API progressed, we needed to redesign the structure of `pkg/ingress`.
This directory now has multiple types of resources mixed together: Kubernetes native resources, APISIX Ingress resources, and Gateway API resources. More types of resources may be added in the future.
In addition, the `controller.go` file has become increasingly large.

We need to redesign the controller so that supporting different resources doesn't require massive changes to the outer files, and to keep the changes in a smaller module as much as possible.

Hints from issue comments:
@tao12345666333: +1
I agree with you.

Do you have a more specific plan? 
We can put this proposal into this year's roadmap

@lingsamuel: Perhaps we could create an interface called "(Feature)Provider". The provider holds several small controllers and associated clientet/listers/informers.

Also, we should separate the translators and only make the Kubernetes translator publicly accessible, since any other translator could potentially rely on it.
APISIX Ingress and any other CRD translators should be private.


- RootController (under `pkg/controller` - former `pkg/ingress`)
  - KubernetesProvider
    - Controllers: secret, service, pod, endpoint...
    - clientset/listers/informers (public)
    - KubernetesTranslator (Only necessary items are public, e.g., translate Services into APISIX upstream)
  - NginxIngressProvider
    - Controllers: NGINX Ingress CRDs
    - clientset/listers/informers
    - NginxIngressTranslator
  - ApisixIngressProvider
    - Controllers: APISIX Ingress CRDs
    - clientset/listers/informers
    - ApisixIngressTranslator
  - GatewayProvider
    - Controllers: Gateway CRDs
    - clientset/listers/informers
    - GatewayTranslator@tao12345666333: SGTM

I prefer to rename KubernetesProvider to CommonProvider.

When dealing with various resources(Ingress/CR/Gateway resource), we may need to deal with these basic resources of Kubernetes.

Solution patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 70782c24..00d3b8f2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -19,6 +19,7 @@
 
 # Table of Contents
 
+- [1.5.0-rc1](#150-rc1)
 - [1.4.1](#141)
 - [1.4.0](#140)
 - [1.3.0](#130)
@@ -32,6 +33,292 @@
 - [0.2.0](#020)
 - [0.1.0](#010)
 
+# 1.5.0-rc1
+
+Welcome to the 1.5.0 release of apisix-ingress-controller!
+
+This is a feature release.
+
+## Highlights
+
+The API version of all custom resources has been upgraded to v2 in this release and mark v2beta3 as deprecated. We plan to remove the v2beta2 API version in the next release. Please go to [#707](https://github.com/apache/apisix-ingress-controller/issues/707) for detail.
+
+We have added partial support for Gateway API, which is not enabled by default, you can set `enable_gateway_api=true` to enable it.
+
+Ingress resources can now use all APISIX plugin configurations by setting the annotation `k8s.apisix.apache.org/plugin-config-name=xxx`.
+
+Please try out the release binaries and report any issues at
+https://github.com/apache/apisix-ingress-controller/issues.
+
+### Contributors
+
+* Jintao Zhang
+* Sarasa Kisaragi
+* Xin Rong
+* John Chever
+* cmssczy
+* dependabot[bot]
+* nevercase
+* Gallardot
+* Nic
+* lsy
+* mango
+* Fatpa
+* Hoshea Jiang
+* JasonZhu
+* Xin Rong
+* Yu.Bozhong
+* seven dickens
+* FesonX
+* GhangZh
+* JasonZhu
+* Kowsz
+* LetsGO
+* Sindweller
+* SkyeYoung
+* Zack Sun
+* bin-ya
+* champly
+* chen zhuo
+* fengxsong
+* greenhandatsjtu
+* hahayyun
+* hf400159
+* wangyunpeng
+* 罗泽轩
+
+### Changes
+<details><summary>144 commits</summary>
+<p>
+
+* [`cccad72`](https://github.com/apache/apisix-ingress-controller/commit/cccad72a1e0ef60525c69371b4b27c4598c587c1) chore: mark v2beta3 deprecated (#1198)
+* [`698ab6d`](https://github.com/apache/apisix-ingress-controller/commit/698ab6d52d4eef0d4404fa3bec43b20039ce9370) chore: Using APISIX 2.15.0 for CI (#1197)
+* [`8094868`](https://github.com/apache/apisix-ingress-controller/commit/80948682d73cb7813d06f85e548392f19dc465f4) e2e: add sync case (#1196)
+* [`37a8e5c`](https://github.com/apache/apisix-ingress-controller/commit/37a8e5c837cd2a8a459bec054c3ed73bac9b52bb) fix: translate error of old Ingress (#1193)
+* [`339531f`](https://github.com/apache/apisix-ingress-controller/commit/339531f3eceb2ce24f07cc6b255fde94f201879a) doc: add a notice about the compatibility of Ingress and Dashboard (#1195)
+* [`2cc586b`](https://github.com/apache/apisix-ingress-controller/commit/2cc586bbe06d3dd4c1c904fd487a9eaa13b5b3a2) fix: apisix_upstream sync panic (#1192)
+* [`6cc718b`](https://github.com/apache/apisix-ingress-controller/commit/6cc718bb85a3d61f3702ba0514e5408d48a873da) helm: update deploy cluster role (#1131)
+* [`3d720c0`](https://github.com/apache/apisix-ingress-controller/commit/3d720c0f9ba41fb2067d3a552a45d357c7a420f7) fix: translate error of old ApisixRoute (#1191)
+* [`8b51c6e`](https://github.com/apache/apisix-ingress-controller/commit/8b51c6e173db12592f6fcfa8d59aaa6fd50e7922) docs: update all api-version to v2 (#1189)
+* [`5f45b63`](https://github.com/apache/apisix-ingress-controller/commit/5f45b63b3d3716dd5de9f0288a6dd5c28228c40d) fix: ScopeQuery should be case sensitive (#1168) (#1188)
+* [`af03e7a`](https://github.com/apache/apisix-ingress-controller/commit/af03e7a95785d0905f33a3458ce2cda02db41853) chore: update APISIX v2.14.1 (#1145)
+* [`516e677`](https://github.com/apache/apisix-ingress-controller/commit/516e6771ff9dd94347177c83b290a2292f4f350c) chore(deps): bump github.com/gin-gonic/gin from 1.7.7 to 1.8.1 (#1184)
+* [`a0bc739`](https://github.com/apache/apisix-ingress-controller/commit/a0bc73990c9d6e2aee4b063232e09de01de8012e) fix: trigger ApisixRoute event when service is created (#1152)
+* [`1765ec9`](https://github.com/apache/apisix-ingress-controller/commit/1765ec9ffe683a323fd3a851e0ea069173950481) test: keep namespace when test failed in dev mod (#1158)
+* [`b1add53`](https://github.com/apache/apisix-ingress-controller/commit/b1add53cbc915af476d61a2798db82eb6bef80b1) Chore dep update (#1180)
+* [`8c2cfbc`](https://github.com/apache/apisix-ingress-controller/commit/8c2cfbc24db8a0ee76a681dec92b85920abd47b8) chore(deps): bump github.com/stretchr/testify from 1.7.0 to 1.8.0 (#1175)
+* [`1a5f2c1`](https://github.com/apache/apisix-ingress-controller/commit/1a5f2c16f96034ec6d66c65a1eaec603fe1c85db) chore(deps): bump github.com/spf13/cobra from 1.2.1 to 1.5.0 (#1176)
+* [`3299260`](https://github.com/apache/apisix-ingress-controller/commit/3299260a958b200c2e3f51789a569971fe8d2157) chore(deps): bump k8s.io/client-go and go-memdb etc. (#1172)
+* [`628abb9`](https://github.com/apache/apisix-ingress-controller/commit/628abb945f7bc515bd5fcccf1479443305236478) ci: upgrade e2e-test-ci (#1149)
+* [`9a6bd92`](https://github.com/apache/apisix-ingress-controller/commit/9a6bd925a3bfb93b2734098d6e29860105b791b1) chore(deps): bump github.com/gruntwork-io/terratest in /test/e2e (#1156)
+* [`edb19cd`](https://github.com/apache/apisix-ingress-controller/commit/edb19cdf34e2f3b3558f0bdbbfa458e6093b51a3) chore(deps): bump github.com/gorilla/websocket in /test/e2e (#1114)
+* [`f198f33`](https://github.com/apache/apisix-ingress-controller/commit/f198f33c74fd2fc3361d01a60f37f37368f6a155) chore(deps): some dependency updates (#1160)
+* [`e75f7e9`](https://github.com/apache/apisix-ingress-controller/commit/e75f7e9b1a3c1b3a9bee3a764d379ca661bd7bb9) chore: change description and labels for this project (#1150)
+* [`35ca03c`](https://github.com/apache/apisix-ingress-controller/commit/35ca03cbb4a4779a131ed52f5da2515624c9f00d) test: add e2e tests and CRDs for ApisixUpstream v2 (#1147)
+* [`6cf8bb7`](https://github.com/apache/apisix-ingress-controller/commit/6cf8bb7c491f5ee9fbc887eef724308ba96e2242) feat: Add annotations to combine ApisixPluginConfig with k8s ingress resource (#1139)
+* [`73498bd`](https://github.com/apache/apisix-ingress-controller/commit/73498bd761e661989b7397eeb12bf18c57683abe) docs: update crd version (#1134)
+* [`a649751`](https://github.com/apache/apisix-ingress-controller/commit/a649751dbdd42cea64ad574dda6fe1eeba6a4a5a) feat: ApisixUpstream v2 (#1141)
+* [`a73b52d`](https://github.com/apache/apisix-ingress-controller/commit/a73b52d3946dcc08473888445a0c66152e659120) feat: support endpointslice, and improve test/e2e/endpoints.go tests. (#1140)
+* [`f0217ae`](https://github.com/apache/apisix-ingress-controller/commit/f0217ae5b022d6086bab2155dd3053567b3fc3aa) ci: pin skywalking-eyes to release (#1143)
+* [`62e0ea2`](https://github.com/apache/apisix-ingress-controller/commit/62e0ea20031ebfa664af5b0d5ab2e76336bac107) chore: add log for syncManifest delete upstream (#1132)
+* [`374f865`](https://github.com/apache/apisix-ingress-controller/commit/374f86565ac3c84b1a19e0450c2251b1dfce58f0) make api version const consistent (#1133)
+* [`93c10e6`](https://github.com/apache/apisix-ingress-controller/commit/93c10e6023ab10aa70e8a61992372ce0869139a9) fix: verify through the cache first, then delete (#1135)
+* [`a642b14`](https://github.com/apache/apisix-ingress-controller/commit/a642b1492dc9e00f350aaacb06fea6025bc4e809) doc: fix enable-authentication-and-restriction.md link failed (#1137)
+* [`e25abdb`](https://github.com/apache/apisix-ingress-controller/commit/e25abdb2be54865be03a7d486960e9c77eb1ba86) fix: ns should unwatch after unlabeling it (#1130)
+* [`398f816`](https://github.com/apache/apisix-ingress-controller/commit/398f816a57e496d0966c6103ab8ab1cae28cf852) e2e-test: Optimize the runtime of ingress/features, and support more default value in NewScaffold (#1128)
+* [`4d172a0`](https://github.com/apache/apisix-ingress-controller/commit/4d172a0306eed6216fd8bf057fd1506053a4a303) chore(deps): bump github.com/stretchr/testify in /test/e2e (#1113)
+* [`aae2105`](https://github.com/apache/apisix-ingress-controller/commit/aae2105e123008a0170b68a0133432695ee230c9) feat: ingress annotations support enable websocket (#1101)
+* [`70c0870`](https://github.com/apache/apisix-ingress-controller/commit/70c08706ae7cae73bc9b08d2ec2d29b98e5b4f89) chore(deps): bump github.com/gruntwork-io/terratest from 0.32.8 to 0.40.17 in /test/e2e (#1112)
+* [`4bc9f0c`](https://github.com/apache/apisix-ingress-controller/commit/4bc9f0cac1205ed71bf355817ec7db09690398f8) fix: update Makefile verify-mdlint (#1126)
+* [`4aa2ca5`](https://github.com/apache/apisix-ingress-controller/commit/4aa2ca5a80149e2cd5bddf8b1fb51ef2f64cfbc3) test: support ApisixRoute v2 and split suit-plugins (#1103)
+* [`810f1a1`](https://github.com/apache/apisix-ingress-controller/commit/810f1a1c5b232808e77f8f153d3ec90c3008e3c0) docs: rename practices to tutorials and add index (#1123)
+* [`b1dc75e`](https://github.com/apache/apisix-ingress-controller/commit/b1dc75e198dbacc4d66e9d2bfbb259cfea9c67a3) chore: enable dependabot for security (#1111)
+* [`0e1f8d4`](https://github.com/apache/apisix-ingress-controller/commit/0e1f8d4afdf4d90743ef238872d1e383cdfa93a4) fix : The ingress backend is modified several times, resulting in residual update events (#1040)
+* [`b33d70c`](https://github.com/apache/apisix-ingress-controller/commit/b33d70c429d32db6d2a065463d37a73ff59ab4ad) feat: support gateway TLSRoute (#1087)
+* [`49991e2`](https://github.com/apache/apisix-ingress-controller/commit/49991e2e74b3a9a40d2ee9bb645408a338458921) feat: sync CRD and ingress resource to apisix mechanism. (#1102)
+* [`f453e80`](https://github.com/apache/apisix-ingress-controller/commit/f453e8071fd7d320b7847235d6b61596c8ef3402) chore: enable stale GitHub action (#1107)
+* [`9e0c658`](https://github.com/apache/apisix-ingress-controller/commit/9e0c658cd2387165b5e0b5fc8d91f6464e3ad406) fix: upstream nodes filed IP occupation. (#1064)
+* [`d46b8e0`](https://github.com/apache/apisix-ingress-controller/commit/d46b8e0f79172ca38b3e8ce93552b7ba1225aca4) feat: support v2 in resource compare (#1093)
+* [`0f95dbe`](https://github.com/apache/apisix-ingress-controller/commit/0f95dbe989921d6b76156d62a3ec93804d21ac50) doc: add v2 CRD reference (#1068)
+* [`5756273`](https://github.com/apache/apisix-ingress-controller/commit/57562739ee06ae080bdbabc35ed3b2e2cd397e42) infra: update golang 1.18 (#1095)
+* [`a69a55a`](https://github.com/apache/apisix-ingress-controller/commit/a69a55aff459ff6aa842c4ab7914b022883a8d46) Revert "feat: sync CRD and ingress resource to APISIX mechanism. (#1022)" (#1099)
+* [`6394cdd`](https://github.com/apache/apisix-ingress-controller/commit/6394cdd11e96f205756bb8c19eaa047bdc114774) feat: sync CRD and ingress resource to APISIX mechanism. (#1022)
+* [`50d6026`](https://github.com/apache/apisix-ingress-controller/commit/50d6026df3953f9005bdd6ce4055909d0b2e3310) fix: make ApisixRouteHTTPBackend support serivce name (#1096)
+* [`3214c69`](https://github.com/apache/apisix-ingress-controller/commit/3214c698f9dd2dc7797fce837e16d15d60f64050) fix: e2e robustness. (#1078)
+* [`c48a62a`](https://github.com/apache/apisix-ingress-controller/commit/c48a62abfbd0b546c1d89bb7931caf1ed11abbb3) feat: support GatewayClass, refactor gateway modules (#1079)
+* [`a0b88d1`](https://github.com/apache/apisix-ingress-controller/commit/a0b88d11c4e906722d7928e7bdd985435e9cbe1c) fix: tag for keyAuth field (#1080)
+* [`f0d64b6`](https://github.com/apache/apisix-ingress-controller/commit/f0d64b6d8ce4f4f1b5f15d03938f3a87b71a84a8) docs: correct typo & link (#1073)
+* [`3520830`](https://github.com/apache/apisix-ingress-controller/commit/3520830ad446ffc8087d48c82e284d1ae08db64c) e2e: gateway api httproute (#1060)
+* [`2af39c9`](https://github.com/apache/apisix-ingress-controller/commit/2af39c949883dc831aa64be075aa7cc0edd2dd8a) docs: add how to change Admin API key for APISIX (#1031)
+* [`96dd07f`](https://github.com/apache/apisix-ingress-controller/commit/96dd07f847bff9d425b223e0bcac0eea2efe6316) e2e-test: add e2e tests for ApisixPluginConfig v2 (#1067)
+* [`d3a823f`](https://github.com/apache/apisix-ingress-controller/commit/d3a823f590843468a555ccc68502acc568d9a9f3) doc: update enable-authentication-and-restriction, jwt-auth and wolf-rbac examples. (#1018)
+* [`deb0440`](https://github.com/apache/apisix-ingress-controller/commit/deb044039e889e88310523f369833bb137c0e145) docs: add "how to use go plugin runner with APISIX Ingress" (#994)
+* [`8d76428`](https://github.com/apache/apisix-ingress-controller/commit/8d764286b018c22b20b62591b7bb68b93de0a93b) e2e-test: upgrade to ginkgo v2 (#1046)
+* [`408eb0d`](https://github.com/apache/apisix-ingress-controller/commit/408eb0d1c9b05517e530688e2e6e82c721699548) feat: support ApisixPluginConfig v2 (#984)
+* [`e1d496d`](https://github.com/apache/apisix-ingress-controller/commit/e1d496dd074eb19982b9e198370440ebd0d16f87) e2e-test: add e2e tests and CRDs for ApisixConsumer v2 (#1044)
+* [`6c7452f`](https://github.com/apache/apisix-ingress-controller/commit/6c7452ffab358c47e25882d802e4cc891e95eb37) feat: support gateway API HTTPRoute (#1037)
+* [`5477fb0`](https://github.com/apache/apisix-ingress-controller/commit/5477fb0e5f62f1c9b1716824bcf0fb7f038b02a7) test: fix wolf-rbac and mTLS test cases (#1055)
+* [`df7a724`](https://github.com/apache/apisix-ingress-controller/commit/df7a724ce11d23ad441209cf2592426f251f597c) e2e-test: add e2e tests and CRDs for ApisixClusterConfig v2 (#1016)
+* [`25daa6e`](https://github.com/apache/apisix-ingress-controller/commit/25daa6e2f02ceb459f605ca2bb5a3aaa8973c624) feat: add csrf plugin annotation in ingress resource (#1023)
+* [`59ba41a`](https://github.com/apache/apisix-ingress-controller/commit/59ba41a8c5780b9273610e944aabda700078a3db) feat: add hmac-auth authorization method (#1035)
+* [`49dd015`](https://github.com/apache/apisix-ingress-controller/commit/49dd015085a68ce4c26ba60076ef281ed71af2aa) doc: update contribute.md doc (#1036)
+* [`f6f0a3b`](https://github.com/apache/apisix-ingress-controller/commit/f6f0a3b5552ba8fda556e0edb9296c1c0a4c3e31) feat: support ApisixConsumer v2 (#989)
+* [`bef2010`](https://github.com/apache/apisix-ingress-controller/commit/bef2010bbc33667bbefd3e718997fe4d20e2f5f8) doc: paraphrasing some descriptions (#1028)
+* [`9bd4b71`](https://github.com/apache/apisix-ingress-controller/commit/9bd4b714ceb8fd427b325757e10090f056803cd3) chore: Changelog for 1.4.1 (#1029)
+* [`537b947`](https://github.com/apache/apisix-ingress-controller/commit/537b947ab708c12c50e33b37c94caf73589bb65d) doc: add apisix_pluginconfig document (#1025)
+* [`bb5104e`](https://github.com/apache/apisix-ingress-controller/commit/bb5104e46911d187b2d624378b0f4c3ed5e7e38a) feat: add wolf-rbac authorization method. (#1011)
+* [`3cccd56`](https://github.com/apache/apisix-ingress-controller/commit/3cccd5666e098f374c262eb443de194d69d6a55e) feat: add jwt-auth authorization method (#1009)
+* [`cd5063f`](https://github.com/apache/apisix-ingress-controller/commit/cd5063f04881248338595cb85100ef1009e75a80) e2e-test: add e2e tests and CRDs for ApisixTls v2 (#1014)
+* [`bac9813`](https://github.com/apache/apisix-ingress-controller/commit/bac9813e4c90a56dadba89808da2faa3d0834b79) feat: support ApisixClusterConfig v2 (#977)
+* [`e2f19b5`](https://github.com/apache/apisix-ingress-controller/commit/e2f19b563e672f21f9bc72c3890ed74908ddc9cc) feat: support ApisixTls v2 (#967)
+* [`75a4166`](https://github.com/apache/apisix-ingress-controller/commit/75a4166bf52fa24da1a9a0f92c0fd7dfc99d5480) docs: added "how to access Apache APISIX Prometheus Metrics on k8s" (#973)
+* [`670d671`](https://github.com/apache/apisix-ingress-controller/commit/670d671d436701ec8083248c6b23713a50ec0c4c) feat:add authorization-annotation the ingress resource (#985)
+* [`78efb00`](https://github.com/apache/apisix-ingress-controller/commit/78efb006a4285a9c558cb50524478f944f849906) feat: update an redirect annotation for ingress resource (#975)
+* [`3a175e5`](https://github.com/apache/apisix-ingress-controller/commit/3a175e5b9a2c221641c74271eca94079d41501a3) chore: modify metrics name apisix_bad_status_codes to apisix_status_codes (#1012)
+* [`f63a29f`](https://github.com/apache/apisix-ingress-controller/commit/f63a29f71e700b381503c9485f41e5225fbe1d9c) doc: add 'enable authentication and restriction' document (#972)
+* [`1899d90`](https://github.com/apache/apisix-ingress-controller/commit/1899d9018ba05eeb34c171d279c445815f248bf8) feat: improve the e2e test of referer-restriction plugin (#976)
+* [`795be22`](https://github.com/apache/apisix-ingress-controller/commit/795be227d9c73bccc079ca263f098cf54d745f62) docs: fix link in certificate management docs (#1007)
+* [`92b89b3`](https://github.com/apache/apisix-ingress-controller/commit/92b89b37d2f63eab6f5c6deb6e3d2a5ccae975aa) chore: update apisix to 2.13.1 (#996)
+* [`eefeec8`](https://github.com/apache/apisix-ingress-controller/commit/eefeec87c53bcf674f9f1241f47b11a163c6ee48) docs: update apisix_upstream.md (#983)
+* [`4a0fc0c`](https://github.com/apache/apisix-ingress-controller/commit/4a0fc0c4b91650bf073047f64c76e82080f3578f) chore: Fix some code formats (#968)
+* [`0f4391a`](https://github.com/apache/apisix-ingress-controller/commit/0f4391a87a38ae9a2c61a0cea717cb831fc55506) refactor: encapsulate functions to reuse code (#971)
+* [`64e2768`](https://github.com/apache/apisix-ingress-controller/commit/64e276813eb539baed44e574c20d2336e119b101) ci: add 3 plugin test cases for e2e (#965)
+* [`f081121`](https://github.com/apache/apisix-ingress-controller/commit/f0811211a718193f8c8a44b7ad12b163ed641f19) feat: add e2e test for serverless plugin (#964)
+* [`eb02429`](https://github.com/apache/apisix-ingress-controller/commit/eb02429de6b92fb7fc6e322f052f33098bd4654f) feat: support forward-auth plugin (#937)
+* [`77ab065`](https://github.com/apache/apisix-ingress-controller/commit/77ab065500cdcf2a95b9126d222f3ae82efac8f5) ci: add dependency-review (#963)
+* [`fe628f6`](https://github.com/apache/apisix-ingress-controller/commit/fe628f68ab8ed36547e0d224694a09cba06453b1) docs: fix subset field typo (#961)
+* [`aee6e78`](https://github.com/apache/apisix-ingress-controller/commit/aee6e7893a731b623fd3bf4f0c1f7bd8d35efae1) fix ApisixConsumerBasicAuthValue password-yaml field error (#960)
+* [`0790458`](https://github.com/apache/apisix-ingress-controller/commit/079045836392432e6836f67f9076f63c85dd6243) ci: fix server-info e2e test case(#959)
+* [`22cfb5e`](https://github.com/apache/apisix-ingress-controller/commit/22cfb5ec7482e1bca6d293091ce8c7aa5342260b) Add a pre-check for E2E tests (#957)
+* [`4bdc947`](https://github.com/apache/apisix-ingress-controller/commit/4bdc9471172a8b1574c61ba5ad5b8fbc4160fc22) Split e2e test cases (#949)
+* [`de33d05`](https://github.com/apache/apisix-ingress-controller/commit/de33d05aeff9ec5fdd1a2431c01535566437ee12) feat(e2e): add e2e test for prometheus (#942)
+* [`7e4ec36`](https://github.com/apache/apisix-ingress-controller/commit/7e4ec36e36c9114d99156e72b19142d2026533fc) fix: ingress update event handler not filter by watching namespaces (#947)
+* [`b5ea236`](https://github.com/apache/apisix-ingress-controller/commit/b5ea23679136b4fc6181d838c8bd36c03ef687b4) docs: update the hard way. (#946)
+* [`f58f3d5`](https://github.com/apache/apisix-ingress-controller/commit/f58f3d51889fe72609ca0b837b373e62306f9a3a) feat: change ApisixRoute to v2 api version (#943)
+* [`3b99353`](https://github.com/apache/apisix-ingress-controller/commit/3b993533d66b25c186f4096727d373e5ed4131c6) feat: introduce v2 apiversion (#939)
+* [`cb45119`](https://github.com/apache/apisix-ingress-controller/commit/cb45119b4c4ec7e9814487b4f29789b4778075e5) doc: add doc about installing apisix ingress with kind (#933)
+* [`4da91b7`](https://github.com/apache/apisix-ingress-controller/commit/4da91b7971d3defd137f35de84f107612e6f96bd) chore: drop v2beta1 api version (#928)
+* [`81831d5`](https://github.com/apache/apisix-ingress-controller/commit/81831d51b39a1df2db4beeb7d9d7af429e0425fc) docs: remove ApisixRoute v2beta1 & v2alphq1 (#930)
+* [`0a66151`](https://github.com/apache/apisix-ingress-controller/commit/0a66151853b2e0ee1d9c43af62f144f9dc63a688) fix: watch all namespaces by default (#919)
+* [`2178857`](https://github.com/apache/apisix-ingress-controller/commit/2178857fbe2608cec1dc8ce5b1e0ec232568b375) fix: ApisixRouteEvent type assertion (#925)
+* [`c9e0c96`](https://github.com/apache/apisix-ingress-controller/commit/c9e0c965cd2226e2c44aa4e692a8bf57d1586aa3) docs: remove development from sidebar config (#923)
+* [`f31f520`](https://github.com/apache/apisix-ingress-controller/commit/f31f5201100169a61cd6b8220683453f2f81379d) docs: merge contribute.md and development.md (#909)
+* [`11bd92b`](https://github.com/apache/apisix-ingress-controller/commit/11bd92beb7e45035a12ec2cd27f4295276fa1af2) docs: upgrade apiVersion from v2beta1 to v2beta3 (#916)
+* [`75098d1`](https://github.com/apache/apisix-ingress-controller/commit/75098d1e4b26136de3164a3aabd6ed018ffdcd6b) chore: clean up useless code (#902)
+* [`4025151`](https://github.com/apache/apisix-ingress-controller/commit/4025151e44b9f64aa0881248faa3088487b53ec6) feat: format gin logger (#904)
+* [`48c924c`](https://github.com/apache/apisix-ingress-controller/commit/48c924c3397f2a433482204934a37aac4c4726b8) docs: add pre-commit todo in the development guide (#907)
+* [`1159522`](https://github.com/apache/apisix-ingress-controller/commit/1159522bf22181b67c2f075055894f488e9bc648) fix: controller err handler should ignore not found error (#893)
+* [`f84a083`](https://github.com/apache/apisix-ingress-controller/commit/f84a083719b5de74c68dd3bf206b1a88f2a540c4) feat: support custom registry for e2e test (#896)
+* [`1ddbfa6`](https://github.com/apache/apisix-ingress-controller/commit/1ddbfa68bb05becda7eb301f3ce6740c02b4d0a1) fix: fix ep resourceVersion comparison and clean up (#901)
+* [`8348d01`](https://github.com/apache/apisix-ingress-controller/commit/8348d010507f679a17f6126e779780c76358446c) chore: shorten the route name for Ingress transformations (#898)
+* [`b5448c3`](https://github.com/apache/apisix-ingress-controller/commit/b5448c37e7b043b4d2a5b5d35b115429a20760a0) fetching newest Endpoint before sync (#821)
+* [`bbaba6f`](https://github.com/apache/apisix-ingress-controller/commit/bbaba6f9c22196e5d3142ad7e1a2224474a1c553) fix: filter useless pod update event (#894)
+* [`5f6a7c1`](https://github.com/apache/apisix-ingress-controller/commit/5f6a7c10b97c5eb575d23140db433bc8dfe60a2c) fix: avoid create pluginconfig in the tranlsation of route (#845)
+* [`035c60e`](https://github.com/apache/apisix-ingress-controller/commit/035c60e456abf064af23634822f8453606fc5cc5) fix: check if stream_routes is disabled (#868)
+* [`8d25525`](https://github.com/apache/apisix-ingress-controller/commit/8d255252a206311bde756eb21424c7b6743d6187) docs: fix #887 (#890)
+* [`cc9b6be`](https://github.com/apache/apisix-ingress-controller/commit/cc9b6be606b4d41267064c45e7ef1e9f5a6d8e47) fix json unmarshal error when list plguins (#888)
+* [`e2475cf`](https://github.com/apache/apisix-ingress-controller/commit/e2475cf8573f9f1efd6956c0e4e6eab2142b8061) feat: add format tool (#885)
+* [`d30bef5`](https://github.com/apache/apisix-ingress-controller/commit/d30bef56d5502724901c7eae42b319ad1b92c76c) fix: endless retry if namespace doesn't exist (#882)
+* [`27f0cfa`](https://github.com/apache/apisix-ingress-controller/commit/27f0cfa8879712070736a91b4b67c1ea0cdd4835) update the-hard-way.md (#875)
+* [`77383b8`](https://github.com/apache/apisix-ingress-controller/commit/77383b88d9c664a64a28374c7f48cdb1d4d5f823) fix ingress delete panic (#872)
+* [`26a1f43`](https://github.com/apache/apisix-ingress-controller/commit/26a1f43d5c2f360ce280f0aa81c8bbcc12036cbd) feat: add update command to Makefile (#881)
+* [`545d22d`](https://github.com/apache/apisix-ingress-controller/commit/545d22d47414e7635c8074aaa28ad6105ae116e3) chore: clean up v1 version related code (#867)
+* [`32a096c`](https://github.com/apache/apisix-ingress-controller/commit/32a096c24dd330e537fd5fed7fcccb63fd8dd813) fix: objects get from lister must be treated as read-only (#829)
+* [`6b0c139`](https://github.com/apache/apisix-ingress-controller/commit/6b0c139ce2fbf56c683d86b8f93c7b4ef854fbc6) fix: ApisixClusterConfig e2e test case (#859)
+* [`df8316a`](https://github.com/apache/apisix-ingress-controller/commit/df8316aaceec40ae86857b1ef972a812c55b1252) rename command line options and update doc. (#848)
+* [`de52243`](https://github.com/apache/apisix-ingress-controller/commit/de522437bd3db3dc3e8d8f33c92f322de67531b8) feat: ensure that the lease can be actively released before program shutdown to reduce the time required for failover (#827)
+* [`81d59b5`](https://github.com/apache/apisix-ingress-controller/commit/81d59b52882779b7b10c8e725c8b4d80ee01f8e0) chore: update ingress/comapre.go watchingNamespac from v2beta1 to v2beta3 (#832)
+* [`3040cf5`](https://github.com/apache/apisix-ingress-controller/commit/3040cf54c5dc95a444b9c3132f5b911495ad5f80) fix: add v2beta3 register resources (#833)
+* [`56d866b`](https://github.com/apache/apisix-ingress-controller/commit/56d866bcbb1f25060a6da31b088e22bee80500db) chore: Update NOTICE to 2022 (#834)
+* [`e40cc31`](https://github.com/apache/apisix-ingress-controller/commit/e40cc3152e4b80e9b95aeca01c1b737fbe183f8f) refactor: remove BaseURL and AdminKey in config (#826)
+* [`2edc4da`](https://github.com/apache/apisix-ingress-controller/commit/2edc4da9433af58db8ae2fd317f489b287405b2c) chore: fix typo in ApidixRoute CRD (#830)
+* [`8364821`](https://github.com/apache/apisix-ingress-controller/commit/83648210fe194fa42f5582e8773969be0a01fa57) fix: consumer name contain "-" (#828)
+* [`ae69cd3`](https://github.com/apache/apisix-ingress-controller/commit/ae69cd3ee42245100b1c559df4518a418f66f6bd) docs: Grafana Dashboard Configuration (#731)
+* [`990971d`](https://github.com/apache/apisix-ingress-controller/commit/990971d4e892b47a6758edb40042c3decde92846) chore: v1.4 release (#819)
+</p>
+</details>
+
+### Dependency Changes
+
+* **github.com/beorn7/perks**                           v1.0.1 **_new_**
+* **github.com/cespare/xxhash/v2**                      v2.1.2 **_new_**
+* **github.com/davecgh/go-spew**                        v1.1.1 **_new_**
+* **github.com/emicklei/go-restful/v3**                 v3.8.0 **_new_**
+* **github.com/evanphx/json-patch**                     v4.12.0 **_new_**
+* **github.com/gin-contrib/sse**                        v0.1.0 **_new_**
+* **github.com/gin-gonic/gin**                          v1.7.7 -> v1.8.1
+* **github.com/go-logr/logr**                           v1.2.3 **_new_**
+* **github.com/go-openapi/jsonpointer**                 v0.19.5 **_new_**
+* **github.com/go-openapi/jsonreference**               v0.20.0 **_new_**
+* **github.com/go-openapi/swag**                        v0.21.1 **_new_**
+* **github.com/go-playground/locales**                  v0.14.0 **_new_**
+* **github.com/go-playground/universal-translator**     v0.18.0 **_new_**
+* **github.com/go-playground/validator/v10**            v10.11.0 **_new_**
+* **github.com/goccy/go-json**                          v0.9.10 **_new_**
+* **github.com/gogo/protobuf**                          v1.3.2 **_new_**
+* **github.com/golang/groupcache**                      41bb18bfe9da **_new_**
+* **github.com/golang/protobuf**                        v1.5.2 **_new_**
+* **github.com/google/gnostic**                         v0.6.9 **_new_**
+* **github.com/google/go-cmp**                          v0.5.7 **_new_**
+* **github.com/google/gofuzz**                          v1.1.0 **_new_**
+* **github.com/hashicorp/errwrap**                      v1.0.0 **_new_**
+* **github.com/hashicorp/go-immutable-radix**           v1.3.0 **_new_**
+* **github.com/hashicorp/go-memdb**                     v1.3.2 -> v1.3.3
+* **github.com/hashicorp/golang-lru**                   v0.5.4 **_new_**
+* **github.com/imdario/mergo**                          v0.3.12 **_new_**
+* **github.com/inconshreveable/mousetrap**              v1.0.0 **_new_**
+* **github.com/josharian/intern**                       v1.0.0 **_new_**
+* **github.com/json-iterator/go**                       v1.1.12 **_new_**
+* **github.com/leodido/go-urn**                         v1.2.1 **_new_**
+* **github.com/mailru/easyjson**                        v0.7.7 **_new_**
+* **github.com/mattn/go-isatty**                        v0.0.14 **_new_**
+* **github.com/matttproud/golang_protobuf_extensions**  c182affec369 **_new_**
+* **github.com/modern-go/concurrent**                   bacd9c7ef1dd **_new_**
+* **github.com/modern-go/reflect2**                     v1.0.2 **_new_**
+* **github.com/munnerz/goautoneg**                      a7dc8b61c822 **_new_**
+* **github.com/pelletier/go-toml/v2**                   v2.0.2 **_new_**
+* **github.com/pkg/errors**                             v0.9.1 **_new_**
+* **github.com/pmezard/go-difflib**                     v1.0.0 **_new_**
+* **github.com/prometheus/client_golang**               v1.11.0 -> v1.12.2
+* **github.com/prometheus/common**                      v0.32.1 **_new_**
+* **github.com/prometheus/procfs**                      v0.7.3 **_new_**
+* **github.com/spf13/cobra**                            v1.2.1 -> v1.5.0
+* **github.com/spf13/pflag**                            v1.0.5 **_new_**
+* **github.com/stretchr/testify**                       v1.7.0 -> v1.8.0
+* **github.com/ugorji/go/codec**                        v1.2.7 **_new_**
+* **github.com/xeipuuv/gojsonpointer**                  4e3ac2762d5f **_new_**
+* **github.com/xeipuuv/gojsonreference**                bd5ef7bd5415 **_new_**
+* **go.uber.org/atomic**                                v1.7.0 **_new_**
+* **go.uber.org/multierr**                              v1.7.0 -> v1.8.0
+* **go.uber.org/zap**                                   v1.19.1 -> v1.21.0
+* **golang.org/x/crypto**                               630584e8d5aa **_new_**
+* **golang.org/x/mod**                                  86c51ed26bb4 **_new_**
+* **golang.org/x/net**                                  fe4d6282115f -> 46097bf591d3
+* **golang.org/x/oauth2**                               d3ed0bb246c8 **_new_**
+* **golang.org/x/sys**                                  bce67f096156 -> 8c9f86f7a55f
+* **golang.org/x/term**                                 03fcf44c2211 **_new_**
+* **golang.org/x/text**                                 v0.3.7 **_new_**
+* **golang.org/x/time**                                 90d013bbcef8 **_new_**
+* **golang.org/x/tools**                                v0.1.11 **_new_**
+* **golang.org/x/xerrors**                              65e65417b02f **_new_**
+* **google.golang.org/appengine**                       v1.6.7 **_new_**
+* **google.golang.org/protobuf**                        v1.28.0 **_new_**
+* **gopkg.in/inf.v0**                                   v0.9.1 **_new_**
+* **gopkg.in/yaml.v3**                                  v3.0.1 **_new_**
+* **k8s.io/api**                                        v0.22.4 -> v0.24.3
+* **k8s.io/apimachinery**                               v0.22.4 -> v0.24.3
+* **k8s.io/client-go**                                  v0.22.4 -> v0.24.3
+* **k8s.io/code-generator**                             v0.22.1 -> v0.24.3
+* **k8s.io/gengo**                                      397b4ae3bce7 **_new_**
+* **k8s.io/klog/v2**                                    v2.70.1 **_new_**
+* **k8s.io/kube-openapi**                               011e075b9cb8 **_new_**
+* **k8s.io/utils**                                      3a6ce19ff2f9 **_new_**
+* **sigs.k8s.io/json**                                  9f7c6b3444d2 **_new_**
+* **sigs.k8s.io/structured-merge-diff/v4**              v4.2.1 **_new_**
+* **sigs.k8s.io/yaml**                                  v1.3.0 **_new_**
+
+Previous release can be found at [1.4.0](https://github.com/apache/apisix-ingress-controller/releases/tag/1.4.0)
+
 # 1.4.1
 
 Welcome to the 1.4.1 release of apisix-ingress-controller!
diff --git a/Makefile b/Makefile
index 52b7ffe4..9a2ed4b1 100644
--- a/Makefile
+++ b/Makefile
@@ -16,7 +16,7 @@
 #
 default: help
 
-VERSION ?= 1.4.1
+VERSION ?= 1.5.0-rc1
 RELEASE_SRC = apache-apisix-ingress-controller-${VERSION}-src
 REGISTRY ?="localhost:5000"
 IMAGE_TAG ?= dev
diff --git a/README.md b/README.md
index afe3e0bd..e85b5242 100644
--- a/README.md
+++ b/README.md
@@ -53,6 +53,18 @@ This project is currently general availability.
 
 [More about comparison among multiple Ingress Controllers.](https://docs.google.com/spreadsheets/d/191WWNpjJ2za6-nbG4ZoUMXMpUK8KlCIosvQB0f-oq3k/edit?ts=5fd6c769#gid=907731238)
 
+## Get started
+
+* [How to install](./install.md)
+* [Get Started](./docs/en/latest/getting-started.md)
+* [Design introduction](./docs/en/latest/design.md)
+* [FAQ](./docs/en/latest/FAQ.md)
+
+## Prerequisites
+
+Apisix ingress controller requires Kubernetes version 1.16+. Because we used `CustomResourceDefinition` v1 stable API.
+From the version 1.0.0, APISIX-ingress-controller need to work with Apache APISIX version 2.7+.
+
 ## Works with APISIX Dashboard
 
 Currently, APISIX Ingress Controller automatically manipulates some APISIX resources, which is not very compatible with APISIX Dashboard. In addition, users should not modify resources labeled `managed-by: apisix-ingress-controllers` via APISIX Dashboard.
@@ -61,11 +73,6 @@ Currently, APISIX Ingress Controller automatically manipulates some APISIX resou
 
 <img src="./docs/assets/images/apisix-ingress-controller-arch.png" alt="module" width="74.3%" height="55.9%" />
 
-## Prerequisites
-
-Apisix ingress controller requires Kubernetes version 1.16+. Because we used `CustomResourceDefinition` v1 stable API.
-From the version 1.0.0, APISIX-ingress-controller need to work with Apache APISIX version 2.7+.
-
 ## Apache APISIX Ingress vs. Kubernetes Ingress Nginx
 
 * Hot-reload during yaml apply.
@@ -75,40 +82,29 @@ From the version 1.0.0, APISIX-ingress-controller need to work with Apache APISI
 * Supports APISIX custom resources and Kubernetes native Ingress resources.
 * More active community
 
-## Contributor over time
-
-[![Contributor over time](https://contributor-graph-api.apiseven.com/contributors-svg?chart=contributorOverTime&repo=apache/apisix-ingress-controller)](https://www.apiseven.com/en/contributor-graph?chart=contributorOverTime&repo=apache/apisix-ingress-controller)
-
-## Get started
-
-* [How to install](./install.md)
-
-## Todos
+## Contributing
 
-* Support UDP definition. [#116](https://github.com/apache/apisix-ingress-controller/issues/116)
-* Support GRPC definition. [#114](https://github.com/apache/apisix-ingress-controller/issues/114)
-* Add a complete demo. [#9](https://github.com/apache/apisix-ingress-controller/issues/9)
-* More todos will display in [issues](https://github.com/apache/apisix-ingress-controller/issues)
+We welcome all kinds of contributions from the open-source community, individuals and partners.
 
-## Documents
+* [Contributing Guide](./docs/en/latest/contribute.md)
 
-* [Get Started](./docs/en/latest/getting-started.md)
-* [Design introduction](./docs/en/latest/design.md)
+### How to contribute
 
-## FAQ
+Most of the contributions that we receive are code contributions, but you can
+also contribute to the documentation or simply report solid bugs
+for us to fix.
 
-* [FAQ](./docs/en/latest/FAQ.md)
+ For new contributors, please take a look at issues with a tag called [Good first issue](https://github.com/apache/apisix-ingress-controller/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) or [Help wanted](https://github.com/apache/apisix-ingress-controller/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22).
 
-## User stories
+### How to report a bug
 
-- [aispeech: Why we create a new k8s ingress controller?(Chinese)](https://mp.weixin.qq.com/s/bmm2ibk2V7-XYneLo9XAPQ)
-- [Tencent Cloud: Why choose Apache APISIX to implement the k8s ingress controller?(Chinese)](https://www.upyun.com/opentalk/448.html)
+* **Ensure the bug was not already reported** by searching on GitHub under [Issues](https://github.com/apache/apisix-ingress-controller/issues).
 
-## Contributing
+* If you're unable to find an open issue addressing the problem, [open a new one](https://github.com/apache/apisix-ingress-controller/issues/new). Be sure to include a **title and clear description**, as much relevant information as possible, and a **code sample** or an **executable test case** demonstrating the expected behavior that is not occurring.
 
-We welcome all kinds of contributions from the open-source community, individuals and partners.
+### Contributor over time
 
-* [Contributing Guide](./docs/en/latest/contribute.md)
+[![Contributor over time](https://contributor-graph-api.apiseven.com/contributors-svg?chart=contributorOverTime&repo=apache/apisix-ingress-controller)](https://www.apiseven.com/en/contributor-graph?chart=contributorOverTime&repo=apache/apisix-ingress-controller)
 
 ## Community
 
@@ -117,6 +113,15 @@ We welcome all kinds of contributions from the open-source community, individual
 * ![Twitter Follow](https://img.shields.io/twitter/follow/ApacheAPISIX?style=social) - follow and interact with us using hashtag `#ApacheAPISIX`
 * [Bilibili video](https://space.bilibili.com/551921247)
 
+## Todos
+
+* More todos will display in [issues](https://github.com/apache/apisix-ingress-controller/issues?q=is%3Aopen+is%3Aissue+label%3Atriage%2Faccepted)
+
+## User stories
+
+- [aispeech: Why we create a new k8s ingress controller?(Chinese)](https://mp.weixin.qq.com/s/bmm2ibk2V7-XYneLo9XAPQ)
+- [Tencent Cloud: Why choose Apache APISIX to implement the k8s ingress controller?(Chinese)](https://www.upyun.com/opentalk/448.html)
+
 ## Milestone
 
 * [Milestone](https://github.com/apache/apisix-ingress-controller/milestones)
@@ -125,21 +130,3 @@ We welcome all kinds of contributions from the open-source community, individual
 
 * Ingress APISIX: the whole service that contains the proxy ([Apache APISIX](https://apisix.apache.org)) and ingress controller (apisix ingress controller).
 * apisix-ingress-controller: the ingress controller component.
-
-## How to contribute
-
-Most of the contributions that we receive are code contributions, but you can
-also contribute to the documentation or simply report solid bugs
-for us to fix.
-
- For new contributors, please take a look at issues with a tag called [Good first issue](https://github.com/apache/apisix-ingress-controller/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) or [Help wanted](https://github.com/apache/apisix-ingress-controller/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22).
-
-## How to report a bug
-
-* **Ensure the bug was not already reported** by searching on GitHub under [Issues](https://github.com/apache/apisix-ingress-controller/issues).
-
-* If you're unable to find an open issue addressing the problem, [open a new one](https://github.com/apache/apisix-ingress-controller/issues/new). Be sure to include a **title and clear description**, as much relevant information as possible, and a **code sample** or an **executable test case** demonstrating the expected behavior that is not occurring.
-
-## License
-
-[Apache License 2.0](https://github.com/apache/apisix-ingress-controller/blob/master/LICENSE)
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/install.md b/install.md
index b8d31ba5..6545db87 100644
--- a/install.md
+++ b/install.md
@@ -21,13 +21,15 @@
 
 This is an index page about installing Ingress APISIX in several environments. Click the following links on demands.
 
-* [Install Ingress APISIX on Azure AKS](./docs/en/latest/deployments/azure.md)
-* [Install Ingress APISIX on AWS EKS](./docs/en/latest/deployments/aws.md)
 * [Install Ingress APISIX on ACK](./docs/en/latest/deployments/ack.md)
+* [Install Ingress APISIX on AWS EKS](./docs/en/latest/deployments/aws.md)
+* [Install Ingress APISIX on Azure AKS](./docs/en/latest/deployments/azure.md)
 * [Install Ingress APISIX on Google Cloud GKE](./docs/en/latest/deployments/gke.md)
-* [Install Ingress APISIX on Minikube](./docs/en/latest/deployments/minikube.md)
+* [Install Ingress APISIX on KIND](./docs/en/latest/deployments/kind.md)
 * [Install Ingress APISIX on KubeSphere](./docs/en/latest/deployments/kubesphere.md)
 * [Install Ingress APISIX on K3S and RKE](./docs/en/latest/deployments/k3s-rke.md)
+* [Install Ingress APISIX on Minikube](./docs/en/latest/deployments/minikube.md)
+* [Install Ingress APISIX on TKE](./docs/en/latest/deployments/tke.md)
 
 ## Kustomize Support
 
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/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/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/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/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/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_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_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_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/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/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/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/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/providers/k8s/pod/provider.go b/pkg/providers/k8s/pod/provider.go
new file mode 100644
index 00000000..50ceef5c
--- /dev/null
+++ b/pkg/providers/k8s/pod/provider.go
@@ -0,0 +1,75 @@
+// 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 pod
+
+import (
+	"context"
+
+	"k8s.io/client-go/tools/cache"
+
+	"github.com/apache/apisix-ingress-controller/pkg/config"
+	"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/providers/utils"
+	"github.com/apache/apisix-ingress-controller/pkg/types"
+)
+
+var _ Provider = (*podProvider)(nil)
+
+type Provider interface {
+	providertypes.Provider
+
+	GetPodCache() types.PodCache
+}
+
+type podProvider struct {
+	cfg *config.Config
+
+	podInformer   cache.SharedIndexInformer
+	podController *podController
+}
+
+func NewProvider(common *providertypes.Common, namespaceProvider namespace.WatchingNamespaceProvider) (Provider, error) {
+	p := &podProvider{
+		cfg: common.Config,
+
+		podInformer: common.PodInformer,
+	}
+
+	p.podController = newPodController(common, namespaceProvider, p.podInformer)
+
+	return p, nil
+}
+
+func (p *podProvider) Run(ctx context.Context) {
+	e := utils.ParallelExecutor{}
+
+	e.Add(func() {
+		p.podInformer.Run(ctx.Done())
+	})
+
+	e.Add(func() {
+		p.podController.run(ctx)
+	})
+
+	e.Wait()
+}
+
+func (p *podProvider) GetPodCache() types.PodCache {
+	return p.podController.podCache
+}
diff --git a/pkg/providers/k8s/provider.go b/pkg/providers/k8s/provider.go
new file mode 100644
index 00000000..644222dd
--- /dev/null
+++ b/pkg/providers/k8s/provider.go
@@ -0,0 +1,80 @@
+// 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 k8s
+
+import (
+	"context"
+
+	"github.com/pkg/errors"
+	"k8s.io/client-go/tools/cache"
+
+	apisixprovider "github.com/apache/apisix-ingress-controller/pkg/providers/apisix"
+	"github.com/apache/apisix-ingress-controller/pkg/providers/k8s/endpoint"
+	"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 = (*k8sProvider)(nil)
+
+type Provider interface {
+	providertypes.Provider
+}
+
+type k8sProvider struct {
+	secretController *secretController
+	endpoint         endpoint.Provider
+
+	secretInformer cache.SharedIndexInformer
+}
+
+func NewProvider(common *providertypes.Common, translator translation.Translator,
+	namespaceProvider namespace.WatchingNamespaceProvider, apisixProvider apisixprovider.Provider) (Provider, error) {
+	var err error
+	provider := &k8sProvider{}
+
+	kubeFactory := common.KubeClient.NewSharedIndexInformerFactory()
+	provider.secretInformer = kubeFactory.Core().V1().Secrets().Informer()
+
+	provider.endpoint, err = endpoint.NewProvider(common, translator, namespaceProvider)
+	if err != nil {
+		return nil, errors.Wrap(err, "failed to init endpoint provider")
+	}
+
+	provider.secretController = newSecretController(common, translator, namespaceProvider, apisixProvider)
+
+	return provider, nil
+}
+
+func (p *k8sProvider) Run(ctx context.Context) {
+	e := utils.ParallelExecutor{}
+
+	e.Add(func() {
+		p.secretInformer.Run(ctx.Done())
+	})
+
+	e.Add(func() {
+		p.secretController.run(ctx)
+	})
+	e.Add(func() {
+		p.endpoint.Run(ctx)
+	})
+
+	e.Wait()
+}
diff --git a/pkg/ingress/secret.go b/pkg/providers/k8s/secret.go
similarity index 62%
rename from pkg/ingress/secret.go
rename to pkg/providers/k8s/secret.go
index 84f8a210..57c36a08 100644
--- a/pkg/ingress/secret.go
+++ b/pkg/providers/k8s/secret.go
@@ -13,51 +13,79 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package ingress
+package k8s
 
 import (
 	"context"
 	"fmt"
-	"sync"
 	"time"
 
 	"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"
 
 	"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"
+	apisixprovider "github.com/apache/apisix-ingress-controller/pkg/providers/apisix"
+	"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"
 	"github.com/apache/apisix-ingress-controller/pkg/types"
 	apisixv1 "github.com/apache/apisix-ingress-controller/pkg/types/apisix/v1"
 )
 
 type secretController struct {
-	controller *Controller
-	workqueue  workqueue.RateLimitingInterface
-	workers    int
+	*providertypes.Common
+
+	workqueue workqueue.RateLimitingInterface
+	workers   int
+
+	secretLister    listerscorev1.SecretLister
+	secretInformer  cache.SharedIndexInformer
+	apisixTlsLister kube.ApisixTlsLister
+
+	namespaceProvider namespace.WatchingNamespaceProvider
+	apisixProvider    apisixprovider.Provider
+
+	translator translation.Translator
 }
 
-func (c *Controller) newSecretController() *secretController {
-	ctl := &secretController{
-		controller: c,
-		workqueue:  workqueue.NewNamedRateLimitingQueue(workqueue.NewItemFastSlowRateLimiter(1*time.Second, 60*time.Second, 5), "Secrets"),
-		workers:    1,
+func newSecretController(common *providertypes.Common, translator translation.Translator,
+	namespaceProvider namespace.WatchingNamespaceProvider, apisixProvider apisixprovider.Provider) *secretController {
+	c := &secretController{
+		Common: common,
+
+		workqueue: workqueue.NewNamedRateLimitingQueue(workqueue.NewItemFastSlowRateLimiter(1*time.Second, 60*time.Second, 5), "Secrets"),
+		workers:   1,
+
+		secretLister:    common.SecretLister,
+		secretInformer:  common.SecretInformer,
+		apisixTlsLister: common.ApisixTlsLister,
+
+		namespaceProvider: namespaceProvider,
+		apisixProvider:    apisixProvider,
+		translator:        translator,
 	}
 
-	ctl.controller.secretInformer.AddEventHandler(
+	c.secretInformer.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 *secretController) run(ctx context.Context) {
@@ -65,7 +93,7 @@ func (c *secretController) run(ctx context.Context) {
 	defer log.Info("secret controller exited")
 	defer c.workqueue.ShutDown()
 
-	if ok := cache.WaitForCacheSync(ctx.Done(), c.controller.secretInformer.HasSynced); !ok {
+	if ok := cache.WaitForCacheSync(ctx.Done(), c.secretInformer.HasSynced); !ok {
 		log.Error("informers sync failed")
 		return
 	}
@@ -96,7 +124,7 @@ func (c *secretController) sync(ctx context.Context, ev *types.Event) error {
 		log.Errorf("invalid resource key: %s", key)
 		return err
 	}
-	sec, err := c.controller.secretLister.Secrets(namespace).Get(name)
+	sec, err := c.secretLister.Secrets(namespace).Get(name)
 	if err != nil {
 		if !k8serrors.IsNotFound(err) {
 			log.Errorw("failed to get Secret",
@@ -128,14 +156,12 @@ func (c *secretController) sync(ctx context.Context, ev *types.Event) error {
 
 	secretMapKey := namespace + "_" + name
 	// sync SSL in APISIX which is store in secretSSLMap
-	ssls, ok := c.controller.secretSSLMap.Load(secretMapKey)
-	if !ok {
-		// This secret is not concerned.
+	sslMap := c.apisixProvider.GetSslFromSecretKey(secretMapKey)
+	if sslMap == nil {
 		return nil
 	}
-	sslMap := ssls.(*sync.Map)
 
-	switch c.controller.cfg.Kubernetes.APIVersion {
+	switch c.Kubernetes.APIVersion {
 	case config.ApisixV2beta3:
 		sslMap.Range(c.syncV2Beta3Handler(ctx, ev, sec, key))
 	case config.ApisixV2:
@@ -154,7 +180,7 @@ func (c *secretController) syncV2Beta3Handler(ctx context.Context, ev *types.Eve
 			return true
 		}
 
-		multiVersioned, err := c.controller.apisixTlsLister.V2beta3(tlsNamespace, tlsName)
+		multiVersioned, err := c.apisixTlsLister.V2beta3(tlsNamespace, tlsName)
 		if err != nil {
 			log.Warnw("secret related ApisixTls resource not found, skip",
 				zap.String("ApisixTls", tlsMetaKey),
@@ -165,16 +191,16 @@ func (c *secretController) syncV2Beta3Handler(ctx context.Context, ev *types.Eve
 
 		// We don't expect a secret to be used as both SSL and mTLS in ApisixTls
 		if tls.Spec.Secret.Namespace == secret.Namespace && tls.Spec.Secret.Name == secret.Name {
-			cert, pkey, err := c.controller.translator.ExtractKeyPair(secret, true)
+			cert, pkey, err := translation.ExtractKeyPair(secret, true)
 			if err != nil {
 				log.Errorw("secret required by ApisixTls invalid",
 					zap.String("ApisixTls", tlsMetaKey),
 					zap.Error(err),
 				)
 				go func(tls *configv2beta3.ApisixTls) {
-					c.controller.recorderEventS(tls, corev1.EventTypeWarning, _resourceSyncAborted,
+					c.RecordEventS(tls, corev1.EventTypeWarning, utils.ResourceSyncAborted,
 						fmt.Sprintf("sync from secret %s changes failed, error: %s", secretKey, err.Error()))
-					c.controller.recordStatus(tls, _resourceSyncAborted, err, metav1.ConditionFalse, tls.GetGeneration())
+					c.recordStatus(tls, utils.ResourceSyncAborted, err, metav1.ConditionFalse, tls.GetGeneration())
 				}(tls)
 				return true
 			}
@@ -183,16 +209,16 @@ func (c *secretController) syncV2Beta3Handler(ctx context.Context, ev *types.Eve
 			ssl.Key = string(pkey)
 		} else if tls.Spec.Client != nil &&
 			tls.Spec.Client.CASecret.Namespace == secret.Namespace && tls.Spec.Client.CASecret.Name == secret.Name {
-			ca, _, err := c.controller.translator.ExtractKeyPair(secret, false)
+			ca, _, err := translation.ExtractKeyPair(secret, false)
 			if err != nil {
 				log.Errorw("ca secret required by ApisixTls invalid",
 					zap.String("ApisixTls", tlsMetaKey),
 					zap.Error(err),
 				)
 				go func(tls *configv2beta3.ApisixTls) {
-					c.controller.recorderEventS(tls, corev1.EventTypeWarning, _resourceSyncAborted,
+					c.RecordEventS(tls, corev1.EventTypeWarning, utils.ResourceSyncAborted,
 						fmt.Sprintf("sync from ca secret %s changes failed, error: %s", secretKey, err.Error()))
-					c.controller.recordStatus(tls, _resourceSyncAborted, err, metav1.ConditionFalse, tls.GetGeneration())
+					c.recordStatus(tls, utils.ResourceSyncAborted, err, metav1.ConditionFalse, tls.GetGeneration())
 				}(tls)
 				return true
 			}
@@ -209,20 +235,20 @@ func (c *secretController) syncV2Beta3Handler(ctx context.Context, ev *types.Eve
 		// Use another goroutine to send requests, to avoid
 		// long time lock occupying.
 		go func(ssl *apisixv1.Ssl, tls *configv2beta3.ApisixTls) {
-			err := c.controller.syncSSL(ctx, ssl, ev.Type)
+			err := c.SyncSSL(ctx, ssl, ev.Type)
 			if err != nil {
 				log.Errorw("failed to sync ssl to APISIX",
 					zap.Error(err),
 					zap.Any("ssl", ssl),
 					zap.Any("secret", secret),
 				)
-				c.controller.recorderEventS(tls, corev1.EventTypeWarning, _resourceSyncAborted,
+				c.RecordEventS(tls, corev1.EventTypeWarning, utils.ResourceSyncAborted,
 					fmt.Sprintf("sync from secret %s changes failed, error: %s", secretKey, err.Error()))
-				c.controller.recordStatus(tls, _resourceSyncAborted, err, metav1.ConditionFalse, tls.GetGeneration())
+				c.recordStatus(tls, utils.ResourceSyncAborted, err, metav1.ConditionFalse, tls.GetGeneration())
 			} else {
-				c.controller.recorderEventS(tls, corev1.EventTypeNormal, _resourceSynced,
+				c.RecordEventS(tls, corev1.EventTypeNormal, utils.ResourceSynced,
 					fmt.Sprintf("sync from secret %s changes", secretKey))
-				c.controller.recordStatus(tls, _resourceSynced, nil, metav1.ConditionTrue, tls.GetGeneration())
+				c.recordStatus(tls, utils.ResourceSynced, nil, metav1.ConditionTrue, tls.GetGeneration())
 			}
 		}(ssl, tls)
 		return true
@@ -239,7 +265,7 @@ func (c *secretController) syncV2Handler(ctx context.Context, ev *types.Event, s
 			return true
 		}
 
-		multiVersioned, err := c.controller.apisixTlsLister.V2(tlsNamespace, tlsName)
+		multiVersioned, err := c.apisixTlsLister.V2(tlsNamespace, tlsName)
 		if err != nil {
 			log.Warnw("secret related ApisixTls resource not found, skip",
 				zap.String("ApisixTls", tlsMetaKey),
@@ -250,16 +276,16 @@ func (c *secretController) syncV2Handler(ctx context.Context, ev *types.Event, s
 
 		// We don't expect a secret to be used as both SSL and mTLS in ApisixTls
 		if tls.Spec.Secret.Namespace == secret.Namespace && tls.Spec.Secret.Name == secret.Name {
-			cert, pkey, err := c.controller.translator.ExtractKeyPair(secret, true)
+			cert, pkey, err := translation.ExtractKeyPair(secret, true)
 			if err != nil {
 				log.Errorw("secret required by ApisixTls invalid",
 					zap.String("ApisixTls", tlsMetaKey),
 					zap.Error(err),
 				)
 				go func(tls *configv2.ApisixTls) {
-					c.controller.recorderEventS(tls, corev1.EventTypeWarning, _resourceSyncAborted,
+					c.RecordEventS(tls, corev1.EventTypeWarning, utils.ResourceSyncAborted,
 						fmt.Sprintf("sync from secret %s changes failed, error: %s", secretKey, err.Error()))
-					c.controller.recordStatus(tls, _resourceSyncAborted, err, metav1.ConditionFalse, tls.GetGeneration())
+					c.recordStatus(tls, utils.ResourceSyncAborted, err, metav1.ConditionFalse, tls.GetGeneration())
 				}(tls)
 				return true
 			}
@@ -268,16 +294,16 @@ func (c *secretController) syncV2Handler(ctx context.Context, ev *types.Event, s
 			ssl.Key = string(pkey)
 		} else if tls.Spec.Client != nil &&
 			tls.Spec.Client.CASecret.Namespace == secret.Namespace && tls.Spec.Client.CASecret.Name == secret.Name {
-			ca, _, err := c.controller.translator.ExtractKeyPair(secret, false)
+			ca, _, err := translation.ExtractKeyPair(secret, false)
 			if err != nil {
 				log.Errorw("ca secret required by ApisixTls invalid",
 					zap.String("ApisixTls", tlsMetaKey),
 					zap.Error(err),
 				)
 				go func(tls *configv2.ApisixTls) {
-					c.controller.recorderEventS(tls, corev1.EventTypeWarning, _resourceSyncAborted,
+					c.RecordEventS(tls, corev1.EventTypeWarning, utils.ResourceSyncAborted,
 						fmt.Sprintf("sync from ca secret %s changes failed, error: %s", secretKey, err.Error()))
-					c.controller.recordStatus(tls, _resourceSyncAborted, err, metav1.ConditionFalse, tls.GetGeneration())
+					c.recordStatus(tls, utils.ResourceSyncAborted, err, metav1.ConditionFalse, tls.GetGeneration())
 				}(tls)
 				return true
 			}
@@ -294,20 +320,20 @@ func (c *secretController) syncV2Handler(ctx context.Context, ev *types.Event, s
 		// Use another goroutine to send requests, to avoid
 		// long time lock occupying.
 		go func(ssl *apisixv1.Ssl, tls *configv2.ApisixTls) {
-			err := c.controller.syncSSL(ctx, ssl, ev.Type)
+			err := c.SyncSSL(ctx, ssl, ev.Type)
 			if err != nil {
 				log.Errorw("failed to sync ssl to APISIX",
 					zap.Error(err),
 					zap.Any("ssl", ssl),
 					zap.Any("secret", secret),
 				)
-				c.controller.recorderEventS(tls, corev1.EventTypeWarning, _resourceSyncAborted,
+				c.RecordEventS(tls, corev1.EventTypeWarning, utils.ResourceSyncAborted,
 					fmt.Sprintf("sync from secret %s changes failed, error: %s", secretKey, err.Error()))
-				c.controller.recordStatus(tls, _resourceSyncAborted, err, metav1.ConditionFalse, tls.GetGeneration())
+				c.recordStatus(tls, utils.ResourceSyncAborted, err, metav1.ConditionFalse, tls.GetGeneration())
 			} else {
-				c.controller.recorderEventS(tls, corev1.EventTypeNormal, _resourceSynced,
+				c.RecordEventS(tls, corev1.EventTypeNormal, utils.ResourceSynced,
 					fmt.Sprintf("sync from secret %s changes", secretKey))
-				c.controller.recordStatus(tls, _resourceSynced, nil, metav1.ConditionTrue, tls.GetGeneration())
+				c.recordStatus(tls, utils.ResourceSynced, nil, metav1.ConditionTrue, tls.GetGeneration())
 			}
 		}(ssl, tls)
 		return true
@@ -317,7 +343,7 @@ func (c *secretController) syncV2Handler(ctx context.Context, ev *types.Event, s
 func (c *secretController) handleSyncErr(obj interface{}, err error) {
 	if err == nil {
 		c.workqueue.Forget(obj)
-		c.controller.MetricsCollector.IncrSyncOperation("secret", "success")
+		c.MetricsCollector.IncrSyncOperation("secret", "success")
 		return
 	}
 	event := obj.(*types.Event)
@@ -334,7 +360,7 @@ func (c *secretController) handleSyncErr(obj interface{}, err error) {
 		zap.Error(err),
 	)
 	c.workqueue.AddRateLimited(obj)
-	c.controller.MetricsCollector.IncrSyncOperation("secret", "failure")
+	c.MetricsCollector.IncrSyncOperation("secret", "failure")
 }
 
 func (c *secretController) onAdd(obj interface{}) {
@@ -343,7 +369,7 @@ func (c *secretController) onAdd(obj interface{}) {
 		log.Errorf("found secret object with bad namespace/name: %s, ignore it", err)
 		return
 	}
-	if !c.controller.isWatchingNamespace(key) {
+	if !c.namespaceProvider.IsWatchingNamespace(key) {
 		return
 	}
 
@@ -355,7 +381,7 @@ func (c *secretController) onAdd(obj interface{}) {
 		Object: key,
 	})
 
-	c.controller.MetricsCollector.IncrEvents("secret", "add")
+	c.MetricsCollector.IncrEvents("secret", "add")
 }
 
 func (c *secretController) onUpdate(prev, curr interface{}) {
@@ -370,7 +396,7 @@ func (c *secretController) onUpdate(prev, curr interface{}) {
 		log.Errorf("found secrets object with bad namespace/name: %s, ignore it", err)
 		return
 	}
-	if !c.controller.isWatchingNamespace(key) {
+	if !c.namespaceProvider.IsWatchingNamespace(key) {
 		return
 	}
 	log.Debugw("secret update event arrived",
@@ -382,7 +408,7 @@ func (c *secretController) onUpdate(prev, curr interface{}) {
 		Object: key,
 	})
 
-	c.controller.MetricsCollector.IncrEvents("secret", "update")
+	c.MetricsCollector.IncrEvents("secret", "update")
 }
 
 func (c *secretController) onDelete(obj interface{}) {
@@ -404,7 +430,7 @@ func (c *secretController) 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(key) {
+	if !c.namespaceProvider.IsWatchingNamespace(key) {
 		return
 	}
 	log.Debugw("secret delete event arrived",
@@ -416,5 +442,67 @@ func (c *secretController) onDelete(obj interface{}) {
 		Tombstone: sec,
 	})
 
-	c.controller.MetricsCollector.IncrEvents("secret", "delete")
+	c.MetricsCollector.IncrEvents("secret", "delete")
+}
+
+// TODO: This is copied from ApisixTls, try to remove this by merge this and ApisixTls?
+// recordStatus record resources status
+func (c *secretController) 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/kube/translation/apisix_upstream.go b/pkg/providers/translation/apisix_upstream.go
similarity index 67%
rename from pkg/kube/translation/apisix_upstream.go
rename to pkg/providers/translation/apisix_upstream.go
index 053823ec..352f0eea 100644
--- a/pkg/kube/translation/apisix_upstream.go
+++ b/pkg/providers/translation/apisix_upstream.go
@@ -22,11 +22,51 @@ import (
 	apisixv1 "github.com/apache/apisix-ingress-controller/pkg/types/apisix/v1"
 )
 
+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) translateUpstreamRetriesAndTimeoutV2beta3(retries *int, timeout *configv2beta3.UpstreamTimeout, ups *apisixv1.Upstream) error {
 	if retries != nil && *retries < 0 {
-		return &translateError{
-			field:  "retries",
-			reason: "invalid value",
+		return &TranslateError{
+			Field:  "retries",
+			Reason: "invalid value",
 		}
 	}
 	ups.Retries = retries
@@ -40,25 +80,25 @@ func (t *translator) translateUpstreamRetriesAndTimeoutV2beta3(retries *int, tim
 	readTimeout := apisixv1.DefaultUpstreamTimeout
 	sendTimeout := apisixv1.DefaultUpstreamTimeout
 	if timeout.Connect.Duration < 0 {
-		return &translateError{
-			field:  "timeout.connect",
-			reason: "invalid value",
+		return &TranslateError{
+			Field:  "timeout.connect",
+			Reason: "invalid value",
 		}
 	} else if timeout.Connect.Duration > 0 {
 		connTimeout = int(timeout.Connect.Seconds())
 	}
 	if timeout.Read.Duration < 0 {
-		return &translateError{
-			field:  "timeout.read",
-			reason: "invalid value",
+		return &TranslateError{
+			Field:  "timeout.read",
+			Reason: "invalid value",
 		}
 	} else if timeout.Read.Duration > 0 {
 		readTimeout = int(timeout.Read.Seconds())
 	}
 	if timeout.Send.Duration < 0 {
-		return &translateError{
-			field:  "timeout.send",
-			reason: "invalid value",
+		return &TranslateError{
+			Field:  "timeout.send",
+			Reason: "invalid value",
 		}
 	} else if timeout.Send.Duration > 0 {
 		sendTimeout = int(timeout.Send.Seconds())
@@ -81,7 +121,7 @@ func (t *translator) translateUpstreamScheme(scheme string, ups *apisixv1.Upstre
 		ups.Scheme = scheme
 		return nil
 	default:
-		return &translateError{field: "scheme", reason: "invalid value"}
+		return &TranslateError{Field: "scheme", Reason: "invalid value"}
 	}
 }
 
@@ -108,12 +148,12 @@ func (t *translator) translateUpstreamLoadBalancerV2beta3(lb *configv2beta3.Load
 		case apisixv1.HashOnVarsCombination:
 			ups.HashOn = lb.HashOn
 		default:
-			return &translateError{field: "loadbalancer.hashOn", reason: "invalid value"}
+			return &TranslateError{Field: "loadbalancer.hashOn", Reason: "invalid value"}
 		}
 	default:
-		return &translateError{
-			field:  "loadbalancer.type",
-			reason: "invalid value",
+		return &TranslateError{
+			Field:  "loadbalancer.type",
+			Reason: "invalid value",
 		}
 	}
 	return nil
@@ -139,9 +179,9 @@ func (t *translator) translateUpstreamHealthCheckV2beta3(config *configv2beta3.H
 		}
 		hc.Active = active
 	} else {
-		return &translateError{
-			field:  "healthCheck.active",
-			reason: "not exist",
+		return &TranslateError{
+			Field:  "healthCheck.active",
+			Reason: "not exist",
 		}
 	}
 
@@ -155,16 +195,16 @@ func (t translator) translateClientTLSV2beta3(config *configv2beta3.ApisixSecret
 	}
 	s, err := t.SecretLister.Secrets(config.Namespace).Get(config.Name)
 	if err != nil {
-		return &translateError{
-			field:  "tlsSecret",
-			reason: fmt.Sprintf("get secret failed, %v", err),
+		return &TranslateError{
+			Field:  "tlsSecret",
+			Reason: fmt.Sprintf("get secret failed, %v", err),
 		}
 	}
-	cert, key, err := t.ExtractKeyPair(s, true)
+	cert, key, err := ExtractKeyPair(s, true)
 	if err != nil {
-		return &translateError{
-			field:  "tlsSecret",
-			reason: fmt.Sprintf("extract cert and key from secret failed, %v", err),
+		return &TranslateError{
+			Field:  "tlsSecret",
+			Reason: fmt.Sprintf("extract cert and key from secret failed, %v", err),
 		}
 	}
 	ups.TLS = &apisixv1.ClientTLS{
@@ -182,25 +222,25 @@ func (t *translator) translateUpstreamActiveHealthCheckV2beta3(config *configv2b
 	case "":
 		active.Type = apisixv1.HealthCheckHTTP
 	default:
-		return nil, &translateError{
-			field:  "healthCheck.active.Type",
-			reason: "invalid value",
+		return nil, &TranslateError{
+			Field:  "healthCheck.active.Type",
+			Reason: "invalid value",
 		}
 	}
 
 	active.Timeout = int(config.Timeout.Seconds())
 	if config.Port < 0 || config.Port > 65535 {
-		return nil, &translateError{
-			field:  "healthCheck.active.port",
-			reason: "invalid value",
+		return nil, &TranslateError{
+			Field:  "healthCheck.active.port",
+			Reason: "invalid value",
 		}
 	} else {
 		active.Port = config.Port
 	}
 	if config.Concurrency < 0 {
-		return nil, &translateError{
-			field:  "healthCheck.active.concurrency",
-			reason: "invalid value",
+		return nil, &TranslateError{
+			Field:  "healthCheck.active.concurrency",
+			Reason: "invalid value",
 		}
 	} else {
 		active.Concurrency = config.Concurrency
@@ -215,24 +255,24 @@ func (t *translator) translateUpstreamActiveHealthCheckV2beta3(config *configv2b
 
 	if config.Healthy != nil {
 		if config.Healthy.Successes < 0 || config.Healthy.Successes > apisixv1.HealthCheckMaxConsecutiveNumber {
-			return nil, &translateError{
-				field:  "healthCheck.active.healthy.successes",
-				reason: "invalid value",
+			return nil, &TranslateError{
+				Field:  "healthCheck.active.healthy.successes",
+				Reason: "invalid value",
 			}
 		}
 		active.Healthy.Successes = config.Healthy.Successes
 		if config.Healthy.HTTPCodes != nil && len(config.Healthy.HTTPCodes) < 1 {
-			return nil, &translateError{
-				field:  "healthCheck.active.healthy.httpCodes",
-				reason: "empty",
+			return nil, &TranslateError{
+				Field:  "healthCheck.active.healthy.httpCodes",
+				Reason: "empty",
 			}
 		}
 		active.Healthy.HTTPStatuses = config.Healthy.HTTPCodes
 
 		if config.Healthy.Interval.Duration < apisixv1.ActiveHealthCheckMinInterval {
-			return nil, &translateError{
-				field:  "healthCheck.active.healthy.interval",
-				reason: "invalid value",
+			return nil, &TranslateError{
+				Field:  "healthCheck.active.healthy.interval",
+				Reason: "invalid value",
 			}
 		}
 		active.Healthy.Interval = int(config.Healthy.Interval.Seconds())
@@ -240,34 +280,34 @@ func (t *translator) translateUpstreamActiveHealthCheckV2beta3(config *configv2b
 
 	if config.Unhealthy != nil {
 		if config.Unhealthy.HTTPFailures < 0 || config.Unhealthy.HTTPFailures > apisixv1.HealthCheckMaxConsecutiveNumber {
-			return nil, &translateError{
-				field:  "healthCheck.active.unhealthy.httpFailures",
-				reason: "invalid value",
+			return nil, &TranslateError{
+				Field:  "healthCheck.active.unhealthy.httpFailures",
+				Reason: "invalid value",
 			}
 		}
 		active.Unhealthy.HTTPFailures = config.Unhealthy.HTTPFailures
 
 		if config.Unhealthy.TCPFailures < 0 || config.Unhealthy.TCPFailures > apisixv1.HealthCheckMaxConsecutiveNumber {
-			return nil, &translateError{
-				field:  "healthCheck.active.unhealthy.tcpFailures",
-				reason: "invalid value",
+			return nil, &TranslateError{
+				Field:  "healthCheck.active.unhealthy.tcpFailures",
+				Reason: "invalid value",
 			}
 		}
 		active.Unhealthy.TCPFailures = config.Unhealthy.TCPFailures
 		active.Unhealthy.Timeouts = config.Unhealthy.Timeouts
 
 		if config.Unhealthy.HTTPCodes != nil && len(config.Unhealthy.HTTPCodes) < 1 {
-			return nil, &translateError{
-				field:  "healthCheck.active.unhealthy.httpCodes",
-				reason: "empty",
+			return nil, &TranslateError{
+				Field:  "healthCheck.active.unhealthy.httpCodes",
+				Reason: "empty",
 			}
 		}
 		active.Unhealthy.HTTPStatuses = config.Unhealthy.HTTPCodes
 
 		if config.Unhealthy.Interval.Duration < apisixv1.ActiveHealthCheckMinInterval {
-			return nil, &translateError{
-				field:  "healthCheck.active.unhealthy.interval",
-				reason: "invalid value",
+			return nil, &TranslateError{
+				Field:  "healthCheck.active.unhealthy.interval",
+				Reason: "invalid value",
 			}
 		}
 		active.Unhealthy.Interval = int(config.Unhealthy.Interval.Seconds())
@@ -284,24 +324,24 @@ func (t *translator) translateUpstreamPassiveHealthCheckV2beta3(config *configv2
 	case "":
 		passive.Type = apisixv1.HealthCheckHTTP
 	default:
-		return nil, &translateError{
-			field:  "healthCheck.passive.Type",
-			reason: "invalid value",
+		return nil, &TranslateError{
+			Field:  "healthCheck.passive.Type",
+			Reason: "invalid value",
 		}
 	}
 	if config.Healthy != nil {
 		// zero means use the default value.
 		if config.Healthy.Successes < 0 || config.Healthy.Successes > apisixv1.HealthCheckMaxConsecutiveNumber {
-			return nil, &translateError{
-				field:  "healthCheck.passive.healthy.successes",
-				reason: "invalid value",
+			return nil, &TranslateError{
+				Field:  "healthCheck.passive.healthy.successes",
+				Reason: "invalid value",
 			}
 		}
 		passive.Healthy.Successes = config.Healthy.Successes
 		if config.Healthy.HTTPCodes != nil && len(config.Healthy.HTTPCodes) < 1 {
-			return nil, &translateError{
-				field:  "healthCheck.passive.healthy.httpCodes",
-				reason: "empty",
+			return nil, &TranslateError{
+				Field:  "healthCheck.passive.healthy.httpCodes",
+				Reason: "empty",
 			}
 		}
 		passive.Healthy.HTTPStatuses = config.Healthy.HTTPCodes
@@ -309,26 +349,26 @@ func (t *translator) translateUpstreamPassiveHealthCheckV2beta3(config *configv2
 
 	if config.Unhealthy != nil {
 		if config.Unhealthy.HTTPFailures < 0 || config.Unhealthy.HTTPFailures > apisixv1.HealthCheckMaxConsecutiveNumber {
-			return nil, &translateError{
-				field:  "healthCheck.passive.unhealthy.httpFailures",
-				reason: "invalid value",
+			return nil, &TranslateError{
+				Field:  "healthCheck.passive.unhealthy.httpFailures",
+				Reason: "invalid value",
 			}
 		}
 		passive.Unhealthy.HTTPFailures = config.Unhealthy.HTTPFailures
 
 		if config.Unhealthy.TCPFailures < 0 || config.Unhealthy.TCPFailures > apisixv1.HealthCheckMaxConsecutiveNumber {
-			return nil, &translateError{
-				field:  "healthCheck.passive.unhealthy.tcpFailures",
-				reason: "invalid value",
+			return nil, &TranslateError{
+				Field:  "healthCheck.passive.unhealthy.tcpFailures",
+				Reason: "invalid value",
 			}
 		}
 		passive.Unhealthy.TCPFailures = config.Unhealthy.TCPFailures
 		passive.Unhealthy.Timeouts = config.Unhealthy.Timeouts
 
 		if config.Unhealthy.HTTPCodes != nil && len(config.Unhealthy.HTTPCodes) < 1 {
-			return nil, &translateError{
-				field:  "healthCheck.passive.unhealthy.httpCodes",
-				reason: "empty",
+			return nil, &TranslateError{
+				Field:  "healthCheck.passive.unhealthy.httpCodes",
+				Reason: "empty",
 			}
 		}
 		passive.Unhealthy.HTTPStatuses = config.Unhealthy.HTTPCodes
@@ -338,9 +378,9 @@ func (t *translator) translateUpstreamPassiveHealthCheckV2beta3(config *configv2
 
 func (t *translator) translateUpstreamRetriesAndTimeoutV2(retries *int, timeout *configv2.UpstreamTimeout, ups *apisixv1.Upstream) error {
 	if retries != nil && *retries < 0 {
-		return &translateError{
-			field:  "retries",
-			reason: "invalid value",
+		return &TranslateError{
+			Field:  "retries",
+			Reason: "invalid value",
 		}
 	}
 	ups.Retries = retries
@@ -354,25 +394,25 @@ func (t *translator) translateUpstreamRetriesAndTimeoutV2(retries *int, timeout
 	readTimeout := apisixv1.DefaultUpstreamTimeout
 	sendTimeout := apisixv1.DefaultUpstreamTimeout
 	if timeout.Connect.Duration < 0 {
-		return &translateError{
-			field:  "timeout.connect",
-			reason: "invalid value",
+		return &TranslateError{
+			Field:  "timeout.connect",
+			Reason: "invalid value",
 		}
 	} else if timeout.Connect.Duration > 0 {
 		connTimeout = int(timeout.Connect.Seconds())
 	}
 	if timeout.Read.Duration < 0 {
-		return &translateError{
-			field:  "timeout.read",
-			reason: "invalid value",
+		return &TranslateError{
+			Field:  "timeout.read",
+			Reason: "invalid value",
 		}
 	} else if timeout.Read.Duration > 0 {
 		readTimeout = int(timeout.Read.Seconds())
 	}
 	if timeout.Send.Duration < 0 {
-		return &translateError{
-			field:  "timeout.send",
-			reason: "invalid value",
+		return &TranslateError{
+			Field:  "timeout.send",
+			Reason: "invalid value",
 		}
 	} else if timeout.Send.Duration > 0 {
 		sendTimeout = int(timeout.Send.Seconds())
@@ -408,12 +448,12 @@ func (t *translator) translateUpstreamLoadBalancerV2(lb *configv2.LoadBalancer,
 		case apisixv1.HashOnVarsCombination:
 			ups.HashOn = lb.HashOn
 		default:
-			return &translateError{field: "loadbalancer.hashOn", reason: "invalid value"}
+			return &TranslateError{Field: "loadbalancer.hashOn", Reason: "invalid value"}
 		}
 	default:
-		return &translateError{
-			field:  "loadbalancer.type",
-			reason: "invalid value",
+		return &TranslateError{
+			Field:  "loadbalancer.type",
+			Reason: "invalid value",
 		}
 	}
 	return nil
@@ -439,9 +479,9 @@ func (t *translator) translateUpstreamHealthCheckV2(config *configv2.HealthCheck
 		}
 		hc.Active = active
 	} else {
-		return &translateError{
-			field:  "healthCheck.active",
-			reason: "not exist",
+		return &TranslateError{
+			Field:  "healthCheck.active",
+			Reason: "not exist",
 		}
 	}
 
@@ -449,22 +489,22 @@ func (t *translator) translateUpstreamHealthCheckV2(config *configv2.HealthCheck
 	return nil
 }
 
-func (t translator) translateClientTLSV2(config *configv2.ApisixSecret, ups *apisixv1.Upstream) error {
+func (t *translator) translateClientTLSV2(config *configv2.ApisixSecret, ups *apisixv1.Upstream) error {
 	if config == nil {
 		return nil
 	}
 	s, err := t.SecretLister.Secrets(config.Namespace).Get(config.Name)
 	if err != nil {
-		return &translateError{
-			field:  "tlsSecret",
-			reason: fmt.Sprintf("get secret failed, %v", err),
+		return &TranslateError{
+			Field:  "tlsSecret",
+			Reason: fmt.Sprintf("get secret failed, %v", err),
 		}
 	}
-	cert, key, err := t.ExtractKeyPair(s, true)
+	cert, key, err := ExtractKeyPair(s, true)
 	if err != nil {
-		return &translateError{
-			field:  "tlsSecret",
-			reason: fmt.Sprintf("extract cert and key from secret failed, %v", err),
+		return &TranslateError{
+			Field:  "tlsSecret",
+			Reason: fmt.Sprintf("extract cert and key from secret failed, %v", err),
 		}
 	}
 	ups.TLS = &apisixv1.ClientTLS{
@@ -482,25 +522,25 @@ func (t *translator) translateUpstreamActiveHealthCheckV2(config *configv2.Activ
 	case "":
 		active.Type = apisixv1.HealthCheckHTTP
 	default:
-		return nil, &translateError{
-			field:  "healthCheck.active.Type",
-			reason: "invalid value",
+		return nil, &TranslateError{
+			Field:  "healthCheck.active.Type",
+			Reason: "invalid value",
 		}
 	}
 
 	active.Timeout = int(config.Timeout.Seconds())
 	if config.Port < 0 || config.Port > 65535 {
-		return nil, &translateError{
-			field:  "healthCheck.active.port",
-			reason: "invalid value",
+		return nil, &TranslateError{
+			Field:  "healthCheck.active.port",
+			Reason: "invalid value",
 		}
 	} else {
 		active.Port = config.Port
 	}
 	if config.Concurrency < 0 {
-		return nil, &translateError{
-			field:  "healthCheck.active.concurrency",
-			reason: "invalid value",
+		return nil, &TranslateError{
+			Field:  "healthCheck.active.concurrency",
+			Reason: "invalid value",
 		}
 	} else {
 		active.Concurrency = config.Concurrency
@@ -515,24 +555,24 @@ func (t *translator) translateUpstreamActiveHealthCheckV2(config *configv2.Activ
 
 	if config.Healthy != nil {
 		if config.Healthy.Successes < 0 || config.Healthy.Successes > apisixv1.HealthCheckMaxConsecutiveNumber {
-			return nil, &translateError{
-				field:  "healthCheck.active.healthy.successes",
-				reason: "invalid value",
+			return nil, &TranslateError{
+				Field:  "healthCheck.active.healthy.successes",
+				Reason: "invalid value",
 			}
 		}
 		active.Healthy.Successes = config.Healthy.Successes
 		if config.Healthy.HTTPCodes != nil && len(config.Healthy.HTTPCodes) < 1 {
-			return nil, &translateError{
-				field:  "healthCheck.active.healthy.httpCodes",
-				reason: "empty",
+			return nil, &TranslateError{
+				Field:  "healthCheck.active.healthy.httpCodes",
+				Reason: "empty",
 			}
 		}
 		active.Healthy.HTTPStatuses = config.Healthy.HTTPCodes
 
 		if config.Healthy.Interval.Duration < apisixv1.ActiveHealthCheckMinInterval {
-			return nil, &translateError{
-				field:  "healthCheck.active.healthy.interval",
-				reason: "invalid value",
+			return nil, &TranslateError{
+				Field:  "healthCheck.active.healthy.interval",
+				Reason: "invalid value",
 			}
 		}
 		active.Healthy.Interval = int(config.Healthy.Interval.Seconds())
@@ -540,34 +580,34 @@ func (t *translator) translateUpstreamActiveHealthCheckV2(config *configv2.Activ
 
 	if config.Unhealthy != nil {
 		if config.Unhealthy.HTTPFailures < 0 || config.Unhealthy.HTTPFailures > apisixv1.HealthCheckMaxConsecutiveNumber {
-			return nil, &translateError{
-				field:  "healthCheck.active.unhealthy.httpFailures",
-				reason: "invalid value",
+			return nil, &TranslateError{
+				Field:  "healthCheck.active.unhealthy.httpFailures",
+				Reason: "invalid value",
 			}
 		}
 		active.Unhealthy.HTTPFailures = config.Unhealthy.HTTPFailures
 
 		if config.Unhealthy.TCPFailures < 0 || config.Unhealthy.TCPFailures > apisixv1.HealthCheckMaxConsecutiveNumber {
-			return nil, &translateError{
-				field:  "healthCheck.active.unhealthy.tcpFailures",
-				reason: "invalid value",
+			return nil, &TranslateError{
+				Field:  "healthCheck.active.unhealthy.tcpFailures",
+				Reason: "invalid value",
 			}
 		}
 		active.Unhealthy.TCPFailures = config.Unhealthy.TCPFailures
 		active.Unhealthy.Timeouts = config.Unhealthy.Timeouts
 
 		if config.Unhealthy.HTTPCodes != nil && len(config.Unhealthy.HTTPCodes) < 1 {
-			return nil, &translateError{
-				field:  "healthCheck.active.unhealthy.httpCodes",
-				reason: "empty",
+			return nil, &TranslateError{
+				Field:  "healthCheck.active.unhealthy.httpCodes",
+				Reason: "empty",
 			}
 		}
 		active.Unhealthy.HTTPStatuses = config.Unhealthy.HTTPCodes
 
 		if config.Unhealthy.Interval.Duration < apisixv1.ActiveHealthCheckMinInterval {
-			return nil, &translateError{
-				field:  "healthCheck.active.unhealthy.interval",
-				reason: "invalid value",
+			return nil, &TranslateError{
+				Field:  "healthCheck.active.unhealthy.interval",
+				Reason: "invalid value",
 			}
 		}
 		active.Unhealthy.Interval = int(config.Unhealthy.Interval.Seconds())
@@ -584,24 +624,24 @@ func (t *translator) translateUpstreamPassiveHealthCheckV2(config *configv2.Pass
 	case "":
 		passive.Type = apisixv1.HealthCheckHTTP
 	default:
-		return nil, &translateError{
-			field:  "healthCheck.passive.Type",
-			reason: "invalid value",
+		return nil, &TranslateError{
+			Field:  "healthCheck.passive.Type",
+			Reason: "invalid value",
 		}
 	}
 	if config.Healthy != nil {
 		// zero means use the default value.
 		if config.Healthy.Successes < 0 || config.Healthy.Successes > apisixv1.HealthCheckMaxConsecutiveNumber {
-			return nil, &translateError{
-				field:  "healthCheck.passive.healthy.successes",
-				reason: "invalid value",
+			return nil, &TranslateError{
+				Field:  "healthCheck.passive.healthy.successes",
+				Reason: "invalid value",
 			}
 		}
 		passive.Healthy.Successes = config.Healthy.Successes
 		if config.Healthy.HTTPCodes != nil && len(config.Healthy.HTTPCodes) < 1 {
-			return nil, &translateError{
-				field:  "healthCheck.passive.healthy.httpCodes",
-				reason: "empty",
+			return nil, &TranslateError{
+				Field:  "healthCheck.passive.healthy.httpCodes",
+				Reason: "empty",
 			}
 		}
 		passive.Healthy.HTTPStatuses = config.Healthy.HTTPCodes
@@ -609,26 +649,26 @@ func (t *translator) translateUpstreamPassiveHealthCheckV2(config *configv2.Pass
 
 	if config.Unhealthy != nil {
 		if config.Unhealthy.HTTPFailures < 0 || config.Unhealthy.HTTPFailures > apisixv1.HealthCheckMaxConsecutiveNumber {
-			return nil, &translateError{
-				field:  "healthCheck.passive.unhealthy.httpFailures",
-				reason: "invalid value",
+			return nil, &TranslateError{
+				Field:  "healthCheck.passive.unhealthy.httpFailures",
+				Reason: "invalid value",
 			}
 		}
 		passive.Unhealthy.HTTPFailures = config.Unhealthy.HTTPFailures
 
 		if config.Unhealthy.TCPFailures < 0 || config.Unhealthy.TCPFailures > apisixv1.HealthCheckMaxConsecutiveNumber {
-			return nil, &translateError{
-				field:  "healthCheck.passive.unhealthy.tcpFailures",
-				reason: "invalid value",
+			return nil, &TranslateError{
+				Field:  "healthCheck.passive.unhealthy.tcpFailures",
+				Reason: "invalid value",
 			}
 		}
 		passive.Unhealthy.TCPFailures = config.Unhealthy.TCPFailures
 		passive.Unhealthy.Timeouts = config.Unhealthy.Timeouts
 
 		if config.Unhealthy.HTTPCodes != nil && len(config.Unhealthy.HTTPCodes) < 1 {
-			return nil, &translateError{
-				field:  "healthCheck.passive.unhealthy.httpCodes",
-				reason: "empty",
+			return nil, &TranslateError{
+				Field:  "healthCheck.passive.unhealthy.httpCodes",
+				Reason: "empty",
 			}
 		}
 		passive.Unhealthy.HTTPStatuses = config.Unhealthy.HTTPCodes
diff --git a/pkg/kube/translation/context.go b/pkg/providers/translation/context.go
similarity index 91%
rename from pkg/kube/translation/context.go
rename to pkg/providers/translation/context.go
index 10aafb62..784a51f2 100644
--- a/pkg/kube/translation/context.go
+++ b/pkg/providers/translation/context.go
@@ -21,14 +21,14 @@ type TranslateContext struct {
 	Routes        []*apisix.Route
 	StreamRoutes  []*apisix.StreamRoute
 	Upstreams     []*apisix.Upstream
-	upstreamMap   map[string]struct{}
+	UpstreamMap   map[string]struct{}
 	SSL           []*apisix.Ssl
 	PluginConfigs []*apisix.PluginConfig
 }
 
 func DefaultEmptyTranslateContext() *TranslateContext {
 	return &TranslateContext{
-		upstreamMap: make(map[string]struct{}),
+		UpstreamMap: make(map[string]struct{}),
 	}
 }
 
@@ -45,15 +45,15 @@ func (tc *TranslateContext) AddStreamRoute(sr *apisix.StreamRoute) {
 }
 
 func (tc *TranslateContext) AddUpstream(u *apisix.Upstream) {
-	if _, ok := tc.upstreamMap[u.Name]; ok {
+	if _, ok := tc.UpstreamMap[u.Name]; ok {
 		return
 	}
-	tc.upstreamMap[u.Name] = struct{}{}
+	tc.UpstreamMap[u.Name] = struct{}{}
 	tc.Upstreams = append(tc.Upstreams, u)
 }
 
 func (tc *TranslateContext) CheckUpstreamExist(name string) (ok bool) {
-	_, ok = tc.upstreamMap[name]
+	_, ok = tc.UpstreamMap[name]
 	return
 }
 
diff --git a/pkg/providers/translation/service.go b/pkg/providers/translation/service.go
new file mode 100644
index 00000000..196e7c45
--- /dev/null
+++ b/pkg/providers/translation/service.go
@@ -0,0 +1,249 @@
+// 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"
+
+	"github.com/apache/apisix-ingress-controller/pkg/config"
+	"github.com/apache/apisix-ingress-controller/pkg/kube"
+	"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"
+)
+
+func (t *translator) TranslateService(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.TranslateEndpoint(*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.TranslateEndpoint(*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) TranslateEndpoint(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) 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.PodProvider.GetPodCache().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
+}
diff --git a/pkg/providers/translation/translator.go b/pkg/providers/translation/translator.go
new file mode 100644
index 00000000..13ee36b2
--- /dev/null
+++ b/pkg/providers/translation/translator.go
@@ -0,0 +1,89 @@
+// 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"
+
+	listerscorev1 "k8s.io/client-go/listers/core/v1"
+
+	"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/providers/k8s/pod"
+	"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)
+}
+
+type Translator interface {
+	// 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.
+	TranslateService(string, string, string, int32) (*apisixv1.Upstream, error)
+	// TranslateUpstreamNodes translate Endpoints resources to APISIX Upstream nodes
+	// according to the give port. Extra labels can be passed to filter the ultimate
+	// upstream nodes.
+	TranslateEndpoint(kube.Endpoint, int32, types.Labels) (apisixv1.UpstreamNodes, error)
+}
+
+// TranslatorOptions contains options to help Translator
+// work well.
+type TranslatorOptions struct {
+	APIVersion string
+
+	EndpointLister       kube.EndpointLister
+	ServiceLister        listerscorev1.ServiceLister
+	SecretLister         listerscorev1.SecretLister
+	PodLister            listerscorev1.PodLister
+	ApisixUpstreamLister kube.ApisixUpstreamLister
+
+	PodProvider pod.Provider
+}
+
+type translator struct {
+	*TranslatorOptions
+}
+
+// NewTranslator initializes a APISIX CRD resources Translator.
+func NewTranslator(opts *TranslatorOptions) Translator {
+	return &translator{
+		TranslatorOptions: opts,
+	}
+}
diff --git a/pkg/providers/translation/util.go b/pkg/providers/translation/util.go
new file mode 100644
index 00000000..acc09813
--- /dev/null
+++ b/pkg/providers/translation/util.go
@@ -0,0 +1,88 @@
+// 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"
+
+	v1 "k8s.io/api/core/v1"
+)
+
+var (
+	_errInvalidAddress = errors.New("address is neither IP or CIDR")
+	// 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")
+)
+
+// ExtractKeyPair extracts certificate and private key pair from secret
+// Supports APISIX style ("cert" and "key") and Kube style ("tls.crt" and "tls.key)
+func ExtractKeyPair(s *v1.Secret, hasPrivateKey bool) ([]byte, []byte, error) {
+	if _, ok := s.Data["cert"]; ok {
+		return extractApisixSecretKeyPair(s, hasPrivateKey)
+	} else if _, ok := s.Data[v1.TLSCertKey]; ok {
+		return extractKubeSecretKeyPair(s, hasPrivateKey)
+	} else {
+		return nil, nil, ErrUnknownSecretFormat
+	}
+}
+
+func 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 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
+}
+
+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/providers/types/types.go b/pkg/providers/types/types.go
new file mode 100644
index 00000000..ea93b9fd
--- /dev/null
+++ b/pkg/providers/types/types.go
@@ -0,0 +1,183 @@
+// 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 types
+
+import (
+	"context"
+	"fmt"
+
+	"go.uber.org/zap"
+	"k8s.io/apimachinery/pkg/runtime"
+	listerscorev1 "k8s.io/client-go/listers/core/v1"
+	"k8s.io/client-go/tools/cache"
+	"k8s.io/client-go/tools/record"
+
+	"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/kube"
+	"github.com/apache/apisix-ingress-controller/pkg/log"
+	"github.com/apache/apisix-ingress-controller/pkg/metrics"
+	"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 Provider interface {
+	// Init() // TODO: should all provider implement this?
+	Run(ctx context.Context)
+}
+
+type ListerInformer struct {
+	EpLister   kube.EndpointLister
+	EpInformer cache.SharedIndexInformer
+
+	SvcLister   listerscorev1.ServiceLister
+	SvcInformer cache.SharedIndexInformer
+
+	SecretLister   listerscorev1.SecretLister
+	SecretInformer cache.SharedIndexInformer
+
+	PodLister   listerscorev1.PodLister
+	PodInformer cache.SharedIndexInformer
+
+	ApisixUpstreamLister   kube.ApisixUpstreamLister
+	ApisixUpstreamInformer cache.SharedIndexInformer
+	ApisixTlsLister        kube.ApisixTlsLister
+	ApisixTlsInformer      cache.SharedIndexInformer
+}
+
+func (c *ListerInformer) Run(ctx context.Context) {
+	e := utils.ParallelExecutor{}
+
+	e.Add(func() {
+		c.EpInformer.Run(ctx.Done())
+	})
+	e.Add(func() {
+		c.SvcInformer.Run(ctx.Done())
+	})
+	e.Add(func() {
+		c.SecretInformer.Run(ctx.Done())
+	})
+	e.Add(func() {
+		c.PodInformer.Run(ctx.Done())
+	})
+	e.Add(func() {
+		c.ApisixUpstreamInformer.Run(ctx.Done())
+	})
+	e.Add(func() {
+		c.ApisixTlsInformer.Run(ctx.Done())
+	})
+
+	e.Wait()
+}
+
+type Common struct {
+	*config.Config
+	*ListerInformer
+
+	APISIX           apisix.APISIX
+	KubeClient       *kube.KubeClient
+	MetricsCollector metrics.Collector
+	Recorder         record.EventRecorder
+}
+
+// RecordEvent recorder events for resources
+func (c *Common) RecordEvent(object runtime.Object, eventtype, reason string, err error) {
+	if err != nil {
+		message := fmt.Sprintf(utils.MessageResourceFailed, utils.Component, err.Error())
+		c.Recorder.Event(object, eventtype, reason, message)
+	} else {
+		message := fmt.Sprintf(utils.MessageResourceSynced, utils.Component)
+		c.Recorder.Event(object, eventtype, reason, message)
+	}
+}
+
+// RecordEventS recorder events for resources
+func (c *Common) RecordEventS(object runtime.Object, eventtype, reason string, msg string) {
+	c.Recorder.Event(object, eventtype, reason, msg)
+}
+
+// TODO: Move sync utils to apisix.APISIX interface?
+func (c *Common) SyncManifests(ctx context.Context, added, updated, deleted *utils.Manifest) error {
+	return utils.SyncManifests(ctx, c.APISIX, c.Config.APISIX.DefaultClusterName, added, updated, deleted)
+}
+
+func (c *Common) SyncSSL(ctx context.Context, ssl *apisixv1.Ssl, event types.EventType) error {
+	var (
+		err error
+	)
+	clusterName := c.Config.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 *Common) SyncConsumer(ctx context.Context, consumer *apisixv1.Consumer, event types.EventType) (err error) {
+	clusterName := c.Config.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 *Common) SyncUpstreamNodesChangeToCluster(ctx context.Context, cluster apisix.Cluster, nodes apisixv1.UpstreamNodes, upsName string) error {
+	log.Debugw("sync upstream nodes change",
+		zap.String("cluster", cluster.String()),
+		zap.String("upstream_name", upsName),
+		zap.Any("nodes", nodes),
+	)
+	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)
+}
diff --git a/pkg/ingress/utils/executor.go b/pkg/providers/utils/executor.go
similarity index 100%
rename from pkg/ingress/utils/executor.go
rename to pkg/providers/utils/executor.go
diff --git a/pkg/ingress/utils/ingress_status.go b/pkg/providers/utils/ingress_status.go
similarity index 100%
rename from pkg/ingress/utils/ingress_status.go
rename to pkg/providers/utils/ingress_status.go
diff --git a/pkg/ingress/utils/manifest.go b/pkg/providers/utils/manifest.go
similarity index 100%
rename from pkg/ingress/utils/manifest.go
rename to pkg/providers/utils/manifest.go
diff --git a/pkg/providers/utils/status.go b/pkg/providers/utils/status.go
new file mode 100644
index 00000000..e8bd62df
--- /dev/null
+++ b/pkg/providers/utils/status.go
@@ -0,0 +1,66 @@
+// 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 utils
+
+import (
+	"fmt"
+
+	"k8s.io/apimachinery/pkg/api/meta"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/client-go/tools/record"
+)
+
+const (
+	ConditionType        = "ResourcesAvailable"
+	CommonSuccessMessage = "Sync Successfully"
+
+	// 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"
+)
+
+// RecorderEvent recorder events for resources
+func RecorderEvent(recorder record.EventRecorder, object runtime.Object, eventtype, reason string, err error) {
+	if err != nil {
+		message := fmt.Sprintf(MessageResourceFailed, Component, err.Error())
+		recorder.Event(object, eventtype, reason, message)
+	} else {
+		message := fmt.Sprintf(MessageResourceSynced, Component)
+		recorder.Event(object, eventtype, reason, message)
+	}
+}
+
+// RecorderEventS recorder events for resources
+func RecorderEventS(recorder record.EventRecorder, object runtime.Object, eventtype, reason string, msg string) {
+	recorder.Event(object, eventtype, reason, msg)
+}
+
+// VerifyGeneration verify generation to decide whether to update status
+func 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
+}
diff --git a/pkg/ingress/utils/string.go b/pkg/providers/utils/string.go
similarity index 100%
rename from pkg/ingress/utils/string.go
rename to pkg/providers/utils/string.go
diff --git a/pkg/types/pod.go b/pkg/types/pod.go
index 28e16612..b09bc535 100644
--- a/pkg/types/pod.go
+++ b/pkg/types/pod.go
@@ -29,7 +29,7 @@ var (
 	ErrPodNotFound = errors.New("pod not found")
 )
 
-// PodCache caches pod. Currently it doesn't cache the pod object but only its
+// PodCache caches pod. Currently, it doesn't cache the pod object but only its
 // name.
 type PodCache interface {
 	// Add adds a pod to cache, only pod which state is RUNNING will be
diff --git a/releases/v1.5.0.toml b/releases/v1.5.0.toml
new file mode 100644
index 00000000..7d83678c
--- /dev/null
+++ b/releases/v1.5.0.toml
@@ -0,0 +1,51 @@
+#
+# 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.
+#
+# commit to be tagged for new release
+commit = "HEAD"
+
+# project_name is used to refer to the project in the notes
+project_name = "apisix-ingress-controller"
+
+# github_repo is the github project, only github is currently supported
+github_repo = "apache/apisix-ingress-controller"
+
+# match_deps is a pattern to determine which dependencies should be included
+# as part of this release. The changelog will also include changes for these
+# dependencies based on the change in the dependency's version.
+match_deps = "^github.com/(apache/[a-zA-Z0-9-]+)$"
+
+# previous release of this project for determining changes
+previous = "1.4.0"
+
+# pre_release is whether to include a disclaimer about being a pre-release
+pre_release = false
+
+# preface is the description of the release which precedes the author list
+# and changelog. This description could include highlights as well as any
+# description of changes. Use markdown formatting.
+preface = """\
+This is a feature release.
+
+## Highlights
+
+The API version of all custom resources has been upgraded to v2 in this release and mark v2beta3 as deprecated. We plan to remove the v2beta2 API version in the next release. Please go to [#707](https://github.com/apache/apisix-ingress-controller/issues/707) for detail.
+
+We have added partial support for Gateway API, which is not enabled by default, you can set `enable_gateway_api=true` to enable it.
+
+Ingress resources can now use all APISIX plugin configurations by setting the annotation `k8s.apisix.apache.org/plugin-config-name=xxx`.
+
+"""



Test patch:
diff --git a/docs/en/latest/FAQ.md b/docs/en/latest/FAQ.md
index d9f7f52a..2eb0c835 100644
--- a/docs/en/latest/FAQ.md
+++ b/docs/en/latest/FAQ.md
@@ -79,7 +79,7 @@ Ref to FAQ #7.
 
 ### 9. Modify the Admin API key in APISIX-Ingress
 
-Usually, you need to modify the Admin API key to protect Apache APISIX. Please refer to this [link](https://apisix.apache.org/docs/apisix/how-to-build/#updating-admin-api-key) to simply change Apache APISIX.
+Usually, you need to modify the Admin API key to protect Apache APISIX. Please refer to this [link](https://apisix.apache.org/docs/apisix/installation-guide/#updating-admin-api-key) to simply change Apache APISIX.
 
 However, in apisix-ingress-controller, if we need to change the Admin API key, we also need to change the Admin API key in apisix-ingress-controller. There are two different ways to implement the requirements here.
 
diff --git a/docs/en/latest/config.json b/docs/en/latest/config.json
index 0bc6b36b..f60a7968 100644
--- a/docs/en/latest/config.json
+++ b/docs/en/latest/config.json
@@ -31,8 +31,9 @@
         "deployments/aws",
         "deployments/azure",
         "deployments/gke",
-        "deployments/k3s-rke",
+        "deployments/kind",
         "deployments/kubesphere",
+        "deployments/k3s-rke",
         "deployments/minikube",
         "deployments/tke"
       ]
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/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/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_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_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_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_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/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/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/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/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_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/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/kube/translation/apisix_upstream_test.go b/pkg/providers/translation/apisix_upstream_test.go
similarity index 81%
rename from pkg/kube/translation/apisix_upstream_test.go
rename to pkg/providers/translation/apisix_upstream_test.go
index 19de3c4c..b70be4b5 100644
--- a/pkg/kube/translation/apisix_upstream_test.go
+++ b/pkg/providers/translation/apisix_upstream_test.go
@@ -106,9 +106,9 @@ func TestTranslateUpstreamPassiveHealthCheckUnusuallyV2beta3(t *testing.T) {
 	}
 
 	err := tr.translateUpstreamHealthCheckV2beta3(hc, nil)
-	assert.Equal(t, &translateError{
-		field:  "healthCheck.passive.Type",
-		reason: "invalid value",
+	assert.Equal(t, &TranslateError{
+		Field:  "healthCheck.passive.Type",
+		Reason: "invalid value",
 	}, err)
 
 	// invalid passive health check healthy successes
@@ -121,9 +121,9 @@ func TestTranslateUpstreamPassiveHealthCheckUnusuallyV2beta3(t *testing.T) {
 		},
 	}
 	err = tr.translateUpstreamHealthCheckV2beta3(hc, nil)
-	assert.Equal(t, &translateError{
-		field:  "healthCheck.passive.healthy.successes",
-		reason: "invalid value",
+	assert.Equal(t, &TranslateError{
+		Field:  "healthCheck.passive.healthy.successes",
+		Reason: "invalid value",
 	}, err)
 
 	// empty passive health check healthy httpCodes.
@@ -137,9 +137,9 @@ func TestTranslateUpstreamPassiveHealthCheckUnusuallyV2beta3(t *testing.T) {
 		},
 	}
 	err = tr.translateUpstreamHealthCheckV2beta3(hc, nil)
-	assert.Equal(t, &translateError{
-		field:  "healthCheck.passive.healthy.httpCodes",
-		reason: "empty",
+	assert.Equal(t, &TranslateError{
+		Field:  "healthCheck.passive.healthy.httpCodes",
+		Reason: "empty",
 	}, err)
 
 	// empty passive health check unhealthy httpFailures.
@@ -152,9 +152,9 @@ func TestTranslateUpstreamPassiveHealthCheckUnusuallyV2beta3(t *testing.T) {
 		},
 	}
 	err = tr.translateUpstreamHealthCheckV2beta3(hc, nil)
-	assert.Equal(t, &translateError{
-		field:  "healthCheck.passive.unhealthy.httpFailures",
-		reason: "invalid value",
+	assert.Equal(t, &TranslateError{
+		Field:  "healthCheck.passive.unhealthy.httpFailures",
+		Reason: "invalid value",
 	}, err)
 
 	// empty passive health check unhealthy tcpFailures.
@@ -167,9 +167,9 @@ func TestTranslateUpstreamPassiveHealthCheckUnusuallyV2beta3(t *testing.T) {
 		},
 	}
 	err = tr.translateUpstreamHealthCheckV2beta3(hc, nil)
-	assert.Equal(t, &translateError{
-		field:  "healthCheck.passive.unhealthy.tcpFailures",
-		reason: "invalid value",
+	assert.Equal(t, &TranslateError{
+		Field:  "healthCheck.passive.unhealthy.tcpFailures",
+		Reason: "invalid value",
 	}, err)
 
 	// empty passive health check unhealthy httpCodes.
@@ -182,9 +182,9 @@ func TestTranslateUpstreamPassiveHealthCheckUnusuallyV2beta3(t *testing.T) {
 		},
 	}
 	err = tr.translateUpstreamHealthCheckV2beta3(hc, nil)
-	assert.Equal(t, &translateError{
-		field:  "healthCheck.passive.unhealthy.httpCodes",
-		reason: "empty",
+	assert.Equal(t, &TranslateError{
+		Field:  "healthCheck.passive.unhealthy.httpCodes",
+		Reason: "empty",
 	}, err)
 }
 
@@ -198,9 +198,9 @@ func TestTranslateUpstreamActiveHealthCheckUnusuallyV2beta3(t *testing.T) {
 		},
 	}
 	err := tr.translateUpstreamHealthCheckV2beta3(hc, nil)
-	assert.Equal(t, &translateError{
-		field:  "healthCheck.active.Type",
-		reason: "invalid value",
+	assert.Equal(t, &TranslateError{
+		Field:  "healthCheck.active.Type",
+		Reason: "invalid value",
 	}, err)
 
 	// invalid active health check port value
@@ -211,9 +211,9 @@ func TestTranslateUpstreamActiveHealthCheckUnusuallyV2beta3(t *testing.T) {
 		},
 	}
 	err = tr.translateUpstreamHealthCheckV2beta3(hc, nil)
-	assert.Equal(t, &translateError{
-		field:  "healthCheck.active.port",
-		reason: "invalid value",
+	assert.Equal(t, &TranslateError{
+		Field:  "healthCheck.active.port",
+		Reason: "invalid value",
 	}, err)
 
 	// invalid active health check concurrency value
@@ -224,9 +224,9 @@ func TestTranslateUpstreamActiveHealthCheckUnusuallyV2beta3(t *testing.T) {
 		},
 	}
 	err = tr.translateUpstreamHealthCheckV2beta3(hc, nil)
-	assert.Equal(t, &translateError{
-		field:  "healthCheck.active.concurrency",
-		reason: "invalid value",
+	assert.Equal(t, &TranslateError{
+		Field:  "healthCheck.active.concurrency",
+		Reason: "invalid value",
 	}, err)
 
 	// invalid active health check healthy successes value
@@ -241,9 +241,9 @@ func TestTranslateUpstreamActiveHealthCheckUnusuallyV2beta3(t *testing.T) {
 		},
 	}
 	err = tr.translateUpstreamHealthCheckV2beta3(hc, nil)
-	assert.Equal(t, &translateError{
-		field:  "healthCheck.active.healthy.successes",
-		reason: "invalid value",
+	assert.Equal(t, &TranslateError{
+		Field:  "healthCheck.active.healthy.successes",
+		Reason: "invalid value",
 	}, err)
 
 	// invalid active health check healthy successes value
@@ -258,9 +258,9 @@ func TestTranslateUpstreamActiveHealthCheckUnusuallyV2beta3(t *testing.T) {
 		},
 	}
 	err = tr.translateUpstreamHealthCheckV2beta3(hc, nil)
-	assert.Equal(t, &translateError{
-		field:  "healthCheck.active.healthy.httpCodes",
-		reason: "empty",
+	assert.Equal(t, &TranslateError{
+		Field:  "healthCheck.active.healthy.httpCodes",
+		Reason: "empty",
 	}, err)
 
 	// invalid active health check healthy interval
@@ -273,9 +273,9 @@ func TestTranslateUpstreamActiveHealthCheckUnusuallyV2beta3(t *testing.T) {
 		},
 	}
 	err = tr.translateUpstreamHealthCheckV2beta3(hc, nil)
-	assert.Equal(t, &translateError{
-		field:  "healthCheck.active.healthy.interval",
-		reason: "invalid value",
+	assert.Equal(t, &TranslateError{
+		Field:  "healthCheck.active.healthy.interval",
+		Reason: "invalid value",
 	}, err)
 
 	// missing active health check healthy interval
@@ -286,9 +286,9 @@ func TestTranslateUpstreamActiveHealthCheckUnusuallyV2beta3(t *testing.T) {
 		},
 	}
 	err = tr.translateUpstreamHealthCheckV2beta3(hc, nil)
-	assert.Equal(t, &translateError{
-		field:  "healthCheck.active.healthy.interval",
-		reason: "invalid value",
+	assert.Equal(t, &TranslateError{
+		Field:  "healthCheck.active.healthy.interval",
+		Reason: "invalid value",
 	}, err)
 
 	// invalid active health check unhealthy httpFailures
@@ -303,9 +303,9 @@ func TestTranslateUpstreamActiveHealthCheckUnusuallyV2beta3(t *testing.T) {
 		},
 	}
 	err = tr.translateUpstreamHealthCheckV2beta3(hc, nil)
-	assert.Equal(t, &translateError{
-		field:  "healthCheck.active.unhealthy.httpFailures",
-		reason: "invalid value",
+	assert.Equal(t, &TranslateError{
+		Field:  "healthCheck.active.unhealthy.httpFailures",
+		Reason: "invalid value",
 	}, err)
 
 	// invalid active health check unhealthy tcpFailures
@@ -320,9 +320,9 @@ func TestTranslateUpstreamActiveHealthCheckUnusuallyV2beta3(t *testing.T) {
 		},
 	}
 	err = tr.translateUpstreamHealthCheckV2beta3(hc, nil)
-	assert.Equal(t, &translateError{
-		field:  "healthCheck.active.unhealthy.tcpFailures",
-		reason: "invalid value",
+	assert.Equal(t, &TranslateError{
+		Field:  "healthCheck.active.unhealthy.tcpFailures",
+		Reason: "invalid value",
 	}, err)
 
 	// invalid active health check unhealthy httpCodes
@@ -337,9 +337,9 @@ func TestTranslateUpstreamActiveHealthCheckUnusuallyV2beta3(t *testing.T) {
 		},
 	}
 	err = tr.translateUpstreamHealthCheckV2beta3(hc, nil)
-	assert.Equal(t, &translateError{
-		field:  "healthCheck.active.unhealthy.httpCodes",
-		reason: "empty",
+	assert.Equal(t, &TranslateError{
+		Field:  "healthCheck.active.unhealthy.httpCodes",
+		Reason: "empty",
 	}, err)
 
 	// invalid active health check unhealthy interval
@@ -352,9 +352,9 @@ func TestTranslateUpstreamActiveHealthCheckUnusuallyV2beta3(t *testing.T) {
 		},
 	}
 	err = tr.translateUpstreamHealthCheckV2beta3(hc, nil)
-	assert.Equal(t, &translateError{
-		field:  "healthCheck.active.unhealthy.interval",
-		reason: "invalid value",
+	assert.Equal(t, &TranslateError{
+		Field:  "healthCheck.active.unhealthy.interval",
+		Reason: "invalid value",
 	}, err)
 
 	// missing active health check unhealthy interval
@@ -365,9 +365,9 @@ func TestTranslateUpstreamActiveHealthCheckUnusuallyV2beta3(t *testing.T) {
 		},
 	}
 	err = tr.translateUpstreamHealthCheckV2beta3(hc, nil)
-	assert.Equal(t, &translateError{
-		field:  "healthCheck.active.unhealthy.interval",
-		reason: "invalid value",
+	assert.Equal(t, &TranslateError{
+		Field:  "healthCheck.active.unhealthy.interval",
+		Reason: "invalid value",
 	}, err)
 }
 
@@ -375,9 +375,9 @@ func TestUpstreamRetriesAndTimeoutV2beta3(t *testing.T) {
 	tr := &translator{}
 	retries := -1
 	err := tr.translateUpstreamRetriesAndTimeoutV2beta3(&retries, nil, nil)
-	assert.Equal(t, &translateError{
-		field:  "retries",
-		reason: "invalid value",
+	assert.Equal(t, &TranslateError{
+		Field:  "retries",
+		Reason: "invalid value",
 	}, err)
 
 	var ups apisixv1.Upstream
@@ -392,9 +392,9 @@ func TestUpstreamRetriesAndTimeoutV2beta3(t *testing.T) {
 	}
 	retries = 3
 	err = tr.translateUpstreamRetriesAndTimeoutV2beta3(&retries, timeout, &ups)
-	assert.Equal(t, &translateError{
-		field:  "timeout.read",
-		reason: "invalid value",
+	assert.Equal(t, &TranslateError{
+		Field:  "timeout.read",
+		Reason: "invalid value",
 	}, err)
 
 	timeout = &configv2beta3.UpstreamTimeout{
@@ -491,9 +491,9 @@ func TestTranslateUpstreamPassiveHealthCheckUnusuallyV2(t *testing.T) {
 	}
 
 	err := tr.translateUpstreamHealthCheckV2(hc, nil)
-	assert.Equal(t, &translateError{
-		field:  "healthCheck.passive.Type",
-		reason: "invalid value",
+	assert.Equal(t, &TranslateError{
+		Field:  "healthCheck.passive.Type",
+		Reason: "invalid value",
 	}, err)
 
 	// invalid passive health check healthy successes
@@ -506,9 +506,9 @@ func TestTranslateUpstreamPassiveHealthCheckUnusuallyV2(t *testing.T) {
 		},
 	}
 	err = tr.translateUpstreamHealthCheckV2(hc, nil)
-	assert.Equal(t, &translateError{
-		field:  "healthCheck.passive.healthy.successes",
-		reason: "invalid value",
+	assert.Equal(t, &TranslateError{
+		Field:  "healthCheck.passive.healthy.successes",
+		Reason: "invalid value",
 	}, err)
 
 	// empty passive health check healthy httpCodes.
@@ -522,9 +522,9 @@ func TestTranslateUpstreamPassiveHealthCheckUnusuallyV2(t *testing.T) {
 		},
 	}
 	err = tr.translateUpstreamHealthCheckV2(hc, nil)
-	assert.Equal(t, &translateError{
-		field:  "healthCheck.passive.healthy.httpCodes",
-		reason: "empty",
+	assert.Equal(t, &TranslateError{
+		Field:  "healthCheck.passive.healthy.httpCodes",
+		Reason: "empty",
 	}, err)
 
 	// empty passive health check unhealthy httpFailures.
@@ -537,9 +537,9 @@ func TestTranslateUpstreamPassiveHealthCheckUnusuallyV2(t *testing.T) {
 		},
 	}
 	err = tr.translateUpstreamHealthCheckV2(hc, nil)
-	assert.Equal(t, &translateError{
-		field:  "healthCheck.passive.unhealthy.httpFailures",
-		reason: "invalid value",
+	assert.Equal(t, &TranslateError{
+		Field:  "healthCheck.passive.unhealthy.httpFailures",
+		Reason: "invalid value",
 	}, err)
 
 	// empty passive health check unhealthy tcpFailures.
@@ -552,9 +552,9 @@ func TestTranslateUpstreamPassiveHealthCheckUnusuallyV2(t *testing.T) {
 		},
 	}
 	err = tr.translateUpstreamHealthCheckV2(hc, nil)
-	assert.Equal(t, &translateError{
-		field:  "healthCheck.passive.unhealthy.tcpFailures",
-		reason: "invalid value",
+	assert.Equal(t, &TranslateError{
+		Field:  "healthCheck.passive.unhealthy.tcpFailures",
+		Reason: "invalid value",
 	}, err)
 
 	// empty passive health check unhealthy httpCodes.
@@ -567,9 +567,9 @@ func TestTranslateUpstreamPassiveHealthCheckUnusuallyV2(t *testing.T) {
 		},
 	}
 	err = tr.translateUpstreamHealthCheckV2(hc, nil)
-	assert.Equal(t, &translateError{
-		field:  "healthCheck.passive.unhealthy.httpCodes",
-		reason: "empty",
+	assert.Equal(t, &TranslateError{
+		Field:  "healthCheck.passive.unhealthy.httpCodes",
+		Reason: "empty",
 	}, err)
 }
 
@@ -583,9 +583,9 @@ func TestTranslateUpstreamActiveHealthCheckUnusuallyV2(t *testing.T) {
 		},
 	}
 	err := tr.translateUpstreamHealthCheckV2(hc, nil)
-	assert.Equal(t, &translateError{
-		field:  "healthCheck.active.Type",
-		reason: "invalid value",
+	assert.Equal(t, &TranslateError{
+		Field:  "healthCheck.active.Type",
+		Reason: "invalid value",
 	}, err)
 
 	// invalid active health check port value
@@ -596,9 +596,9 @@ func TestTranslateUpstreamActiveHealthCheckUnusuallyV2(t *testing.T) {
 		},
 	}
 	err = tr.translateUpstreamHealthCheckV2(hc, nil)
-	assert.Equal(t, &translateError{
-		field:  "healthCheck.active.port",
-		reason: "invalid value",
+	assert.Equal(t, &TranslateError{
+		Field:  "healthCheck.active.port",
+		Reason: "invalid value",
 	}, err)
 
 	// invalid active health check concurrency value
@@ -609,9 +609,9 @@ func TestTranslateUpstreamActiveHealthCheckUnusuallyV2(t *testing.T) {
 		},
 	}
 	err = tr.translateUpstreamHealthCheckV2(hc, nil)
-	assert.Equal(t, &translateError{
-		field:  "healthCheck.active.concurrency",
-		reason: "invalid value",
+	assert.Equal(t, &TranslateError{
+		Field:  "healthCheck.active.concurrency",
+		Reason: "invalid value",
 	}, err)
 
 	// invalid active health check healthy successes value
@@ -626,9 +626,9 @@ func TestTranslateUpstreamActiveHealthCheckUnusuallyV2(t *testing.T) {
 		},
 	}
 	err = tr.translateUpstreamHealthCheckV2(hc, nil)
-	assert.Equal(t, &translateError{
-		field:  "healthCheck.active.healthy.successes",
-		reason: "invalid value",
+	assert.Equal(t, &TranslateError{
+		Field:  "healthCheck.active.healthy.successes",
+		Reason: "invalid value",
 	}, err)
 
 	// invalid active health check healthy successes value
@@ -643,9 +643,9 @@ func TestTranslateUpstreamActiveHealthCheckUnusuallyV2(t *testing.T) {
 		},
 	}
 	err = tr.translateUpstreamHealthCheckV2(hc, nil)
-	assert.Equal(t, &translateError{
-		field:  "healthCheck.active.healthy.httpCodes",
-		reason: "empty",
+	assert.Equal(t, &TranslateError{
+		Field:  "healthCheck.active.healthy.httpCodes",
+		Reason: "empty",
 	}, err)
 
 	// invalid active health check healthy interval
@@ -658,9 +658,9 @@ func TestTranslateUpstreamActiveHealthCheckUnusuallyV2(t *testing.T) {
 		},
 	}
 	err = tr.translateUpstreamHealthCheckV2(hc, nil)
-	assert.Equal(t, &translateError{
-		field:  "healthCheck.active.healthy.interval",
-		reason: "invalid value",
+	assert.Equal(t, &TranslateError{
+		Field:  "healthCheck.active.healthy.interval",
+		Reason: "invalid value",
 	}, err)
 
 	// missing active health check healthy interval
@@ -671,9 +671,9 @@ func TestTranslateUpstreamActiveHealthCheckUnusuallyV2(t *testing.T) {
 		},
 	}
 	err = tr.translateUpstreamHealthCheckV2(hc, nil)
-	assert.Equal(t, &translateError{
-		field:  "healthCheck.active.healthy.interval",
-		reason: "invalid value",
+	assert.Equal(t, &TranslateError{
+		Field:  "healthCheck.active.healthy.interval",
+		Reason: "invalid value",
 	}, err)
 
 	// invalid active health check unhealthy httpFailures
@@ -688,9 +688,9 @@ func TestTranslateUpstreamActiveHealthCheckUnusuallyV2(t *testing.T) {
 		},
 	}
 	err = tr.translateUpstreamHealthCheckV2(hc, nil)
-	assert.Equal(t, &translateError{
-		field:  "healthCheck.active.unhealthy.httpFailures",
-		reason: "invalid value",
+	assert.Equal(t, &TranslateError{
+		Field:  "healthCheck.active.unhealthy.httpFailures",
+		Reason: "invalid value",
 	}, err)
 
 	// invalid active health check unhealthy tcpFailures
@@ -705,9 +705,9 @@ func TestTranslateUpstreamActiveHealthCheckUnusuallyV2(t *testing.T) {
 		},
 	}
 	err = tr.translateUpstreamHealthCheckV2(hc, nil)
-	assert.Equal(t, &translateError{
-		field:  "healthCheck.active.unhealthy.tcpFailures",
-		reason: "invalid value",
+	assert.Equal(t, &TranslateError{
+		Field:  "healthCheck.active.unhealthy.tcpFailures",
+		Reason: "invalid value",
 	}, err)
 
 	// invalid active health check unhealthy httpCodes
@@ -722,9 +722,9 @@ func TestTranslateUpstreamActiveHealthCheckUnusuallyV2(t *testing.T) {
 		},
 	}
 	err = tr.translateUpstreamHealthCheckV2(hc, nil)
-	assert.Equal(t, &translateError{
-		field:  "healthCheck.active.unhealthy.httpCodes",
-		reason: "empty",
+	assert.Equal(t, &TranslateError{
+		Field:  "healthCheck.active.unhealthy.httpCodes",
+		Reason: "empty",
 	}, err)
 
 	// invalid active health check unhealthy interval
@@ -737,9 +737,9 @@ func TestTranslateUpstreamActiveHealthCheckUnusuallyV2(t *testing.T) {
 		},
 	}
 	err = tr.translateUpstreamHealthCheckV2(hc, nil)
-	assert.Equal(t, &translateError{
-		field:  "healthCheck.active.unhealthy.interval",
-		reason: "invalid value",
+	assert.Equal(t, &TranslateError{
+		Field:  "healthCheck.active.unhealthy.interval",
+		Reason: "invalid value",
 	}, err)
 
 	// missing active health check unhealthy interval
@@ -750,9 +750,9 @@ func TestTranslateUpstreamActiveHealthCheckUnusuallyV2(t *testing.T) {
 		},
 	}
 	err = tr.translateUpstreamHealthCheckV2(hc, nil)
-	assert.Equal(t, &translateError{
-		field:  "healthCheck.active.unhealthy.interval",
-		reason: "invalid value",
+	assert.Equal(t, &TranslateError{
+		Field:  "healthCheck.active.unhealthy.interval",
+		Reason: "invalid value",
 	}, err)
 }
 
@@ -760,9 +760,9 @@ func TestUpstreamRetriesAndTimeoutV2(t *testing.T) {
 	tr := &translator{}
 	retries := -1
 	err := tr.translateUpstreamRetriesAndTimeoutV2(&retries, nil, nil)
-	assert.Equal(t, &translateError{
-		field:  "retries",
-		reason: "invalid value",
+	assert.Equal(t, &TranslateError{
+		Field:  "retries",
+		Reason: "invalid value",
 	}, err)
 
 	var ups apisixv1.Upstream
@@ -777,9 +777,9 @@ func TestUpstreamRetriesAndTimeoutV2(t *testing.T) {
 	}
 	retries = 3
 	err = tr.translateUpstreamRetriesAndTimeoutV2(&retries, timeout, &ups)
-	assert.Equal(t, &translateError{
-		field:  "timeout.read",
-		reason: "invalid value",
+	assert.Equal(t, &TranslateError{
+		Field:  "timeout.read",
+		Reason: "invalid value",
 	}, err)
 
 	timeout = &configv2.UpstreamTimeout{
diff --git a/pkg/kube/translation/context_test.go b/pkg/providers/translation/context_test.go
similarity index 100%
rename from pkg/kube/translation/context_test.go
rename to pkg/providers/translation/context_test.go
diff --git a/pkg/kube/translation/translator_test.go b/pkg/providers/translation/translator_test.go
similarity index 88%
rename from pkg/kube/translation/translator_test.go
rename to pkg/providers/translation/translator_test.go
index 59d271fe..73ecf974 100644
--- a/pkg/kube/translation/translator_test.go
+++ b/pkg/providers/translation/translator_test.go
@@ -70,9 +70,9 @@ func TestTranslateUpstreamConfigV2beta3(t *testing.T) {
 		Scheme: "dns",
 	}
 	_, err = tr.TranslateUpstreamConfigV2beta3(au)
-	assert.Error(t, err, &translateError{
-		field:  "scheme",
-		reason: "invalid value",
+	assert.Error(t, err, &TranslateError{
+		Field:  "scheme",
+		Reason: "invalid value",
 	})
 
 	au = &configv2beta3.ApisixUpstreamConfig{
@@ -81,9 +81,9 @@ func TestTranslateUpstreamConfigV2beta3(t *testing.T) {
 		},
 	}
 	_, err = tr.TranslateUpstreamConfigV2beta3(au)
-	assert.Error(t, err, &translateError{
-		field:  "loadbalancer.type",
-		reason: "invalid value",
+	assert.Error(t, err, &TranslateError{
+		Field:  "loadbalancer.type",
+		Reason: "invalid value",
 	})
 
 	au = &configv2beta3.ApisixUpstreamConfig{
@@ -93,9 +93,9 @@ func TestTranslateUpstreamConfigV2beta3(t *testing.T) {
 		},
 	}
 	_, err = tr.TranslateUpstreamConfigV2beta3(au)
-	assert.Error(t, err, &translateError{
-		field:  "loadbalancer.hashOn",
-		reason: "invalid value",
+	assert.Error(t, err, &TranslateError{
+		Field:  "loadbalancer.hashOn",
+		Reason: "invalid value",
 	})
 }
 
@@ -136,9 +136,9 @@ func TestTranslateUpstreamConfigV2(t *testing.T) {
 		Scheme: "dns",
 	}
 	_, err = tr.TranslateUpstreamConfigV2(au)
-	assert.Error(t, err, &translateError{
-		field:  "scheme",
-		reason: "invalid value",
+	assert.Error(t, err, &TranslateError{
+		Field:  "scheme",
+		Reason: "invalid value",
 	})
 
 	au = &configv2.ApisixUpstreamConfig{
@@ -147,9 +147,9 @@ func TestTranslateUpstreamConfigV2(t *testing.T) {
 		},
 	}
 	_, err = tr.TranslateUpstreamConfigV2(au)
-	assert.Error(t, err, &translateError{
-		field:  "loadbalancer.type",
-		reason: "invalid value",
+	assert.Error(t, err, &TranslateError{
+		Field:  "loadbalancer.type",
+		Reason: "invalid value",
 	})
 
 	au = &configv2.ApisixUpstreamConfig{
@@ -159,9 +159,9 @@ func TestTranslateUpstreamConfigV2(t *testing.T) {
 		},
 	}
 	_, err = tr.TranslateUpstreamConfigV2(au)
-	assert.Error(t, err, &translateError{
-		field:  "loadbalancer.hashOn",
-		reason: "invalid value",
+	assert.Error(t, err, &TranslateError{
+		Field:  "loadbalancer.hashOn",
+		Reason: "invalid value",
 	})
 }
 
@@ -244,14 +244,14 @@ func TestTranslateUpstreamNodes(t *testing.T) {
 	}}
 	<-processCh
 
-	nodes, err := tr.TranslateUpstreamNodes(kube.NewEndpoint(endpoints), 10080, nil)
+	nodes, err := tr.TranslateEndpoint(kube.NewEndpoint(endpoints), 10080, nil)
 	assert.Nil(t, nodes)
-	assert.Equal(t, &translateError{
-		field:  "service.spec.ports",
-		reason: "port not defined",
+	assert.Equal(t, &TranslateError{
+		Field:  "service.spec.ports",
+		Reason: "port not defined",
 	}, err)
 
-	nodes, err = tr.TranslateUpstreamNodes(kube.NewEndpoint(endpoints), 80, nil)
+	nodes, err = tr.TranslateEndpoint(kube.NewEndpoint(endpoints), 80, nil)
 	assert.Nil(t, err)
 	assert.Equal(t, apisixv1.UpstreamNodes{
 		{
@@ -266,7 +266,7 @@ func TestTranslateUpstreamNodes(t *testing.T) {
 		},
 	}, nodes)
 
-	nodes, err = tr.TranslateUpstreamNodes(kube.NewEndpoint(endpoints), 443, nil)
+	nodes, err = tr.TranslateEndpoint(kube.NewEndpoint(endpoints), 443, nil)
 	assert.Nil(t, err)
 	assert.Equal(t, apisixv1.UpstreamNodes{
 		{
@@ -373,14 +373,14 @@ func TestTranslateUpstreamNodesWithEndpointSlices(t *testing.T) {
 	}}
 	<-processCh
 
-	nodes, err := tr.TranslateUpstreamNodes(kube.NewEndpointWithSlice(ep), 10080, nil)
+	nodes, err := tr.TranslateEndpoint(kube.NewEndpointWithSlice(ep), 10080, nil)
 	assert.Nil(t, nodes)
-	assert.Equal(t, err, &translateError{
-		field:  "service.spec.ports",
-		reason: "port not defined",
+	assert.Equal(t, err, &TranslateError{
+		Field:  "service.spec.ports",
+		Reason: "port not defined",
 	})
 
-	nodes, err = tr.TranslateUpstreamNodes(kube.NewEndpointWithSlice(ep), 80, nil)
+	nodes, err = tr.TranslateEndpoint(kube.NewEndpointWithSlice(ep), 80, nil)
 	assert.Nil(t, err)
 	assert.Equal(t, apisixv1.UpstreamNodes{
 		{
@@ -395,7 +395,7 @@ func TestTranslateUpstreamNodesWithEndpointSlices(t *testing.T) {
 		},
 	}, nodes)
 
-	nodes, err = tr.TranslateUpstreamNodes(kube.NewEndpointWithSlice(ep), 443, nil)
+	nodes, err = tr.TranslateEndpoint(kube.NewEndpointWithSlice(ep), 443, nil)
 	assert.Nil(t, err)
 	assert.Equal(t, apisixv1.UpstreamNodes{
 		{
diff --git a/pkg/kube/translation/util_test.go b/pkg/providers/translation/util_test.go
similarity index 93%
rename from pkg/kube/translation/util_test.go
rename to pkg/providers/translation/util_test.go
index 4c812d38..a55e1e23 100644
--- a/pkg/kube/translation/util_test.go
+++ b/pkg/providers/translation/util_test.go
@@ -26,7 +26,7 @@ func TestValidateRemoteAddrs(t *testing.T) {
 		"192.168.3.3/16",
 		"123",
 	}
-	err := validateRemoteAddrs(addrs)
+	err := ValidateRemoteAddrs(addrs)
 	assert.Equal(t, _errInvalidAddress, err)
 
 	addrs = []string{
@@ -35,5 +35,5 @@ func TestValidateRemoteAddrs(t *testing.T) {
 		"2001:db8::68",
 		"2001:db8:a0b:12f0::1/32",
 	}
-	assert.Nil(t, validateRemoteAddrs(addrs))
+	assert.Nil(t, ValidateRemoteAddrs(addrs))
 }
diff --git a/pkg/ingress/utils/manifest_test.go b/pkg/providers/utils/manifest_test.go
similarity index 100%
rename from pkg/ingress/utils/manifest_test.go
rename to pkg/providers/utils/manifest_test.go
diff --git a/test/e2e/scaffold/ingress.go b/test/e2e/scaffold/ingress.go
index 1e712033..4da381dc 100644
--- a/test/e2e/scaffold/ingress.go
+++ b/test/e2e/scaffold/ingress.go
@@ -19,6 +19,8 @@ import (
 	"context"
 	"encoding/base64"
 	"fmt"
+	"os"
+	"strings"
 	"time"
 
 	"github.com/gruntwork-io/terratest/modules/k8s"
@@ -211,7 +213,97 @@ subjects:
   name: ingress-apisix-e2e-test-service-account
   namespace: %s
 `
-	_ingressAPISIXDeploymentTemplate = `
+	_ingressAPISIXAdmissionService = `
+apiVersion: v1
+kind: Service
+metadata:
+  name: webhook
+  namespace: %s
+spec:
+  ports:
+    - name: https
+      protocol: TCP
+      port: 8443
+      targetPort: 8443
+  selector:
+    app: ingress-apisix-controller-deployment-e2e-test
+`
+	_ingressAPISIXAdmissionWebhook = `
+apiVersion: admissionregistration.k8s.io/v1beta1
+kind: ValidatingWebhookConfiguration
+metadata:
+  name: apisix-validation-webhooks-e2e-test
+webhooks:
+  - name: apisixroute-validator-webhook.apisix.apache.org
+    clientConfig:
+      service:
+        name: webhook
+        namespace: %s
+        port: 8443
+        path: "/validation/apisixroutes"
+      caBundle: %s
+    rules:
+      - operations: [ "CREATE", "UPDATE" ]
+        apiGroups: ["apisix.apache.org"]
+        apiVersions: ["*"]
+        resources: ["apisixroutes"]
+    timeoutSeconds: 30
+    failurePolicy: Fail
+  - name: apisixconsumer-validator-webhook.apisix.apache.org
+    clientConfig:
+      service:
+        name: webhook
+        namespace: %s
+        port: 8443
+        path: "/validation/apisixconsumers"
+      caBundle: %s
+    rules:
+      - operations: [ "CREATE", "UPDATE" ]
+        apiGroups: ["apisix.apache.org"]
+        apiVersions: ["*"]
+        resources: ["apisixconsumers"]
+    timeoutSeconds: 30
+    failurePolicy: Fail
+  - name: apisixtls-validator-webhook.apisix.apache.org
+    clientConfig:
+      service:
+        name: webhook
+        namespace: %s
+        port: 8443
+        path: "/validation/apisixtlses"
+      caBundle: %s
+    rules:
+      - operations: [ "CREATE", "UPDATE" ]
+        apiGroups: ["apisix.apache.org"]
+        apiVersions: ["*"]
+        resources: ["apisixtlses"]
+    timeoutSeconds: 30
+    failurePolicy: Fail
+  - name: apisixupstream-validator-webhook.apisix.apache.org
+    clientConfig:
+      service:
+        name: webhook
+        namespace: %s
+        port: 8443
+        path: "/validation/apisixupstreams"
+      caBundle: %s
+    rules:
+      - operations: [ "CREATE", "UPDATE" ]
+        apiGroups: ["apisix.apache.org"]
+        apiVersions: ["*"]
+        resources: ["apisixupstreams"]
+    timeoutSeconds: 30
+    failurePolicy: Fail
+`
+	_webhookCertSecret = "webhook-certs"
+	_volumeMounts      = `volumeMounts:
+           - name: webhook-certs
+             mountPath: /etc/webhook/certs
+             readOnly: true
+`
+)
+
+var _ingressAPISIXDeploymentTemplate = `
 apiVersion: apps/v1
 kind: Deployment
 metadata:
@@ -308,95 +400,12 @@ spec:
            secretName: %s
       serviceAccount: ingress-apisix-e2e-test-service-account
 `
-	_ingressAPISIXAdmissionService = `
-apiVersion: v1
-kind: Service
-metadata:
-  name: webhook
-  namespace: %s
-spec:
-  ports:
-    - name: https
-      protocol: TCP
-      port: 8443
-      targetPort: 8443
-  selector:
-    app: ingress-apisix-controller-deployment-e2e-test
-`
-	_ingressAPISIXAdmissionWebhook = `
-apiVersion: admissionregistration.k8s.io/v1beta1
-kind: ValidatingWebhookConfiguration
-metadata:
-  name: apisix-validation-webhooks-e2e-test
-webhooks:
-  - name: apisixroute-validator-webhook.apisix.apache.org
-    clientConfig:
-      service:
-        name: webhook
-        namespace: %s
-        port: 8443
-        path: "/validation/apisixroutes"
-      caBundle: %s
-    rules:
-      - operations: [ "CREATE", "UPDATE" ]
-        apiGroups: ["apisix.apache.org"]
-        apiVersions: ["*"]
-        resources: ["apisixroutes"]
-    timeoutSeconds: 30
-    failurePolicy: Fail
-  - name: apisixconsumer-validator-webhook.apisix.apache.org
-    clientConfig:
-      service:
-        name: webhook
-        namespace: %s
-        port: 8443
-        path: "/validation/apisixconsumers"
-      caBundle: %s
-    rules:
-      - operations: [ "CREATE", "UPDATE" ]
-        apiGroups: ["apisix.apache.org"]
-        apiVersions: ["*"]
-        resources: ["apisixconsumers"]
-    timeoutSeconds: 30
-    failurePolicy: Fail
-  - name: apisixtls-validator-webhook.apisix.apache.org
-    clientConfig:
-      service:
-        name: webhook
-        namespace: %s
-        port: 8443
-        path: "/validation/apisixtlses"
-      caBundle: %s
-    rules:
-      - operations: [ "CREATE", "UPDATE" ]
-        apiGroups: ["apisix.apache.org"]
-        apiVersions: ["*"]
-        resources: ["apisixtlses"]
-    timeoutSeconds: 30
-    failurePolicy: Fail
-  - name: apisixupstream-validator-webhook.apisix.apache.org
-    clientConfig:
-      service:
-        name: webhook
-        namespace: %s
-        port: 8443
-        path: "/validation/apisixupstreams"
-      caBundle: %s
-    rules:
-      - operations: [ "CREATE", "UPDATE" ]
-        apiGroups: ["apisix.apache.org"]
-        apiVersions: ["*"]
-        resources: ["apisixupstreams"]
-    timeoutSeconds: 30
-    failurePolicy: Fail
-`
-	_webhookCertSecret = "webhook-certs"
-	_volumeMounts      = `volumeMounts:
-           - name: webhook-certs
-             mountPath: /etc/webhook/certs
-             readOnly: true
-`
-)
+
+func init() {
+	if os.Getenv("E2E_ENV") != "ci" {
+		_ingressAPISIXDeploymentTemplate = strings.Replace(_ingressAPISIXDeploymentTemplate, "imagePullPolicy: IfNotPresent", "imagePullPolicy: Always", -1)
+	}
+}
 
 func (s *Scaffold) newIngressAPISIXController() error {
 	err := k8s.CreateServiceAccountE(s.t, s.kubectlOptions, _serviceAccount)
diff --git a/test/e2e/scaffold/scaffold.go b/test/e2e/scaffold/scaffold.go
index b2b2e4c0..a8b97be9 100644
--- a/test/e2e/scaffold/scaffold.go
+++ b/test/e2e/scaffold/scaffold.go
@@ -427,7 +427,7 @@ func (s *Scaffold) afterEach() {
 	defer ginkgo.GinkgoRecover()
 
 	if ginkgo.CurrentSpecReport().Failed() {
-		if os.Getenv("E2E_ENV") != "dev" {
+		if os.Getenv("E2E_ENV") == "ci" {
 			// dump and delete related resource
 			_, _ = fmt.Fprintln(ginkgo.GinkgoWriter, "Dumping namespace contents")
 			output, _ := k8s.RunKubectlAndGetOutputE(ginkgo.GinkgoT(), s.kubectlOptions, "get", "deploy,sts,svc,pods")