You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@shenyu.apache.org by xi...@apache.org on 2023/05/08 10:15:22 UTC

[shenyu] branch master updated: [type: feature] init shenyu ingress controller (#4620)

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

xiaoyu pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/shenyu.git


The following commit(s) were added to refs/heads/master by this push:
     new e7c3fae19 [type: feature] init shenyu ingress controller (#4620)
e7c3fae19 is described below

commit e7c3fae1936862122368124fc59c663bad243a34
Author: Kunshuai Zhu <jo...@gmail.com>
AuthorDate: Mon May 8 18:15:13 2023 +0800

    [type: feature] init shenyu ingress controller (#4620)
    
    * [type: feature] init shenyu ingress controller
    
    * fix: change parent of starter-k8s to starter
    
    * fix: fix shenyu-dist image name
---
 pom.xml                                            |   8 +
 shenyu-bootstrap/pom.xml                           |   8 +
 .../src/main/resources/application.yml             |  13 +
 shenyu-common/pom.xml                              |  10 +
 .../common/config}/NettyChannelProperties.java     |   2 +-
 .../shenyu/common/config}/NettyHttpProperties.java | 121 +++++-
 .../common/config/ssl/ShenyuSniAsyncMapping.java   | 126 +++++++
 .../shenyu/common/config/ssl/SslCrtAndKey.java     |  24 ++
 .../shenyu/common/config/ssl/SslCrtAndKeyFile.java |  93 +++++
 .../common/config/ssl/SslCrtAndKeyStream.java      |  68 ++++
 .../pom.xml                                        |  33 +-
 .../org/apache/shenyu/k8s/cache/IngressCache.java  |  85 +++++
 .../shenyu/k8s/cache/IngressSecretCache.java       | 124 +++++++
 .../shenyu/k8s/cache/IngressSelectorCache.java     | 123 +++++++
 .../apache/shenyu/k8s/cache/K8sResourceCache.java  |  53 +++
 .../org/apache/shenyu/k8s/cache/SelectorCache.java |  57 +++
 .../shenyu/k8s/cache/ServiceIngressCache.java      | 101 +++++
 .../apache/shenyu/k8s/common/IngressConstants.java |  40 ++
 .../shenyu/k8s/common/ShenyuMemoryConfig.java      |  97 +++++
 .../apache/shenyu/k8s/parser/IngressParser.java    | 356 ++++++++++++++++++
 .../shenyu/k8s/parser/K8sResourceParser.java       |  39 ++
 .../shenyu/k8s/reconciler/EndpointsReconciler.java | 171 +++++++++
 .../shenyu/k8s/reconciler/IngressReconciler.java   | 406 +++++++++++++++++++++
 .../k8s/repository/ShenyuCacheRepository.java      | 135 +++++++
 .../apache/shenyu/plugin/base/trie/ShenyuTrie.java |   4 +-
 shenyu-spring-boot-starter/pom.xml                 |   1 +
 .../netty/ShenyuNettyWebServerConfiguration.java   |  81 +++-
 .../ShenyuNettyWebServerConfigurationTest.java     |   1 +
 .../{ => shenyu-spring-boot-starter-k8s}/pom.xml   |  30 +-
 .../k8s/IngressControllerConfiguration.java        | 264 ++++++++++++++
 .../src/main/resources/META-INF/spring.factories   |   3 +
 .../src/main/resources/META-INF/spring.provides    |   1 +
 .../ShenyuThreadPoolConfiguration.java             |   5 +-
 33 files changed, 2642 insertions(+), 41 deletions(-)

diff --git a/pom.xml b/pom.xml
index 06f6f9b1b..b1f0db302 100644
--- a/pom.xml
+++ b/pom.xml
@@ -45,6 +45,7 @@
         <module>shenyu-alert</module>
         <module>shenyu-sdk</module>
         <module>shenyu-discovery</module>
+        <module>shenyu-kubernetes-controller</module>
     </modules>
 
     <licenses>
@@ -138,6 +139,7 @@
         <clickhouse-http-client.version>0.3.2-patch11</clickhouse-http-client.version>
         <eureka.version>1.10.17</eureka.version>
         <javatuples.version>1.2</javatuples.version>
+        <k8s-client.version>17.0.2</k8s-client.version>
         <!--maven plugin version-->
         <exec-maven-plugin.version>1.6.0</exec-maven-plugin.version>
         <jacoco-maven-plugin.version>0.8.7</jacoco-maven-plugin.version>
@@ -504,6 +506,12 @@
                 <artifactId>eureka-client</artifactId>
                 <version>${eureka.version}</version>
             </dependency>
+
+            <dependency>
+                <groupId>io.kubernetes</groupId>
+                <artifactId>client-java-spring-integration</artifactId>
+                <version>${k8s-client.version}</version>
+            </dependency>
         </dependencies>
     </dependencyManagement>
 
diff --git a/shenyu-bootstrap/pom.xml b/shenyu-bootstrap/pom.xml
index c0fce4281..29efa85bc 100644
--- a/shenyu-bootstrap/pom.xml
+++ b/shenyu-bootstrap/pom.xml
@@ -571,6 +571,14 @@
         </dependency>
         <!-- shenyu brpc plugin end -->
 
+        <!-- shenyu kubernetes controller starter -->
+<!--        <dependency>-->
+<!--            <groupId>org.apache.shenyu</groupId>-->
+<!--            <artifactId>shenyu-spring-boot-starter-k8s</artifactId>-->
+<!--            <version>${project.version}</version>-->
+<!--        </dependency>-->
+        <!-- shenyu kubernetes controller end -->
+
     </dependencies>
     <profiles>
         <profile>
diff --git a/shenyu-bootstrap/src/main/resources/application.yml b/shenyu-bootstrap/src/main/resources/application.yml
index 87e400a51..e49e6a083 100644
--- a/shenyu-bootstrap/src/main/resources/application.yml
+++ b/shenyu-bootstrap/src/main/resources/application.yml
@@ -116,6 +116,19 @@ shenyu:
         allocType: "pooled"
         messageSizeEstimator: 8
         singleEventExecutorPerGroup: true
+      sni:
+        enabled: false
+        mod: k8s #manul
+        defaultK8sSecretNamespace: shenyu-ingress
+        defaultK8sSecretName: default-cert
+#        mod: manual
+#        certificates:
+#          - domain: 'localhost'
+#            keyCertChainFile: '/Users/zhukunshuai/Desktop/cert/example.com+1.pem'
+#            keyFile: '/Users/zhukunshuai/Desktop/cert/example.com+1-key.pem'
+#          - domain: 'example.com'
+#            keyCertChainFile: '/Users/zhukunshuai/Desktop/cert/example.com+1.pem'
+#            keyFile: '/Users/zhukunshuai/Desktop/cert/example.com+1-key.pem'
 #  httpclient:
 #    strategy: webClient
 #    connectTimeout: 45000
diff --git a/shenyu-common/pom.xml b/shenyu-common/pom.xml
index d860e682b..051aa35c6 100644
--- a/shenyu-common/pom.xml
+++ b/shenyu-common/pom.xml
@@ -73,5 +73,15 @@
             <groupId>org.ow2.asm</groupId>
             <artifactId>asm-tree</artifactId>
         </dependency>
+
+        <dependency>
+            <groupId>io.projectreactor.netty</groupId>
+            <artifactId>reactor-netty-core</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>io.projectreactor.netty</groupId>
+            <artifactId>reactor-netty-http</artifactId>
+        </dependency>
     </dependencies>
 </project>
diff --git a/shenyu-spring-boot-starter/shenyu-spring-boot-starter-gateway/src/main/java/org/apache/shenyu/springboot/starter/netty/NettyChannelProperties.java b/shenyu-common/src/main/java/org/apache/shenyu/common/config/NettyChannelProperties.java
similarity index 99%
rename from shenyu-spring-boot-starter/shenyu-spring-boot-starter-gateway/src/main/java/org/apache/shenyu/springboot/starter/netty/NettyChannelProperties.java
rename to shenyu-common/src/main/java/org/apache/shenyu/common/config/NettyChannelProperties.java
index 20600f7a7..0a18fbb92 100644
--- a/shenyu-spring-boot-starter/shenyu-spring-boot-starter-gateway/src/main/java/org/apache/shenyu/springboot/starter/netty/NettyChannelProperties.java
+++ b/shenyu-common/src/main/java/org/apache/shenyu/common/config/NettyChannelProperties.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.apache.shenyu.springboot.starter.netty;
+package org.apache.shenyu.common.config;
 
 import io.netty.buffer.ByteBufAllocator;
 import io.netty.buffer.PooledByteBufAllocator;
diff --git a/shenyu-spring-boot-starter/shenyu-spring-boot-starter-gateway/src/main/java/org/apache/shenyu/springboot/starter/netty/NettyHttpProperties.java b/shenyu-common/src/main/java/org/apache/shenyu/common/config/NettyHttpProperties.java
similarity index 70%
rename from shenyu-spring-boot-starter/shenyu-spring-boot-starter-gateway/src/main/java/org/apache/shenyu/springboot/starter/netty/NettyHttpProperties.java
rename to shenyu-common/src/main/java/org/apache/shenyu/common/config/NettyHttpProperties.java
index b0cb71eac..1c14d796d 100644
--- a/shenyu-spring-boot-starter/shenyu-spring-boot-starter-gateway/src/main/java/org/apache/shenyu/springboot/starter/netty/NettyHttpProperties.java
+++ b/shenyu-common/src/main/java/org/apache/shenyu/common/config/NettyHttpProperties.java
@@ -15,7 +15,11 @@
  * limitations under the License.
  */
 
-package org.apache.shenyu.springboot.starter.netty;
+package org.apache.shenyu.common.config;
+
+import org.apache.shenyu.common.config.ssl.SslCrtAndKeyFile;
+
+import java.util.List;
 
 /**
  * The netty tcp properties.
@@ -34,6 +38,8 @@ public class NettyHttpProperties {
 
     private SocketChannelProperties socketChannel = new SocketChannelProperties();
 
+    private SniProperties sni = new SniProperties();
+
     /**
      * get webServerFactoryEnabled.
      *
@@ -125,6 +131,24 @@ public class NettyHttpProperties {
         return socketChannel;
     }
 
+    /**
+     * get sni properties.
+     *
+     * @return sni properties
+     */
+    public SniProperties getSni() {
+        return sni;
+    }
+
+    /**
+     * set sni properties.
+     *
+     * @param sni sni properties
+     */
+    public void setSni(final SniProperties sni) {
+        this.sni = sni;
+    }
+
     /**
      * get access log state.
      *
@@ -287,4 +311,99 @@ public class NettyHttpProperties {
             this.allowHalfClosure = allowHalfClosure;
         }
     }
+
+    public static class SniProperties {
+
+        private Boolean enabled = false;
+
+        private String mod;
+
+        private String defaultK8sSecretName;
+
+        private String defaultK8sSecretNamespace;
+
+        private List<SslCrtAndKeyFile> certificates;
+
+        /**
+         * Get default kubernetes secret namespace.
+         *
+         * @return default kubernetes secret namespace
+         */
+        public String getDefaultK8sSecretNamespace() {
+            return defaultK8sSecretNamespace;
+        }
+
+        /**
+         * Set default kubernetes secret namespace.
+         *
+         * @param defaultK8sSecretNamespace default kubernetes secret namespace
+         */
+        public void setDefaultK8sSecretNamespace(final String defaultK8sSecretNamespace) {
+            this.defaultK8sSecretNamespace = defaultK8sSecretNamespace;
+        }
+
+        /**
+         * get enabled.
+         * @return enabled
+         */
+        public Boolean getEnabled() {
+            return enabled;
+        }
+
+        /**
+         * set enabled.
+         * @param enabled enabled
+         */
+        public void setEnabled(final Boolean enabled) {
+            this.enabled = enabled;
+        }
+
+        /**
+         * get mod.
+         * @return mod
+         */
+        public String getMod() {
+            return mod;
+        }
+
+        /**
+         * set mod.
+         * @param mod mod
+         */
+        public void setMod(final String mod) {
+            this.mod = mod;
+        }
+
+        /**
+         * get defaultK8sSecretName.
+         * @return defaultK8sSecretName
+         */
+        public String getDefaultK8sSecretName() {
+            return defaultK8sSecretName;
+        }
+
+        /**
+         * set defaultK8sSecretName.
+         * @param defaultK8sSecretName defaultK8sSecretName
+         */
+        public void setDefaultK8sSecretName(final String defaultK8sSecretName) {
+            this.defaultK8sSecretName = defaultK8sSecretName;
+        }
+
+        /**
+         * get certificates.
+         * @return certificates
+         */
+        public List<SslCrtAndKeyFile> getCertificates() {
+            return certificates;
+        }
+
+        /**
+         * set certificates.
+         * @param certificates certificates
+         */
+        public void setCertificates(final List<SslCrtAndKeyFile> certificates) {
+            this.certificates = certificates;
+        }
+    }
 }
diff --git a/shenyu-common/src/main/java/org/apache/shenyu/common/config/ssl/ShenyuSniAsyncMapping.java b/shenyu-common/src/main/java/org/apache/shenyu/common/config/ssl/ShenyuSniAsyncMapping.java
new file mode 100644
index 000000000..561b58d7a
--- /dev/null
+++ b/shenyu-common/src/main/java/org/apache/shenyu/common/config/ssl/ShenyuSniAsyncMapping.java
@@ -0,0 +1,126 @@
+/*
+ * 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 org.apache.shenyu.common.config.ssl;
+
+import io.netty.util.AsyncMapping;
+import io.netty.util.concurrent.Future;
+import io.netty.util.concurrent.Promise;
+import org.apache.shenyu.common.exception.ShenyuException;
+import reactor.netty.http.Http11SslContextSpec;
+import reactor.netty.tcp.SslProvider;
+import reactor.netty.tcp.TcpSslContextSpec;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.regex.Pattern;
+
+/**
+ * Sni async map, can be used to dynamically configure ssl certificates.
+ */
+public class ShenyuSniAsyncMapping implements AsyncMapping<String, SslProvider> {
+
+    private final ConcurrentHashMap<String, SslProvider> sslProviderMap;
+
+    public ShenyuSniAsyncMapping() {
+        this.sslProviderMap = new ConcurrentHashMap<>();
+    }
+
+    public ShenyuSniAsyncMapping(final List<SslCrtAndKeyFile> sslCrtAndKeys) {
+        if (sslCrtAndKeys == null || sslCrtAndKeys.isEmpty()) {
+            throw new ShenyuException("The sslCrtAndKeys can not be null");
+        }
+        this.sslProviderMap = new ConcurrentHashMap<>();
+        sslCrtAndKeys.forEach(sslCrtAndKey -> {
+            Http11SslContextSpec sslContextSpec = Http11SslContextSpec.forServer(new File(sslCrtAndKey.getKeyCertChainFile()),
+                    new File(sslCrtAndKey.getKeyFile()));
+            SslProvider sslProvider = SslProvider.builder().sslContext(sslContextSpec).build();
+            this.sslProviderMap.put(sslCrtAndKey.getDomain(), sslProvider);
+        });
+    }
+
+    /**
+     * Add SslProvider by domain.
+     *
+     * @param domain domain
+     * @param sslProvider SslProvider
+     */
+    public void addSslProvider(final String domain, final SslProvider sslProvider) {
+        sslProviderMap.put(domain, sslProvider);
+    }
+
+    /**
+     * Add ssl config.
+     *
+     * @param sslCrtAndKey sslCrtAndKey
+     * @throws IOException IOException
+     */
+    public void addSslCertificate(final SslCrtAndKey sslCrtAndKey) throws IOException {
+        if (sslCrtAndKey instanceof SslCrtAndKeyFile) {
+            SslCrtAndKeyFile sslCrtAndKeyFile = (SslCrtAndKeyFile) sslCrtAndKey;
+            TcpSslContextSpec sslContextSpec = TcpSslContextSpec.forServer(new File(sslCrtAndKeyFile.getKeyCertChainFile()),
+                    new File(sslCrtAndKeyFile.getKeyFile()));
+            SslProvider sslProvider = SslProvider.builder().sslContext(sslContextSpec).build();
+            this.sslProviderMap.put(sslCrtAndKeyFile.getDomain(), sslProvider);
+        } else if (sslCrtAndKey instanceof SslCrtAndKeyStream) {
+            SslCrtAndKeyStream sslCrtAndKeyStream = (SslCrtAndKeyStream) sslCrtAndKey;
+            sslCrtAndKeyStream.getKeyCertChainInputStream().reset();
+            sslCrtAndKeyStream.getKeyInputStream().reset();
+            TcpSslContextSpec sslContextSpec = TcpSslContextSpec.forServer(sslCrtAndKeyStream.getKeyCertChainInputStream(),
+                    sslCrtAndKeyStream.getKeyInputStream());
+            SslProvider sslProvider = SslProvider.builder().sslContext(sslContextSpec).build();
+            this.sslProviderMap.put(sslCrtAndKeyStream.getDomain(), sslProvider);
+        }
+    }
+
+    /**
+     * Remove ssl config by domain.
+     *
+     * @param domain domain
+     */
+    public void removeSslCertificate(final String domain) {
+        this.sslProviderMap.remove(domain);
+    }
+
+    /**
+     * Get SslProvider by domain.
+     *
+     * @param domain domain
+     * @param promise the promise of SslProvider
+     * @return SslProvider Future
+     */
+    @Override
+    public Future<SslProvider> map(final String domain, final Promise<SslProvider> promise) {
+        try {
+            for (String key : sslProviderMap.keySet()) {
+                if (matchDomain(domain, key)) {
+                    return promise.setSuccess(sslProviderMap.get(key));
+                }
+            }
+            return promise.setFailure(new ShenyuException(
+                    String.format("Can not find ssl certificate of domain %s", domain)));
+        } catch (Throwable cause) {
+            return promise.setFailure(cause);
+        }
+    }
+
+    private boolean matchDomain(final String domain, final String pattern) {
+        return Pattern.matches(pattern.replace(".", "\\.").replace("*", ".*"), domain);
+    }
+}
diff --git a/shenyu-common/src/main/java/org/apache/shenyu/common/config/ssl/SslCrtAndKey.java b/shenyu-common/src/main/java/org/apache/shenyu/common/config/ssl/SslCrtAndKey.java
new file mode 100644
index 000000000..87531ba4b
--- /dev/null
+++ b/shenyu-common/src/main/java/org/apache/shenyu/common/config/ssl/SslCrtAndKey.java
@@ -0,0 +1,24 @@
+/*
+ * 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 org.apache.shenyu.common.config.ssl;
+
+/**
+ * ssl certificate and key.
+ */
+public interface SslCrtAndKey {
+}
diff --git a/shenyu-common/src/main/java/org/apache/shenyu/common/config/ssl/SslCrtAndKeyFile.java b/shenyu-common/src/main/java/org/apache/shenyu/common/config/ssl/SslCrtAndKeyFile.java
new file mode 100644
index 000000000..8a39a2848
--- /dev/null
+++ b/shenyu-common/src/main/java/org/apache/shenyu/common/config/ssl/SslCrtAndKeyFile.java
@@ -0,0 +1,93 @@
+/*
+ * 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 org.apache.shenyu.common.config.ssl;
+
+/**
+ * ssl certificate and key.
+ */
+public class SslCrtAndKeyFile implements SslCrtAndKey {
+
+    private String domain;
+
+    private String keyCertChainFile;
+
+    private String keyFile;
+
+    public SslCrtAndKeyFile() {
+    }
+
+    public SslCrtAndKeyFile(final String domain, final String keyCertChainFile, final String keyFile) {
+        this.domain = domain;
+        this.keyCertChainFile = keyCertChainFile;
+        this.keyFile = keyFile;
+    }
+
+    /**
+     * set domain.
+     *
+     * @param domain domain
+     */
+    public void setDomain(final String domain) {
+        this.domain = domain;
+    }
+
+    /**
+     * set keyCertChainFile.
+     *
+     * @param keyCertChainFile keyCertChainFile
+     */
+    public void setKeyCertChainFile(final String keyCertChainFile) {
+        this.keyCertChainFile = keyCertChainFile;
+    }
+
+    /**
+     * set keyFile.
+     *
+     * @param keyFile keyFile
+     */
+    public void setKeyFile(final String keyFile) {
+        this.keyFile = keyFile;
+    }
+
+    /**
+     * get domain.
+     *
+     * @return domain
+     */
+    public String getDomain() {
+        return domain;
+    }
+
+    /**
+     * get keyCertChainFile.
+     *
+     * @return keyCertChainFile
+     */
+    public String getKeyCertChainFile() {
+        return keyCertChainFile;
+    }
+
+    /**
+     * get keyFile.
+     *
+     * @return keyFile
+     */
+    public String getKeyFile() {
+        return keyFile;
+    }
+}
diff --git a/shenyu-common/src/main/java/org/apache/shenyu/common/config/ssl/SslCrtAndKeyStream.java b/shenyu-common/src/main/java/org/apache/shenyu/common/config/ssl/SslCrtAndKeyStream.java
new file mode 100644
index 000000000..67728bc5a
--- /dev/null
+++ b/shenyu-common/src/main/java/org/apache/shenyu/common/config/ssl/SslCrtAndKeyStream.java
@@ -0,0 +1,68 @@
+/*
+ * 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 org.apache.shenyu.common.config.ssl;
+
+import java.io.InputStream;
+
+/**
+ * ssl certificate and key.
+ */
+public class SslCrtAndKeyStream implements SslCrtAndKey {
+
+    private String domain;
+
+    private InputStream keyCertChainInputStream;
+
+    private InputStream keyInputStream;
+
+    public SslCrtAndKeyStream() {
+    }
+
+    public SslCrtAndKeyStream(final String domain, final InputStream keyCertChainInputStream, final InputStream keyInputStream) {
+        this.domain = domain;
+        this.keyCertChainInputStream = keyCertChainInputStream;
+        this.keyInputStream = keyInputStream;
+    }
+
+    /**
+     * get domain.
+     *
+     * @return domain
+     */
+    public String getDomain() {
+        return domain;
+    }
+
+    /**
+     * get keyCertChainInputStream.
+     *
+     * @return keyCertChainInputStream
+     */
+    public InputStream getKeyCertChainInputStream() {
+        return keyCertChainInputStream;
+    }
+
+    /**
+     * get keyInputStream.
+     *
+     * @return keyInputStream
+     */
+    public InputStream getKeyInputStream() {
+        return keyInputStream;
+    }
+}
diff --git a/shenyu-spring-boot-starter/pom.xml b/shenyu-kubernetes-controller/pom.xml
similarity index 60%
copy from shenyu-spring-boot-starter/pom.xml
copy to shenyu-kubernetes-controller/pom.xml
index 071894ae9..fcf0ce898 100644
--- a/shenyu-spring-boot-starter/pom.xml
+++ b/shenyu-kubernetes-controller/pom.xml
@@ -16,31 +16,32 @@
   ~ limitations under the License.
   -->
 
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
     <parent>
-        <groupId>org.apache.shenyu</groupId>
         <artifactId>shenyu</artifactId>
+        <groupId>org.apache.shenyu</groupId>
         <version>2.6.0-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
-    <artifactId>shenyu-spring-boot-starter</artifactId>
-    <packaging>pom</packaging>
-
-    <modules>
-        <module>shenyu-spring-boot-starter-gateway</module>
-        <module>shenyu-spring-boot-starter-plugin</module>
-        <module>shenyu-spring-boot-starter-sync-data-center</module>
-        <module>shenyu-spring-boot-starter-client</module>
-        <module>shenyu-spring-boot-starter-instance</module>
-        <module>shenyu-spring-boot-starter-sdk</module>
-    </modules>
+    <artifactId>shenyu-kubernetes-controller</artifactId>
 
     <dependencies>
+        <dependency>
+            <groupId>org.apache.shenyu</groupId>
+            <artifactId>shenyu-plugin-base</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
         <dependency>
             <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-configuration-processor</artifactId>
-            <optional>true</optional>
+            <artifactId>spring-boot-starter-actuator</artifactId>
         </dependency>
-    </dependencies>
 
+        <dependency>
+            <groupId>io.kubernetes</groupId>
+            <artifactId>client-java-spring-integration</artifactId>
+        </dependency>
+    </dependencies>
 </project>
diff --git a/shenyu-kubernetes-controller/src/main/java/org/apache/shenyu/k8s/cache/IngressCache.java b/shenyu-kubernetes-controller/src/main/java/org/apache/shenyu/k8s/cache/IngressCache.java
new file mode 100644
index 000000000..f16b2c268
--- /dev/null
+++ b/shenyu-kubernetes-controller/src/main/java/org/apache/shenyu/k8s/cache/IngressCache.java
@@ -0,0 +1,85 @@
+/*
+ * 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 org.apache.shenyu.k8s.cache;
+
+import com.google.common.collect.Maps;
+import io.kubernetes.client.openapi.models.V1Ingress;
+
+import java.util.Map;
+
+/**
+ * The cache for V1Ingress.
+ */
+public final class IngressCache implements K8sResourceCache<V1Ingress> {
+
+    private static final IngressCache INSTANCE = new IngressCache();
+
+    private static final Map<String, V1Ingress> INGRESS_MAP = Maps.newConcurrentMap();
+
+    private IngressCache() {
+    }
+
+    /**
+     * Get singleton of IngressCache.
+     *
+     * @return IngressCache
+     */
+    public static IngressCache getInstance() {
+        return INSTANCE;
+    }
+
+    /**
+     * Put ingress.
+     *
+     * @param namespace namespace
+     * @param name name
+     * @param resource resource
+     */
+    @Override
+    public void put(final String namespace, final String name, final V1Ingress resource) {
+        INGRESS_MAP.put(getKey(namespace, name), resource);
+    }
+
+    /**
+     * Get ingress.
+     *
+     * @param namespace namespace
+     * @param name name
+     * @return V1Ingress
+     */
+    @Override
+    public V1Ingress get(final String namespace, final String name) {
+        return INGRESS_MAP.get(getKey(namespace, name));
+    }
+
+    /**
+     * Remove ingress.
+     *
+     * @param namespace namespace
+     * @param name name
+     * @return V1Ingress
+     */
+    @Override
+    public V1Ingress remove(final String namespace, final String name) {
+        return INGRESS_MAP.remove(getKey(namespace, name));
+    }
+
+    private String getKey(final String namespace, final String name) {
+        return String.format("%s-%s", namespace, name);
+    }
+}
diff --git a/shenyu-kubernetes-controller/src/main/java/org/apache/shenyu/k8s/cache/IngressSecretCache.java b/shenyu-kubernetes-controller/src/main/java/org/apache/shenyu/k8s/cache/IngressSecretCache.java
new file mode 100644
index 000000000..274c5d52d
--- /dev/null
+++ b/shenyu-kubernetes-controller/src/main/java/org/apache/shenyu/k8s/cache/IngressSecretCache.java
@@ -0,0 +1,124 @@
+/*
+ * 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 org.apache.shenyu.k8s.cache;
+
+import com.google.common.collect.Maps;
+import org.apache.shenyu.common.exception.ShenyuException;
+
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * The cache for tls config.
+ */
+public final class IngressSecretCache {
+
+    private static final IngressSecretCache INSTANCE = new IngressSecretCache();
+
+    private static final Map<String, Set<String>> INGRESS_DOMAIN_MAP = Maps.newConcurrentMap();
+
+    private static final Map<String, AtomicInteger> DOMAIN_NUMS_MAP = Maps.newConcurrentMap();
+
+    private IngressSecretCache() {
+    }
+
+    /**
+     * Get singleton of IngressSecretCache.
+     *
+     * @return IngressSecretCache
+     */
+    public static IngressSecretCache getInstance() {
+        return INSTANCE;
+    }
+
+    /**
+     * Put tls domain by ingress.
+     *
+     * @param namespace namespace
+     * @param ingressName name
+     * @param domain tls domain
+     */
+    public void putDomainByIngress(final String namespace, final String ingressName, final String domain) {
+        Set<String> set = INGRESS_DOMAIN_MAP.computeIfAbsent(getKey(namespace, ingressName), k -> new HashSet<>());
+        set.add(domain);
+    }
+
+    /**
+     * Put tls domain set by ingress.
+     *
+     * @param namespace namespace
+     * @param ingressName name
+     * @param domainSet domain set
+     */
+    public void putDomainByIngress(final String namespace, final String ingressName, final Set<String> domainSet) {
+        INGRESS_DOMAIN_MAP.put(getKey(namespace, ingressName), domainSet);
+    }
+
+    /**
+     * Get domain set by ingress.
+     *
+     * @param namespace namespace
+     * @param ingressName ingressName
+     * @return domain set
+     */
+    public Set<String> getDomainByIngress(final String namespace, final String ingressName) {
+        return INGRESS_DOMAIN_MAP.get(getKey(namespace, ingressName));
+    }
+
+    /**
+     * Remove domain set by ingress.
+     *
+     * @param namespace namespace
+     * @param ingressName ingressName
+     * @return domain set
+     */
+    public Set<String> removeDomainByIngress(final String namespace, final String ingressName) {
+        return INGRESS_DOMAIN_MAP.remove(getKey(namespace, ingressName));
+    }
+
+    /**
+     * Get and increment the number of the ingress that enables the domain name to take effect.
+     *
+     * @param domain tls domain
+     * @return the previous number of the ingress that enables the domain name to take effect
+     */
+    public Integer getAndIncrementDomainNums(final String domain) {
+        AtomicInteger count = DOMAIN_NUMS_MAP.computeIfAbsent(domain, k -> new AtomicInteger(0));
+        return count.getAndIncrement();
+    }
+
+    /**
+     * Get and decrement the number of the ingress that enables the domain name to take effect.
+     *
+     * @param domain tls domain
+     * @return the previous number of the ingress that enables the domain name to take effect
+     */
+    public Integer getAndDecrementDomainNums(final String domain) {
+        AtomicInteger count = DOMAIN_NUMS_MAP.computeIfAbsent(domain, k -> new AtomicInteger(0));
+        if (count.intValue() > 0) {
+            return count.getAndDecrement();
+        }
+        throw new ShenyuException("Decrement when domain ssl counts <= 0, an unknown exception has occurred.");
+    }
+
+    private String getKey(final String namespace, final String name) {
+        return namespace + "-" + name;
+    }
+}
diff --git a/shenyu-kubernetes-controller/src/main/java/org/apache/shenyu/k8s/cache/IngressSelectorCache.java b/shenyu-kubernetes-controller/src/main/java/org/apache/shenyu/k8s/cache/IngressSelectorCache.java
new file mode 100644
index 000000000..2d61876bc
--- /dev/null
+++ b/shenyu-kubernetes-controller/src/main/java/org/apache/shenyu/k8s/cache/IngressSelectorCache.java
@@ -0,0 +1,123 @@
+/*
+ * 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 org.apache.shenyu.k8s.cache;
+
+import com.google.common.collect.Maps;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * The cache mapping Ingress to Selector id list.
+ */
+public final class IngressSelectorCache implements SelectorCache {
+
+    private static final IngressSelectorCache INSTANCE = new IngressSelectorCache();
+
+    private static final Map<String, List<String>> SELECTOR_MAP = Maps.newConcurrentMap();
+
+    private static final AtomicLong GROWING_ID = new AtomicLong(10);
+
+    private IngressSelectorCache() {
+    }
+
+    /**
+     * Get singleton of IngressSelectorCache.
+     *
+     * @return IngressSelectorCache
+     */
+    public static IngressSelectorCache getInstance() {
+        return INSTANCE;
+    }
+
+    /**
+     * Put selector id list by ingress.
+     *
+     * @param namespace ingress namespace
+     * @param name ingress name
+     * @param pluginName plugin name
+     * @param selectorIdList selector id list
+     * @return the previous selector id list
+     */
+    @Override
+    public List<String> put(final String namespace,
+                            final String name,
+                            final String pluginName,
+                            final List<String> selectorIdList) {
+        return SELECTOR_MAP.put(getKey(namespace, name, pluginName), selectorIdList);
+    }
+
+    /**
+     * Put selector id by ingress.
+     *
+     * @param namespace namespace
+     * @param name name
+     * @param pluginName plugin name
+     * @param selectorId selector id
+     * @return the previous selector id list
+     */
+    public List<String> put(final String namespace,
+                            final String name,
+                            final String pluginName,
+                            final String selectorId) {
+        List<String> selectorIdList = SELECTOR_MAP.computeIfAbsent(getKey(namespace, name, pluginName), k -> new ArrayList<>());
+        selectorIdList.add(selectorId);
+        return SELECTOR_MAP.put(getKey(namespace, name, pluginName), selectorIdList);
+    }
+
+    /**
+     * Get Selector id list by ingress.
+     *
+     * @param namespace ingress namespace
+     * @param name ingress name
+     * @param pluginName plugin name
+     * @return selector id list
+     */
+    @Override
+    public List<String> get(final String namespace, final String name, final String pluginName) {
+        return SELECTOR_MAP.get(getKey(namespace, name, pluginName));
+    }
+
+    /**
+     * Remove Selector id list by ingress.
+     *
+     * @param namespace ingress namespace
+     * @param name ingress name
+     * @param pluginName plugin name
+     * @return selector id list
+     */
+    @Override
+    public List<String> remove(final String namespace, final String name, final String pluginName) {
+        return SELECTOR_MAP.remove(getKey(namespace, name, pluginName));
+    }
+
+    /**
+     * Get auto-incremented selector id.
+     *
+     * @return selector id
+     */
+    public String generateSelectorId() {
+        return String.valueOf(GROWING_ID.getAndIncrement());
+    }
+
+    private String getKey(final String namespace, final String name, final String pluginName) {
+        return String.format("%s-%s-%s", namespace, name, pluginName);
+    }
+}
diff --git a/shenyu-kubernetes-controller/src/main/java/org/apache/shenyu/k8s/cache/K8sResourceCache.java b/shenyu-kubernetes-controller/src/main/java/org/apache/shenyu/k8s/cache/K8sResourceCache.java
new file mode 100644
index 000000000..aa0c46998
--- /dev/null
+++ b/shenyu-kubernetes-controller/src/main/java/org/apache/shenyu/k8s/cache/K8sResourceCache.java
@@ -0,0 +1,53 @@
+/*
+ * 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 org.apache.shenyu.k8s.cache;
+
+/**
+ * The cache for Kubernetes resource.
+ *
+ * @param <T> resource type
+ */
+public interface K8sResourceCache<T> {
+
+    /**
+     * Put Kubernetes resource.
+     *
+     * @param namespace namespace
+     * @param name name
+     * @param resource resource
+     */
+    void put(String namespace, String name, T resource);
+
+    /**
+     * Get Kubernetes resource.
+     *
+     * @param namespace namespace
+     * @param name name
+     * @return resource
+     */
+    T get(String namespace, String name);
+
+    /**
+     * Remove Kubernetes resource.
+     *
+     * @param namespace namespace
+     * @param name name
+     * @return the resource removed
+     */
+    T remove(String namespace, String name);
+}
diff --git a/shenyu-kubernetes-controller/src/main/java/org/apache/shenyu/k8s/cache/SelectorCache.java b/shenyu-kubernetes-controller/src/main/java/org/apache/shenyu/k8s/cache/SelectorCache.java
new file mode 100644
index 000000000..513a3bb45
--- /dev/null
+++ b/shenyu-kubernetes-controller/src/main/java/org/apache/shenyu/k8s/cache/SelectorCache.java
@@ -0,0 +1,57 @@
+/*
+ * 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 org.apache.shenyu.k8s.cache;
+
+import java.util.List;
+
+/**
+ * The cache for Selector id.
+ */
+public interface SelectorCache {
+
+    /**
+     * Put Selector id list by resource namespace, name and plugin name.
+     *
+     * @param namespace resource namespace
+     * @param name resource name
+     * @param pluginName plugin name
+     * @param selectorIdList selector id list
+     * @return The previous value associated with key, or null if there was no mapping for key
+     */
+    List<String> put(String namespace, String name, String pluginName, List<String> selectorIdList);
+
+    /**
+     * Get Selector id list by resource namespace, name and plugin name.
+     *
+     * @param namespace resource namespace
+     * @param name resource name
+     * @param pluginName plugin name
+     * @return Selector id list
+     */
+    List<String> get(String namespace, String name, String pluginName);
+
+    /**
+     * Remove Selector id list by resource namespace, name and plugin name.
+     *
+     * @param namespace resource namespace
+     * @param name resource name
+     * @param pluginName plugin name
+     * @return Selector id list
+     */
+    List<String> remove(String namespace, String name, String pluginName);
+}
diff --git a/shenyu-kubernetes-controller/src/main/java/org/apache/shenyu/k8s/cache/ServiceIngressCache.java b/shenyu-kubernetes-controller/src/main/java/org/apache/shenyu/k8s/cache/ServiceIngressCache.java
new file mode 100644
index 000000000..4531e713f
--- /dev/null
+++ b/shenyu-kubernetes-controller/src/main/java/org/apache/shenyu/k8s/cache/ServiceIngressCache.java
@@ -0,0 +1,101 @@
+/*
+ * 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 org.apache.shenyu.k8s.cache;
+
+import com.google.common.collect.Maps;
+import org.apache.commons.lang3.tuple.Pair;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * The cache for mapping service name to ingress name.
+ */
+public final class ServiceIngressCache {
+
+    private static final ServiceIngressCache INSTANCE = new ServiceIngressCache();
+
+    private static final Map<String, List<Pair<String, String>>> INGRESS_MAP = Maps.newConcurrentMap();
+
+    private ServiceIngressCache() {
+    }
+
+    /**
+     * Get singleton of ServiceIngressCache.
+     *
+     * @return ServiceIngressCache
+     */
+    public static ServiceIngressCache getInstance() {
+        return INSTANCE;
+    }
+
+    /**
+     * Get ingress namespace and name by service namespace and namespace.
+     *
+     * @param namespace namespace
+     * @param serviceName service name
+     * @return ingress namespace and name
+     */
+    public List<Pair<String, String>> getIngressName(final String namespace, final String serviceName) {
+        return INGRESS_MAP.get(getKey(namespace, serviceName));
+    }
+
+    /**
+     * Put ingress by service namespace and name.
+     *
+     * @param namespace service namespace
+     * @param serviceName service name
+     * @param ingressNamespace ingress namespace
+     * @param ingressName ingress name
+     */
+    public void putIngressName(final String namespace, final String serviceName, final String ingressNamespace, final String ingressName) {
+        List<Pair<String, String>> list = INGRESS_MAP.computeIfAbsent(getKey(namespace, serviceName), k -> new ArrayList<>());
+        list.add(Pair.of(ingressNamespace, ingressName));
+    }
+
+    /**
+     * Remove all ingress by service namespace and name.
+     *
+     * @param namespace service namespace
+     * @param serviceName service name
+     * @return the ingress list removed
+     */
+    public List<Pair<String, String>> removeAllIngressName(final String namespace, final String serviceName) {
+        return INGRESS_MAP.remove(getKey(namespace, serviceName));
+    }
+
+    /**
+     * Remove specified ingress by service and ingress.
+     *
+     * @param namespace service namespace
+     * @param serviceName service name
+     * @param ingressNamespace ingress namespace
+     * @param ingressName ingress name
+     */
+    public void removeSpecifiedIngressName(final String namespace, final String serviceName, final String ingressNamespace, final String ingressName) {
+        List<Pair<String, String>> list = INGRESS_MAP.get(getKey(namespace, serviceName));
+        if (list != null) {
+            list.removeIf(item -> item.getLeft().equals(ingressNamespace) && item.getRight().equals(ingressName));
+        }
+    }
+
+    private String getKey(final String namespace, final String name) {
+        return String.format("%s-%s", namespace, name);
+    }
+}
diff --git a/shenyu-kubernetes-controller/src/main/java/org/apache/shenyu/k8s/common/IngressConstants.java b/shenyu-kubernetes-controller/src/main/java/org/apache/shenyu/k8s/common/IngressConstants.java
new file mode 100644
index 000000000..e3383c171
--- /dev/null
+++ b/shenyu-kubernetes-controller/src/main/java/org/apache/shenyu/k8s/common/IngressConstants.java
@@ -0,0 +1,40 @@
+/*
+ * 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 org.apache.shenyu.k8s.common;
+
+public class IngressConstants {
+
+    public static final String K8S_INGRESS_CLASS_ANNOTATION_KEY = "kubernetes.io/ingress.class";
+
+    public static final String SHENYU_INGRESS_CLASS = "shenyu";
+
+    // Load balance type name, refer to LoadBalanceEnum
+    public static final String LOADBALANCER_ANNOTATION_KEY = "shenyu.apache.org/loadbalancer";
+
+    // number of retries
+    public static final String RETRY_ANNOTATION_KEY = "shenyu.apache.org/retry";
+
+    // timeout, in milliseconds
+    public static final String TIMEOUT_ANNOTATION_KEY = "shenyu.apache.org/timeout";
+
+    // The maximum length of the request body, in bytes
+    public static final String HEADER_MAX_SIZE_ANNOTATION_KEY = "shenyu.apache.org/header-max-size";
+
+    // The maximum length of the request header, in bytes
+    public static final String REQUEST_MAX_SIZE_ANNOTATION_KEY = "shenyu.apache.org/request-max-size";
+}
diff --git a/shenyu-kubernetes-controller/src/main/java/org/apache/shenyu/k8s/common/ShenyuMemoryConfig.java b/shenyu-kubernetes-controller/src/main/java/org/apache/shenyu/k8s/common/ShenyuMemoryConfig.java
new file mode 100644
index 000000000..702000413
--- /dev/null
+++ b/shenyu-kubernetes-controller/src/main/java/org/apache/shenyu/k8s/common/ShenyuMemoryConfig.java
@@ -0,0 +1,97 @@
+/*
+ * 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 org.apache.shenyu.k8s.common;
+
+import org.apache.commons.lang3.tuple.Pair;
+import org.apache.shenyu.common.config.ssl.SslCrtAndKeyStream;
+import org.apache.shenyu.common.dto.RuleData;
+import org.apache.shenyu.common.dto.SelectorData;
+
+import java.util.List;
+
+/**
+ * The configuration that shenyu can read directly.
+ */
+public class ShenyuMemoryConfig {
+
+    private Pair<Pair<String, String>, Pair<SelectorData, RuleData>> globalDefaultBackend;
+
+    private List<Pair<SelectorData, RuleData>> routeConfigList;
+
+    private List<SslCrtAndKeyStream> tlsConfigList;
+
+    /**
+     * ShenyuMemoryConfig Constructor.
+     */
+    public ShenyuMemoryConfig() {
+    }
+
+    /**
+     * Get GlobalDefaultBackend.
+     *
+     * @return GlobalDefaultBackend
+     */
+    public Pair<Pair<String, String>, Pair<SelectorData, RuleData>> getGlobalDefaultBackend() {
+        return globalDefaultBackend;
+    }
+
+    /**
+     * Set GlobalDefaultBackend.
+     *
+     * @param globalDefaultBackend GlobalDefaultBackend
+     */
+    public void setGlobalDefaultBackend(final Pair<Pair<String, String>, Pair<SelectorData, RuleData>> globalDefaultBackend) {
+        this.globalDefaultBackend = globalDefaultBackend;
+    }
+
+    /**
+     * Get RouteConfigList.
+     *
+     * @return RouteConfigList
+     */
+    public List<Pair<SelectorData, RuleData>> getRouteConfigList() {
+        return routeConfigList;
+    }
+
+    /**
+     * Set RouteConfigList.
+     *
+     * @param routeConfigList RouteConfigList
+     */
+    public void setRouteConfigList(final List<Pair<SelectorData, RuleData>> routeConfigList) {
+        this.routeConfigList = routeConfigList;
+    }
+
+    /**
+     * Get TlsConfigList.
+     *
+     * @return TlsConfigList
+     */
+    public List<SslCrtAndKeyStream> getTlsConfigList() {
+        return tlsConfigList;
+    }
+
+    /**
+     * Set TlsConfigList.
+     *
+     * @param tlsConfigList TlsConfigList
+     */
+    public void setTlsConfigList(final List<SslCrtAndKeyStream> tlsConfigList) {
+        this.tlsConfigList = tlsConfigList;
+    }
+}
diff --git a/shenyu-kubernetes-controller/src/main/java/org/apache/shenyu/k8s/parser/IngressParser.java b/shenyu-kubernetes-controller/src/main/java/org/apache/shenyu/k8s/parser/IngressParser.java
new file mode 100644
index 000000000..2483c7bc4
--- /dev/null
+++ b/shenyu-kubernetes-controller/src/main/java/org/apache/shenyu/k8s/parser/IngressParser.java
@@ -0,0 +1,356 @@
+/*
+ * 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 org.apache.shenyu.k8s.parser;
+
+import io.kubernetes.client.informer.SharedIndexInformer;
+import io.kubernetes.client.informer.cache.Lister;
+import io.kubernetes.client.openapi.ApiException;
+import io.kubernetes.client.openapi.apis.CoreV1Api;
+import io.kubernetes.client.openapi.models.V1Endpoints;
+import io.kubernetes.client.openapi.models.V1EndpointSubset;
+import io.kubernetes.client.openapi.models.V1EndpointAddress;
+import io.kubernetes.client.openapi.models.V1Ingress;
+import io.kubernetes.client.openapi.models.V1IngressBackend;
+import io.kubernetes.client.openapi.models.V1IngressRule;
+import io.kubernetes.client.openapi.models.V1IngressTLS;
+import io.kubernetes.client.openapi.models.V1HTTPIngressPath;
+import io.kubernetes.client.openapi.models.V1Service;
+import io.kubernetes.client.openapi.models.V1IngressServiceBackend;
+import io.kubernetes.client.openapi.models.V1Secret;
+import org.apache.commons.lang3.tuple.Pair;
+import org.apache.shenyu.common.config.ssl.SslCrtAndKeyStream;
+import org.apache.shenyu.common.dto.ConditionData;
+import org.apache.shenyu.common.dto.RuleData;
+import org.apache.shenyu.common.dto.SelectorData;
+import org.apache.shenyu.common.dto.convert.rule.impl.DivideRuleHandle;
+import org.apache.shenyu.common.dto.convert.selector.DivideUpstream;
+import org.apache.shenyu.common.enums.MatchModeEnum;
+import org.apache.shenyu.common.enums.LoadBalanceEnum;
+import org.apache.shenyu.common.enums.OperatorEnum;
+import org.apache.shenyu.common.enums.ParamTypeEnum;
+import org.apache.shenyu.common.enums.PluginEnum;
+import org.apache.shenyu.common.enums.SelectorTypeEnum;
+import org.apache.shenyu.common.utils.GsonUtils;
+import org.apache.shenyu.k8s.common.IngressConstants;
+import org.apache.shenyu.k8s.common.ShenyuMemoryConfig;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.Map;
+
+/**
+ * Parser of Ingress.
+ */
+public class IngressParser implements K8sResourceParser<V1Ingress> {
+
+    private static final Logger LOG = LoggerFactory.getLogger(IngressParser.class);
+
+    private final Lister<V1Service> serviceLister;
+
+    private final Lister<V1Endpoints> endpointsLister;
+
+    /**
+     * IngressParser Constructor.
+     *
+     * @param serviceInformer serviceInformer
+     * @param endpointsInformer endpointsInformer
+     */
+    public IngressParser(final SharedIndexInformer<V1Service> serviceInformer, final SharedIndexInformer<V1Endpoints> endpointsInformer) {
+        this.serviceLister = new Lister<>(serviceInformer.getIndexer());
+        this.endpointsLister = new Lister<>(endpointsInformer.getIndexer());
+    }
+
+    /**
+     * Parse ingress to ShenyuMemoryConfig.
+     *
+     * @param ingress ingress resource
+     * @param coreV1Api coreV1Api
+     * @return ShenyuMemoryConfig
+     */
+    @Override
+    public ShenyuMemoryConfig parse(final V1Ingress ingress, final CoreV1Api coreV1Api) {
+        ShenyuMemoryConfig res = new ShenyuMemoryConfig();
+
+        if (ingress.getSpec() != null) {
+            // Parse the default backend
+            V1IngressBackend defaultBackend = ingress.getSpec().getDefaultBackend();
+            List<V1IngressRule> rules = ingress.getSpec().getRules();
+            List<V1IngressTLS> tlsList = ingress.getSpec().getTls();
+
+            String namespace = Objects.requireNonNull(ingress.getMetadata()).getNamespace();
+            List<DivideUpstream> defaultUpstreamList = parseDefaultService(defaultBackend, namespace);
+
+            if (rules == null || rules.isEmpty()) {
+                // if rules is null, defaultBackend become global default
+                if (defaultBackend != null && defaultBackend.getService() != null) {
+                    Pair<SelectorData, RuleData> defaultRouteConfig = getDefaultRouteConfig(defaultUpstreamList, ingress.getMetadata().getAnnotations());
+                    res.setGlobalDefaultBackend(Pair.of(Pair.of(namespace + "/" + ingress.getMetadata().getName(), defaultBackend.getService().getName()),
+                            defaultRouteConfig));
+                }
+            } else {
+                // if rules is not null, defaultBackend is default in this ingress
+                List<Pair<SelectorData, RuleData>> routeList = new ArrayList<>(rules.size());
+                for (V1IngressRule ingressRule : rules) {
+                    List<Pair<SelectorData, RuleData>> routes = parseIngressRule(ingressRule, defaultUpstreamList,
+                            Objects.requireNonNull(ingress.getMetadata()).getNamespace(), ingress.getMetadata().getAnnotations());
+                    routeList.addAll(routes);
+                }
+                res.setRouteConfigList(routeList);
+            }
+
+            // Parse tls
+            if (tlsList != null && !tlsList.isEmpty()) {
+                List<SslCrtAndKeyStream> sslList = new ArrayList<>();
+                for (V1IngressTLS tls : tlsList) {
+                    if (tls.getSecretName() != null && tls.getHosts() != null && !tls.getHosts().isEmpty()) {
+                        try {
+                            V1Secret secret = coreV1Api.readNamespacedSecret(tls.getSecretName(), namespace, "ture");
+                            if (secret.getData() != null) {
+                                InputStream keyCertChainInputStream = new ByteArrayInputStream(secret.getData().get("tls.crt"));
+                                InputStream keyInputStream = new ByteArrayInputStream(secret.getData().get("tls.key"));
+                                tls.getHosts().forEach(host ->
+                                    sslList.add(new SslCrtAndKeyStream(host, keyCertChainInputStream, keyInputStream))
+                                );
+                            }
+                        } catch (ApiException e) {
+                            LOG.error("parse tls failed ", e);
+                        }
+                    }
+                }
+                res.setTlsConfigList(sslList);
+            }
+        }
+        return res;
+    }
+
+    private List<DivideUpstream> parseDefaultService(final V1IngressBackend defaultBackend, final String namespace) {
+        List<DivideUpstream> defaultUpstreamList = new ArrayList<>();
+        if (defaultBackend != null && defaultBackend.getService() != null) {
+            String serviceName = defaultBackend.getService().getName();
+            // shenyu routes directly to the container
+            V1Endpoints v1Endpoints = endpointsLister.namespace(namespace).get(serviceName);
+            List<V1EndpointSubset> subsets = v1Endpoints.getSubsets();
+            if (subsets == null || subsets.isEmpty()) {
+                LOG.info("Endpoints {} do not have subsets", serviceName);
+            } else {
+                for (V1EndpointSubset subset : subsets) {
+                    List<V1EndpointAddress> addresses = subset.getAddresses();
+                    if (addresses == null || addresses.isEmpty()) {
+                        continue;
+                    }
+                    for (V1EndpointAddress address : addresses) {
+                        String upstreamIp = address.getIp();
+                        String defaultPort = parsePort(defaultBackend.getService());
+                        if (defaultPort != null) {
+                            DivideUpstream upstream = new DivideUpstream();
+                            upstream.setUpstreamUrl(upstreamIp + ":" + defaultPort);
+                            upstream.setWeight(100);
+                            // TODO support config protocol in annotation
+                            upstream.setProtocol("http://");
+                            upstream.setWarmup(0);
+                            upstream.setStatus(true);
+                            upstream.setUpstreamHost("");
+                            defaultUpstreamList.add(upstream);
+                        }
+                    }
+                }
+            }
+        }
+        return defaultUpstreamList;
+    }
+
+    private List<Pair<SelectorData, RuleData>> parseIngressRule(final V1IngressRule ingressRule,
+                                                                final List<DivideUpstream> defaultUpstream,
+                                                                final String namespace,
+                                                                final Map<String, String> annotations) {
+        List<Pair<SelectorData, RuleData>> res = new ArrayList<>();
+
+        ConditionData hostCondition = null;
+        if (ingressRule.getHost() != null) {
+            hostCondition = new ConditionData();
+            hostCondition.setParamType(ParamTypeEnum.DOMAIN.getName());
+            hostCondition.setOperator(OperatorEnum.EQ.getAlias());
+            hostCondition.setParamValue(ingressRule.getHost());
+        }
+        if (ingressRule.getHttp() != null) {
+            List<V1HTTPIngressPath> paths = ingressRule.getHttp().getPaths();
+            if (paths != null) {
+                for (V1HTTPIngressPath path : paths) {
+                    if (path.getPath() == null) {
+                        continue;
+                    }
+
+                    OperatorEnum operator;
+                    if ("ImplementationSpecific".equals(path.getPathType())) {
+                        operator = OperatorEnum.MATCH;
+                    } else if ("Prefix".equals(path.getPathType())) {
+                        operator = OperatorEnum.STARTS_WITH;
+                    } else if ("Exact".equals(path.getPathType())) {
+                        operator = OperatorEnum.EQ;
+                    } else {
+                        LOG.info("Invalid path type, set it with match operator");
+                        operator = OperatorEnum.MATCH;
+                    }
+
+                    ConditionData pathCondition = new ConditionData();
+                    pathCondition.setOperator(operator.getAlias());
+                    pathCondition.setParamType(ParamTypeEnum.URI.getName());
+                    pathCondition.setParamValue(path.getPath());
+                    List<ConditionData> conditionList = new ArrayList<>(2);
+                    if (hostCondition != null) {
+                        conditionList.add(hostCondition);
+                    }
+                    conditionList.add(pathCondition);
+
+                    SelectorData selectorData = SelectorData.builder()
+                            .pluginId(String.valueOf(PluginEnum.DIVIDE.getCode()))
+                            .pluginName(PluginEnum.DIVIDE.getName())
+                            .name(path.getPath())
+                            .matchMode(MatchModeEnum.AND.getCode())
+                            .type(SelectorTypeEnum.CUSTOM_FLOW.getCode())
+                            .enabled(true)
+                            .logged(false)
+                            .continued(true)
+                            .conditionList(conditionList).build();
+                    List<DivideUpstream> upstreamList = parseUpstream(path.getBackend(), namespace);
+                    if (upstreamList.isEmpty()) {
+                        upstreamList = defaultUpstream;
+                    }
+                    selectorData.setHandle(GsonUtils.getInstance().toJson(upstreamList));
+
+                    DivideRuleHandle divideRuleHandle = new DivideRuleHandle();
+                    if (annotations != null) {
+                        divideRuleHandle.setLoadBalance(annotations.getOrDefault(IngressConstants.LOADBALANCER_ANNOTATION_KEY, LoadBalanceEnum.RANDOM.getName()));
+                        divideRuleHandle.setRetry(Integer.parseInt(annotations.getOrDefault(IngressConstants.RETRY_ANNOTATION_KEY, "3")));
+                        divideRuleHandle.setTimeout(Long.parseLong(annotations.getOrDefault(IngressConstants.TIMEOUT_ANNOTATION_KEY, "3000")));
+                        divideRuleHandle.setHeaderMaxSize(Long.parseLong(annotations.getOrDefault(IngressConstants.HEADER_MAX_SIZE_ANNOTATION_KEY, "10240")));
+                        divideRuleHandle.setRequestMaxSize(Long.parseLong(annotations.getOrDefault(IngressConstants.REQUEST_MAX_SIZE_ANNOTATION_KEY, "102400")));
+                    }
+                    RuleData ruleData = RuleData.builder()
+                            .name(path.getPath())
+                            .pluginName(PluginEnum.DIVIDE.getName())
+                            .matchMode(MatchModeEnum.AND.getCode())
+                            .conditionDataList(conditionList)
+                            .handle(GsonUtils.getInstance().toJson(divideRuleHandle))
+                            .loged(false)
+                            .enabled(true).build();
+
+                    res.add(Pair.of(selectorData, ruleData));
+                }
+            }
+        }
+        return res;
+    }
+
+    private String parsePort(final V1IngressServiceBackend service) {
+        if (service.getPort() != null) {
+            if (service.getPort().getNumber() != null && service.getPort().getNumber() > 0) {
+                return String.valueOf(service.getPort().getNumber());
+            } else if (service.getPort().getName() != null && !"".equals(service.getPort().getName().trim())) {
+                return service.getPort().getName().trim();
+            }
+        }
+        return null;
+    }
+
+    private List<DivideUpstream> parseUpstream(final V1IngressBackend backend, final String namespace) {
+        List<DivideUpstream> upstreamList = new ArrayList<>();
+        if (backend != null && backend.getService() != null && backend.getService().getName() != null) {
+            String serviceName = backend.getService().getName();
+            // shenyu routes directly to the container
+            V1Endpoints v1Endpoints = endpointsLister.namespace(namespace).get(serviceName);
+            List<V1EndpointSubset> subsets = v1Endpoints.getSubsets();
+            if (subsets == null || subsets.isEmpty()) {
+                LOG.info("Endpoints {} do not have subsets", serviceName);
+            } else {
+                for (V1EndpointSubset subset : subsets) {
+                    List<V1EndpointAddress> addresses = subset.getAddresses();
+                    if (addresses == null || addresses.isEmpty()) {
+                        continue;
+                    }
+                    for (V1EndpointAddress address : addresses) {
+                        String upstreamIp = address.getIp();
+                        String defaultPort = parsePort(backend.getService());
+                        if (defaultPort != null) {
+                            DivideUpstream upstream = new DivideUpstream();
+                            upstream.setUpstreamUrl(upstreamIp + ":" + defaultPort);
+                            upstream.setWeight(100);
+                            // TODO support config protocol in annotation
+                            upstream.setProtocol("http://");
+                            upstream.setWarmup(0);
+                            upstream.setStatus(true);
+                            upstream.setUpstreamHost("");
+                            upstreamList.add(upstream);
+                        }
+                    }
+                }
+            }
+        }
+        return upstreamList;
+    }
+
+    private Pair<SelectorData, RuleData> getDefaultRouteConfig(final List<DivideUpstream> divideUpstream, final Map<String, String> annotations) {
+        final ConditionData conditionData = new ConditionData();
+        conditionData.setParamName("default");
+        conditionData.setParamType(ParamTypeEnum.URI.getName());
+        conditionData.setOperator(OperatorEnum.PATH_PATTERN.getAlias());
+        conditionData.setParamValue("/**");
+
+        final SelectorData selectorData = SelectorData.builder()
+                .name("default-selector")
+                .sort(Integer.MAX_VALUE)
+                .conditionList(Collections.singletonList(conditionData))
+                .handle(GsonUtils.getInstance().toJson(divideUpstream))
+                .enabled(true)
+                .id("1")
+                .pluginName(PluginEnum.DIVIDE.getName())
+                .pluginId(String.valueOf(PluginEnum.DIVIDE.getCode()))
+                .logged(false)
+                .continued(true)
+                .matchMode(MatchModeEnum.AND.getCode())
+                .type(SelectorTypeEnum.FULL_FLOW.getCode()).build();
+
+        DivideRuleHandle divideRuleHandle = new DivideRuleHandle();
+        // TODO need an annotation parsing common way
+        if (annotations != null) {
+            divideRuleHandle.setLoadBalance(annotations.getOrDefault(IngressConstants.LOADBALANCER_ANNOTATION_KEY, LoadBalanceEnum.RANDOM.getName()));
+            divideRuleHandle.setRetry(Integer.parseInt(annotations.getOrDefault(IngressConstants.RETRY_ANNOTATION_KEY, "3")));
+            divideRuleHandle.setTimeout(Long.parseLong(annotations.getOrDefault(IngressConstants.TIMEOUT_ANNOTATION_KEY, "3000")));
+            divideRuleHandle.setHeaderMaxSize(Long.parseLong(annotations.getOrDefault(IngressConstants.HEADER_MAX_SIZE_ANNOTATION_KEY, "10240")));
+            divideRuleHandle.setRequestMaxSize(Long.parseLong(annotations.getOrDefault(IngressConstants.REQUEST_MAX_SIZE_ANNOTATION_KEY, "102400")));
+        }
+        final RuleData ruleData = RuleData.builder()
+                .selectorId("1")
+                .pluginName(PluginEnum.DIVIDE.getName())
+                .name("default-rule")
+                .matchMode(MatchModeEnum.AND.getCode())
+                .conditionDataList(Collections.singletonList(conditionData))
+                .handle(GsonUtils.getInstance().toJson(divideRuleHandle))
+                .loged(false)
+                .enabled(true)
+                .sort(Integer.MAX_VALUE).build();
+
+        return Pair.of(selectorData, ruleData);
+    }
+}
diff --git a/shenyu-kubernetes-controller/src/main/java/org/apache/shenyu/k8s/parser/K8sResourceParser.java b/shenyu-kubernetes-controller/src/main/java/org/apache/shenyu/k8s/parser/K8sResourceParser.java
new file mode 100644
index 000000000..5e541aa3b
--- /dev/null
+++ b/shenyu-kubernetes-controller/src/main/java/org/apache/shenyu/k8s/parser/K8sResourceParser.java
@@ -0,0 +1,39 @@
+/*
+ * 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 org.apache.shenyu.k8s.parser;
+
+import io.kubernetes.client.openapi.apis.CoreV1Api;
+import org.apache.shenyu.k8s.common.ShenyuMemoryConfig;
+
+/**
+ * Parser of Kubernetes resource.
+ * Such as ingress, gateway, or even custom api resource.
+ *
+ * @param <T> resource type
+ */
+public interface K8sResourceParser<T> {
+
+    /**
+     * Parse resource to ShenyuMemoryConfig.
+     *
+     * @param resource resource
+     * @param coreV1Api coreV1Api
+     * @return ShenyuMemoryConfig
+     */
+    ShenyuMemoryConfig parse(T resource, CoreV1Api coreV1Api);
+}
diff --git a/shenyu-kubernetes-controller/src/main/java/org/apache/shenyu/k8s/reconciler/EndpointsReconciler.java b/shenyu-kubernetes-controller/src/main/java/org/apache/shenyu/k8s/reconciler/EndpointsReconciler.java
new file mode 100644
index 000000000..d96cea67c
--- /dev/null
+++ b/shenyu-kubernetes-controller/src/main/java/org/apache/shenyu/k8s/reconciler/EndpointsReconciler.java
@@ -0,0 +1,171 @@
+/*
+ * 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 org.apache.shenyu.k8s.reconciler;
+
+import io.kubernetes.client.extended.controller.reconciler.Reconciler;
+import io.kubernetes.client.extended.controller.reconciler.Request;
+import io.kubernetes.client.extended.controller.reconciler.Result;
+import io.kubernetes.client.informer.SharedIndexInformer;
+import io.kubernetes.client.informer.cache.Lister;
+import io.kubernetes.client.openapi.ApiClient;
+import io.kubernetes.client.openapi.models.CoreV1EndpointPort;
+import io.kubernetes.client.openapi.models.V1EndpointSubset;
+import io.kubernetes.client.openapi.models.V1EndpointAddress;
+import io.kubernetes.client.openapi.models.V1Endpoints;
+import io.kubernetes.client.openapi.models.V1Ingress;
+import org.apache.commons.lang3.tuple.Pair;
+import org.apache.shenyu.common.dto.SelectorData;
+import org.apache.shenyu.common.dto.convert.selector.DivideUpstream;
+import org.apache.shenyu.common.enums.PluginEnum;
+import org.apache.shenyu.common.exception.ShenyuException;
+import org.apache.shenyu.common.utils.GsonUtils;
+import org.apache.shenyu.k8s.cache.IngressSelectorCache;
+import org.apache.shenyu.k8s.cache.ServiceIngressCache;
+import org.apache.shenyu.k8s.repository.ShenyuCacheRepository;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * The Reconciler of Endpoints.
+ */
+public class EndpointsReconciler implements Reconciler {
+
+    private static final Logger LOG = LoggerFactory.getLogger(EndpointsReconciler.class);
+
+    private final Lister<V1Ingress> ingressLister;
+
+    private final Lister<V1Endpoints> endpointsLister;
+
+    private final ShenyuCacheRepository shenyuCacheRepository;
+
+    private final ApiClient apiClient;
+
+    /**
+     * EndpointsReconciler Constructor.
+     *
+     * @param ingressInformer ingressInformer
+     * @param endpointsInformer endpointsInformer
+     * @param shenyuCacheRepository shenyuCacheRepository
+     * @param apiClient apiClient
+     */
+    public EndpointsReconciler(final SharedIndexInformer<V1Ingress> ingressInformer,
+                               final SharedIndexInformer<V1Endpoints> endpointsInformer,
+                             final ShenyuCacheRepository shenyuCacheRepository,
+                             final ApiClient apiClient) {
+        this.ingressLister = new Lister<>(ingressInformer.getIndexer());
+        this.endpointsLister = new Lister<>(endpointsInformer.getIndexer());
+        this.shenyuCacheRepository = shenyuCacheRepository;
+        this.apiClient = apiClient;
+    }
+
+    /**
+     * Reconcile cycle.
+     *
+     * @param request request
+     * @return reconcile result
+     */
+    @Override
+    public Result reconcile(final Request request) {
+        List<Pair<String, String>> ingressList = ServiceIngressCache.getInstance().getIngressName(request.getNamespace(), request.getName());
+        if (ingressList == null || ingressList.isEmpty()) {
+            return new Result(false);
+        }
+
+        V1Endpoints v1Endpoints = endpointsLister.namespace(request.getNamespace()).get(request.getName());
+        if (v1Endpoints == null) {
+            // The deletion event is not processed, because deleting all upstreams in the Selector has
+            // the same effect as not deleting them, and they cannot be accessed
+            LOG.info("Cannot find endpoints {}", request);
+            return new Result(false);
+        }
+
+        // 1. Obtain upstream according to endpoints
+        List<DivideUpstream> upstreamList = getUpstreamFromEndpoints(v1Endpoints);
+
+        // 2. Update the handler of the selector
+        List<SelectorData> totalSelectors = shenyuCacheRepository.findSelectorDataList(PluginEnum.DIVIDE.getName());
+        Set<String> needUpdateSelectorId = new HashSet<>();
+        ingressList.forEach(item -> {
+            List<String> selectorIdList = IngressSelectorCache.getInstance().get(item.getLeft(), item.getRight(), PluginEnum.DIVIDE.getName());
+            needUpdateSelectorId.addAll(selectorIdList);
+        });
+        totalSelectors.forEach(selectorData -> {
+            if (needUpdateSelectorId.contains(selectorData.getId())) {
+                SelectorData newSelectorData = SelectorData.builder().id(selectorData.getId())
+                        .pluginId(selectorData.getPluginId())
+                        .pluginName(selectorData.getPluginName())
+                        .name(selectorData.getName())
+                        .matchMode(selectorData.getMatchMode())
+                        .type(selectorData.getType())
+                        .sort(selectorData.getSort())
+                        .enabled(selectorData.getEnabled())
+                        .logged(selectorData.getLogged())
+                        .continued(selectorData.getContinued())
+                        .handle(GsonUtils.getInstance().toJson(upstreamList))
+                        .conditionList(selectorData.getConditionList())
+                        .matchRestful(selectorData.getMatchRestful()).build();
+                shenyuCacheRepository.saveOrUpdateSelectorData(newSelectorData);
+            }
+        });
+        LOG.info("Update selector for endpoint {}", request);
+
+        return new Result(false);
+    }
+
+    private List<DivideUpstream> getUpstreamFromEndpoints(final V1Endpoints v1Endpoints) {
+        List<DivideUpstream> res = new ArrayList<>();
+        if (v1Endpoints.getSubsets() != null) {
+            for (V1EndpointSubset subset : v1Endpoints.getSubsets()) {
+                List<CoreV1EndpointPort> ports = subset.getPorts();
+                if (ports == null || ports.isEmpty() || subset.getAddresses() == null || subset.getAddresses().isEmpty()) {
+                    continue;
+                }
+                CoreV1EndpointPort endpointPort = ports.stream()
+                        .filter(coreV1EndpointPort -> "TCP".equals(coreV1EndpointPort.getProtocol()))
+                        .findFirst()
+                        .orElseThrow(() -> new ShenyuException("Can't find port from endpoints"));
+                String port = null;
+                if (endpointPort.getPort() > 0) {
+                    port = String.valueOf(endpointPort.getPort());
+                } else if (endpointPort.getName() != null) {
+                    port = endpointPort.getName();
+                }
+                for (V1EndpointAddress address : subset.getAddresses()) {
+                    String ip = address.getIp();
+                    if (ip != null) {
+                        DivideUpstream upstream = new DivideUpstream();
+                        upstream.setUpstreamUrl(ip + ":" + port);
+                        upstream.setWeight(100);
+                        // TODO support config protocol in annotation
+                        upstream.setProtocol("http://");
+                        upstream.setWarmup(0);
+                        upstream.setStatus(true);
+                        upstream.setUpstreamHost("");
+                        res.add(upstream);
+                    }
+                }
+            }
+        }
+        return res;
+    }
+}
diff --git a/shenyu-kubernetes-controller/src/main/java/org/apache/shenyu/k8s/reconciler/IngressReconciler.java b/shenyu-kubernetes-controller/src/main/java/org/apache/shenyu/k8s/reconciler/IngressReconciler.java
new file mode 100644
index 000000000..27b286854
--- /dev/null
+++ b/shenyu-kubernetes-controller/src/main/java/org/apache/shenyu/k8s/reconciler/IngressReconciler.java
@@ -0,0 +1,406 @@
+/*
+ * 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 org.apache.shenyu.k8s.reconciler;
+
+import io.kubernetes.client.extended.controller.reconciler.Reconciler;
+import io.kubernetes.client.extended.controller.reconciler.Request;
+import io.kubernetes.client.extended.controller.reconciler.Result;
+import io.kubernetes.client.informer.SharedIndexInformer;
+import io.kubernetes.client.informer.cache.Lister;
+import io.kubernetes.client.openapi.ApiClient;
+import io.kubernetes.client.openapi.apis.CoreV1Api;
+import io.kubernetes.client.openapi.models.V1HTTPIngressPath;
+import io.kubernetes.client.openapi.models.V1Ingress;
+import io.kubernetes.client.openapi.models.V1IngressRule;
+import io.kubernetes.client.openapi.models.V1Secret;
+import io.kubernetes.client.openapi.models.V1IngressBuilder;
+import org.apache.commons.lang3.tuple.Pair;
+import org.apache.shenyu.common.config.ssl.ShenyuSniAsyncMapping;
+import org.apache.shenyu.common.config.ssl.SslCrtAndKeyStream;
+import org.apache.shenyu.common.dto.PluginData;
+import org.apache.shenyu.common.dto.RuleData;
+import org.apache.shenyu.common.dto.SelectorData;
+import org.apache.shenyu.common.enums.PluginEnum;
+import org.apache.shenyu.common.enums.PluginRoleEnum;
+import org.apache.shenyu.k8s.cache.IngressCache;
+import org.apache.shenyu.k8s.cache.IngressSecretCache;
+import org.apache.shenyu.k8s.cache.IngressSelectorCache;
+import org.apache.shenyu.k8s.cache.ServiceIngressCache;
+import org.apache.shenyu.k8s.common.IngressConstants;
+import org.apache.shenyu.k8s.common.ShenyuMemoryConfig;
+import org.apache.shenyu.k8s.parser.IngressParser;
+import org.apache.shenyu.k8s.repository.ShenyuCacheRepository;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Optional;
+
+/**
+ * The Reconciler of Ingress.
+ */
+public class IngressReconciler implements Reconciler {
+
+    private static final Logger LOG = LoggerFactory.getLogger(IngressReconciler.class);
+
+    // ingressName serviceName selectorData ruleData
+    private static Pair<Pair<String, String>, Pair<SelectorData, RuleData>> globalDefaultBackend;
+
+    private final Lister<V1Ingress> ingressLister;
+
+    private final Lister<V1Secret> secretLister;
+
+    private final ShenyuCacheRepository shenyuCacheRepository;
+
+    private final ShenyuSniAsyncMapping shenyuSniAsyncMapping;
+
+    private final IngressParser ingressParser;
+
+    private final ApiClient apiClient;
+
+    /**
+     * IngressReconciler Constructor.
+     *
+     * @param ingressInformer ingressInformer
+     * @param secretInformer secretInformer
+     * @param shenyuCacheRepository shenyuCacheRepository
+     * @param shenyuSniAsyncMapping shenyuSniAsyncMapping
+     * @param ingressParser ingressParser
+     * @param apiClient apiClient
+     */
+    public IngressReconciler(final SharedIndexInformer<V1Ingress> ingressInformer,
+                             final SharedIndexInformer<V1Secret> secretInformer,
+                             final ShenyuCacheRepository shenyuCacheRepository,
+                             final ShenyuSniAsyncMapping shenyuSniAsyncMapping,
+                             final IngressParser ingressParser,
+                             final ApiClient apiClient) {
+        this.ingressLister = new Lister<>(ingressInformer.getIndexer());
+        this.secretLister = new Lister<>(secretInformer.getIndexer());
+        this.shenyuCacheRepository = shenyuCacheRepository;
+        this.shenyuSniAsyncMapping = shenyuSniAsyncMapping;
+        this.ingressParser = ingressParser;
+        this.apiClient = apiClient;
+        initPlugins(shenyuCacheRepository);
+    }
+
+    /**
+     * Reconcile cycle.
+     *
+     * @param request request
+     * @return reconcile result
+     */
+    @Override
+    public Result reconcile(final Request request) {
+        LOG.info("Starting to reconcile ingress {}", request);
+
+        // Do not modify current ingress object directly
+        final V1Ingress v1Ingress = this.ingressLister.namespace(request.getNamespace()).get(request.getName());
+        final V1Ingress oldIngress = IngressCache.getInstance().get(request.getNamespace(), request.getName());
+        if (v1Ingress == null) {
+            if (oldIngress != null) {
+                // Delete ingress binding selectors
+                doDeleteConfigByIngress(request, oldIngress);
+
+                // Remove ssl config
+                Set<String> sslDomainSet = IngressSecretCache.getInstance().getDomainByIngress(request.getNamespace(), request.getName());
+                if (sslDomainSet != null && !sslDomainSet.isEmpty()) {
+                    for (String sslDomain : sslDomainSet) {
+                        Integer preDomainSslNums = IngressSecretCache.getInstance().getAndDecrementDomainNums(sslDomain);
+                        if (preDomainSslNums == 1) {
+                            shenyuSniAsyncMapping.removeSslCertificate(sslDomain);
+                            LOG.info("Remove ssl config for domain {}", sslDomain);
+                        }
+                    }
+                }
+                IngressSecretCache.getInstance().removeDomainByIngress(request.getNamespace(), request.getName());
+
+                IngressCache.getInstance().remove(request.getNamespace(), request.getName());
+                LOG.info("Delete selector and rule for ingress {}", request);
+            } else {
+                LOG.info("Cannot find ingress {}", request);
+            }
+            return new Result(false);
+        }
+
+        if (!checkIngressClass(v1Ingress)) {
+            LOG.info("IngressClass is not match {}", request);
+            return new Result(false);
+        }
+
+        if (oldIngress == null) {
+            try {
+                addNewIngressConfigToShenyu(v1Ingress, new CoreV1Api(apiClient));
+            } catch (IOException e) {
+                LOG.error("add new ingress config error", e);
+            }
+        } else if (needUpdate(oldIngress, v1Ingress)) {
+            // Update logic
+            // 1. clean old config
+            doDeleteConfigByIngress(request, oldIngress);
+
+            // 2. add new config
+            try {
+                addNewIngressConfigToShenyu(v1Ingress, new CoreV1Api(apiClient));
+            } catch (IOException e) {
+                LOG.error("add new ingress config error", e);
+            }
+        }
+        IngressCache.getInstance().put(request.getNamespace(), request.getName(), v1Ingress);
+        List<Pair<String, String>> serviceList = parseServiceFromIngress(v1Ingress);
+        Objects.requireNonNull(serviceList).forEach(pair -> {
+            ServiceIngressCache.getInstance().putIngressName(pair.getLeft(), pair.getRight(), request.getNamespace(), request.getName());
+            LOG.info("Add service cache {} for ingress {}", pair.getLeft() + "/" + pair.getRight(), request.getNamespace() + "/" + request.getName());
+        });
+
+        return new Result(false);
+    }
+
+    private void doDeleteConfigByIngress(final Request request, final V1Ingress oldIngress) {
+        List<String> selectorList = deleteSelectorByIngressName(request.getNamespace(), request.getName(), PluginEnum.DIVIDE.getName());
+        if (selectorList != null && !selectorList.isEmpty()) {
+            IngressSelectorCache.getInstance().remove(request.getNamespace(), request.getName(), PluginEnum.DIVIDE.getName());
+        }
+        List<Pair<String, String>> serviceList = parseServiceFromIngress(oldIngress);
+        Objects.requireNonNull(serviceList).forEach(pair -> {
+            ServiceIngressCache.getInstance().removeSpecifiedIngressName(pair.getLeft(), pair.getRight(), request.getNamespace(), request.getName());
+            LOG.info("Delete service cache {} for ingress {}", pair.getLeft() + "/" + pair.getRight(), request.getNamespace() + "/" + request.getName());
+        });
+        deleteGlobalDefaultBackend(request.getNamespace(), request.getName());
+    }
+
+    private void deleteGlobalDefaultBackend(final String namespace, final String name) {
+        if (globalDefaultBackend != null && (namespace + "/" + name).equals(globalDefaultBackend.getLeft().getLeft())) {
+            globalDefaultBackend = null;
+        }
+    }
+
+    private void initPlugins(final ShenyuCacheRepository shenyuCacheRepository) {
+        //GLOBAL
+        PluginData globalPlugin = PluginData.builder()
+                .id(String.valueOf(PluginEnum.GLOBAL.getCode()))
+                .name(PluginEnum.GLOBAL.getName())
+                .config("")
+                .role(PluginRoleEnum.SYS.getName())
+                .enabled(true)
+                .sort(PluginEnum.GLOBAL.getCode())
+                .build();
+        shenyuCacheRepository.saveOrUpdatePluginData(globalPlugin);
+        //uri
+        PluginData uriPlugin = PluginData.builder()
+                .id(String.valueOf(PluginEnum.URI.getCode()))
+                .name(PluginEnum.URI.getName())
+                .config("")
+                .role(PluginRoleEnum.SYS.getName())
+                .enabled(true)
+                .sort(PluginEnum.URI.getCode())
+                .build();
+        shenyuCacheRepository.saveOrUpdatePluginData(uriPlugin);
+        //nettyHttpClient
+        PluginData webclientPlugin = PluginData.builder()
+                .id(String.valueOf(PluginEnum.NETTY_HTTP_CLIENT.getCode()))
+                .config("")
+                .name(PluginEnum.NETTY_HTTP_CLIENT.getName())
+                .role(PluginRoleEnum.SYS.getName())
+                .enabled(true)
+                .sort(PluginEnum.NETTY_HTTP_CLIENT.getCode())
+                .build();
+        shenyuCacheRepository.saveOrUpdatePluginData(webclientPlugin);
+        //divide
+        PluginData dividePlugin = PluginData.builder()
+                .id(String.valueOf(PluginEnum.DIVIDE.getCode()))
+                .name(PluginEnum.DIVIDE.getName())
+                .config("{multiSelectorHandle: 1, multiRuleHandle:0}")
+                .role(PluginRoleEnum.SYS.getName())
+                .enabled(true)
+                .sort(PluginEnum.DIVIDE.getCode())
+                .build();
+        shenyuCacheRepository.saveOrUpdatePluginData(dividePlugin);
+        //GeneralContextPlugin
+        PluginData generalContextPlugin = PluginData.builder()
+                .id(String.valueOf(PluginEnum.GENERAL_CONTEXT.getCode()))
+                .config("")
+                .name(PluginEnum.GENERAL_CONTEXT.getName())
+                .role(PluginRoleEnum.SYS.getName())
+                .enabled(true)
+                .sort(PluginEnum.GENERAL_CONTEXT.getCode())
+                .build();
+        shenyuCacheRepository.saveOrUpdatePluginData(generalContextPlugin);
+    }
+
+    /**
+     * Check whether the IngressClass is shenyu, check the annotation first.
+     *
+     * @param v1Ingress v1Ingress
+     * @return boolean
+     */
+    private boolean checkIngressClass(final V1Ingress v1Ingress) {
+        if (v1Ingress.getMetadata() != null) {
+            Map<String, String> annotations = v1Ingress.getMetadata().getAnnotations();
+            if (annotations != null
+                    && annotations.get(IngressConstants.K8S_INGRESS_CLASS_ANNOTATION_KEY) != null) {
+                return IngressConstants.SHENYU_INGRESS_CLASS.equals(annotations.get(IngressConstants.K8S_INGRESS_CLASS_ANNOTATION_KEY));
+            } else {
+                return v1Ingress.getSpec() != null && IngressConstants.SHENYU_INGRESS_CLASS.equals(v1Ingress.getSpec().getIngressClassName());
+            }
+        } else {
+            return false;
+        }
+    }
+
+    private List<String> deleteSelectorByIngressName(final String namespace, final String name, final String pluginName) {
+        final List<String> selectorList = IngressSelectorCache.getInstance().get(namespace, name, pluginName);
+        if (selectorList != null && !selectorList.isEmpty()) {
+            for (String selectorId : selectorList) {
+                List<RuleData> ruleList = shenyuCacheRepository.findRuleDataList(selectorId);
+                // To avoid ConcurrentModificationException, copy the ruleId to list
+                List<String> ruleIdList = new ArrayList<>();
+                ruleList.forEach(rule -> ruleIdList.add(rule.getId()));
+                for (String id : ruleIdList) {
+                    shenyuCacheRepository.deleteRuleData(pluginName, selectorId, id);
+                }
+                shenyuCacheRepository.deleteSelectorData(pluginName, selectorId);
+            }
+        }
+        return selectorList;
+    }
+
+    private List<Pair<String, String>> parseServiceFromIngress(final V1Ingress ingress) {
+        List<Pair<String, String>> res = new ArrayList<>();
+        if (ingress == null || ingress.getSpec() == null) {
+            return res;
+        }
+        String namespace = Objects.requireNonNull(ingress.getMetadata()).getNamespace();
+        String name = ingress.getMetadata().getName();
+        String namespacedName = namespace + "/" + name;
+        String defaultService = null;
+        if (ingress.getSpec().getDefaultBackend() != null && ingress.getSpec().getDefaultBackend().getService() != null) {
+            defaultService = ingress.getSpec().getDefaultBackend().getService().getName();
+            if (ingress.getSpec().getRules() == null) {
+                if (globalDefaultBackend != null) {
+                    if (globalDefaultBackend.getLeft().getLeft().equals(namespacedName)) {
+                        res.add(Pair.of(namespace, defaultService));
+                    }
+                } else {
+                    res.add(Pair.of(namespace, defaultService));
+                }
+                return res;
+            }
+        }
+        Set<String> deduplicateSet = new HashSet<>();
+        if (ingress.getSpec().getRules() == null) {
+            return res;
+        }
+        for (V1IngressRule rule : ingress.getSpec().getRules()) {
+            if (rule.getHttp() != null && rule.getHttp().getPaths() != null) {
+                for (V1HTTPIngressPath path : rule.getHttp().getPaths()) {
+                    if (path.getBackend() != null && path.getBackend().getService() != null) {
+                        if (!deduplicateSet.contains(path.getBackend().getService().getName())) {
+                            res.add(Pair.of(namespace, path.getBackend().getService().getName()));
+                            deduplicateSet.add(path.getBackend().getService().getName());
+                        }
+                    } else {
+                        if (defaultService != null && !deduplicateSet.contains(defaultService)) {
+                            res.add(Pair.of(namespace, defaultService));
+                            deduplicateSet.add(defaultService);
+                        }
+                    }
+                }
+            }
+        }
+        return res;
+    }
+
+    private boolean needUpdate(final V1Ingress oldIngress, final V1Ingress currentIngress) {
+        return !oldIngress.equals(currentIngress);
+    }
+
+    private void addNewIngressConfigToShenyu(final V1Ingress v1Ingress, final CoreV1Api apiClient) throws IOException {
+        V1Ingress ingressCopy = new V1IngressBuilder(v1Ingress).build();
+        ShenyuMemoryConfig shenyuMemoryConfig = ingressParser.parse(ingressCopy, apiClient);
+        if (shenyuMemoryConfig != null) {
+            List<Pair<SelectorData, RuleData>> routeConfigList = shenyuMemoryConfig.getRouteConfigList();
+            List<SslCrtAndKeyStream> tlsConfigList = shenyuMemoryConfig.getTlsConfigList();
+
+            if (routeConfigList != null) {
+                routeConfigList.forEach(routeConfig -> {
+                    SelectorData selectorData = routeConfig.getLeft();
+                    RuleData ruleData = routeConfig.getRight();
+                    if (selectorData != null) {
+                        selectorData.setId(IngressSelectorCache.getInstance().generateSelectorId());
+                        selectorData.setSort(100);
+                        shenyuCacheRepository.saveOrUpdateSelectorData(selectorData);
+                        if (ruleData != null) {
+                            ruleData.setId(selectorData.getId());
+                            ruleData.setSelectorId(selectorData.getId());
+                            ruleData.setSort(100);
+                            shenyuCacheRepository.saveOrUpdateRuleData(ruleData);
+                            IngressSelectorCache.getInstance().put(Objects.requireNonNull(v1Ingress.getMetadata()).getNamespace(),
+                                    v1Ingress.getMetadata().getName(), PluginEnum.DIVIDE.getName(), selectorData.getId());
+                        } else {
+                            shenyuCacheRepository.deleteSelectorData(selectorData.getPluginName(), selectorData.getId());
+                        }
+                    }
+                });
+            }
+
+            if (shenyuMemoryConfig.getGlobalDefaultBackend() != null) {
+                synchronized (IngressReconciler.class) {
+                    if (globalDefaultBackend == null) {
+                        // Add a default backend
+                        shenyuCacheRepository.saveOrUpdateSelectorData(shenyuMemoryConfig.getGlobalDefaultBackend().getRight().getLeft());
+                        shenyuCacheRepository.saveOrUpdateRuleData(shenyuMemoryConfig.getGlobalDefaultBackend().getRight().getRight());
+                        globalDefaultBackend = shenyuMemoryConfig.getGlobalDefaultBackend();
+                        IngressSelectorCache.getInstance().put(Objects.requireNonNull(v1Ingress.getMetadata()).getNamespace(),
+                                v1Ingress.getMetadata().getName(), PluginEnum.DIVIDE.getName(), shenyuMemoryConfig.getGlobalDefaultBackend().getRight().getLeft().getId());
+                    }
+                }
+            }
+
+            if (tlsConfigList != null) {
+                final String namespace = Objects.requireNonNull(v1Ingress.getMetadata()).getNamespace();
+                final String ingressName = v1Ingress.getMetadata().getName();
+                Set<String> oldDomainSet = Optional.ofNullable(IngressSecretCache.getInstance().removeDomainByIngress(namespace, ingressName)).orElse(new HashSet<>());
+                Set<String> newDomainSet = new HashSet<>();
+                for (SslCrtAndKeyStream sslCrtAndKeyStream : tlsConfigList) {
+                    final String domain = sslCrtAndKeyStream.getDomain();
+                    if (!oldDomainSet.contains(domain)) {
+                        if (IngressSecretCache.getInstance().getAndIncrementDomainNums(domain) == 0) {
+                            shenyuSniAsyncMapping.addSslCertificate(sslCrtAndKeyStream);
+                            LOG.info("Add ssl config for domain {}", domain);
+                        }
+                    }
+                    newDomainSet.add(domain);
+                }
+                oldDomainSet.removeAll(newDomainSet);
+                oldDomainSet.forEach(domain -> {
+                    if (IngressSecretCache.getInstance().getAndDecrementDomainNums(domain) == 1) {
+                        shenyuSniAsyncMapping.removeSslCertificate(domain);
+                        LOG.info("Remove ssl config for domain {}", domain);
+                    }
+                });
+                IngressSecretCache.getInstance().putDomainByIngress(namespace, ingressName, newDomainSet);
+            }
+        }
+    }
+}
diff --git a/shenyu-kubernetes-controller/src/main/java/org/apache/shenyu/k8s/repository/ShenyuCacheRepository.java b/shenyu-kubernetes-controller/src/main/java/org/apache/shenyu/k8s/repository/ShenyuCacheRepository.java
new file mode 100644
index 000000000..d5c7ac456
--- /dev/null
+++ b/shenyu-kubernetes-controller/src/main/java/org/apache/shenyu/k8s/repository/ShenyuCacheRepository.java
@@ -0,0 +1,135 @@
+/*
+ * 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 org.apache.shenyu.k8s.repository;
+
+import org.apache.shenyu.common.dto.PluginData;
+import org.apache.shenyu.common.dto.RuleData;
+import org.apache.shenyu.common.dto.SelectorData;
+import org.apache.shenyu.plugin.base.cache.BaseDataCache;
+import org.apache.shenyu.sync.data.api.PluginDataSubscriber;
+
+import java.util.List;
+
+/**
+ * The repository to config shenyu.
+ *
+ * <p>Should try to avoid directly operating memory configuration through PluginDataSubscriber
+ * in ingress-controller, but use ShenyuCacheRepository.
+ * This will make it easier for us if we have architectural changes to the ingress-controller.
+ * </p>
+ */
+public class ShenyuCacheRepository {
+
+    private final PluginDataSubscriber subscriber;
+
+    /**
+     * Shenyu Cache Repository Constructor.
+     *
+     * @param subscriber PluginDataSubscriber
+     */
+    public ShenyuCacheRepository(final PluginDataSubscriber subscriber) {
+        this.subscriber = subscriber;
+    }
+
+    /**
+     * Find PluginData by plugin name.
+     *
+     * @param pluginName  plugin name
+     * @return PluginData
+     */
+    public PluginData findPluginData(final String pluginName) {
+        return BaseDataCache.getInstance().obtainPluginData(pluginName);
+    }
+
+    /**
+     * Save or update PluginData by PluginData.
+     *
+     * @param pluginData PluginData
+     */
+    public void saveOrUpdatePluginData(final PluginData pluginData) {
+        subscriber.onSubscribe(pluginData);
+    }
+
+    /**
+     * Delete PluginData by plugin name.
+     *
+     * @param pluginName plugin name
+     */
+    public void deletePluginData(final String pluginName) {
+        subscriber.unSubscribe(PluginData.builder().name(pluginName).build());
+    }
+
+    /**
+     * Find SelectorData list by pluginName.
+     *
+     * @param pluginName plugin name
+     * @return SelectorData list
+     */
+    public List<SelectorData> findSelectorDataList(final String pluginName) {
+        return BaseDataCache.getInstance().obtainSelectorData(pluginName);
+    }
+
+    /**
+     * Save or update SelectorData by SelectorData.
+     *
+     * @param selectorData SelectorData
+     */
+    public void saveOrUpdateSelectorData(final SelectorData selectorData) {
+        subscriber.onSelectorSubscribe(selectorData);
+    }
+
+    /**
+     * Delete SelectorData by plugin name and selector id.
+     *
+     * @param pluginName plugin name
+     * @param selectorId selector id
+     */
+    public void deleteSelectorData(final String pluginName, final String selectorId) {
+        subscriber.unSelectorSubscribe(SelectorData.builder().pluginName(pluginName).id(selectorId).build());
+    }
+
+    /**
+     * Find RuleData list by selector id.
+     *
+     * @param selectorId selector id
+     * @return RuleData list
+     */
+    public List<RuleData> findRuleDataList(final String selectorId) {
+        return BaseDataCache.getInstance().obtainRuleData(selectorId);
+    }
+
+    /**
+     * Save or update RuleData by RuleData.
+     *
+     * @param ruleData RuleData
+     */
+    public void saveOrUpdateRuleData(final RuleData ruleData) {
+        subscriber.onRuleSubscribe(ruleData);
+    }
+
+    /**
+     * Delete RuleData by plugin name, selector id and rule id.
+     *
+     * @param pluginName plugin name
+     * @param selectorId selector id
+     * @param ruleId rule id
+     */
+    public void deleteRuleData(final String pluginName, final String selectorId, final String ruleId) {
+        subscriber.unRuleSubscribe(RuleData.builder().pluginName(pluginName).selectorId(selectorId).id(ruleId).build());
+    }
+}
diff --git a/shenyu-plugin/shenyu-plugin-base/src/main/java/org/apache/shenyu/plugin/base/trie/ShenyuTrie.java b/shenyu-plugin/shenyu-plugin-base/src/main/java/org/apache/shenyu/plugin/base/trie/ShenyuTrie.java
index 80cedf0c8..dd8aef30d 100644
--- a/shenyu-plugin/shenyu-plugin-base/src/main/java/org/apache/shenyu/plugin/base/trie/ShenyuTrie.java
+++ b/shenyu-plugin/shenyu-plugin-base/src/main/java/org/apache/shenyu/plugin/base/trie/ShenyuTrie.java
@@ -274,7 +274,9 @@ public class ShenyuTrie {
                 String[] parentPathArray = Arrays.copyOfRange(pathParts, 0, pathParts.length - 1);
                 String parentPath = String.join("/", parentPathArray);
                 ShenyuTrieNode parentNode = this.getNode(parentPath);
-                parentNode.getChildren().remove(key);
+                if (parentNode != null) {
+                    parentNode.getChildren().remove(key);
+                }
             }
         }
     }
diff --git a/shenyu-spring-boot-starter/pom.xml b/shenyu-spring-boot-starter/pom.xml
index 071894ae9..4150dcc15 100644
--- a/shenyu-spring-boot-starter/pom.xml
+++ b/shenyu-spring-boot-starter/pom.xml
@@ -33,6 +33,7 @@
         <module>shenyu-spring-boot-starter-client</module>
         <module>shenyu-spring-boot-starter-instance</module>
         <module>shenyu-spring-boot-starter-sdk</module>
+        <module>shenyu-spring-boot-starter-k8s</module>
     </modules>
 
     <dependencies>
diff --git a/shenyu-spring-boot-starter/shenyu-spring-boot-starter-gateway/src/main/java/org/apache/shenyu/springboot/starter/netty/ShenyuNettyWebServerConfiguration.java b/shenyu-spring-boot-starter/shenyu-spring-boot-starter-gateway/src/main/java/org/apache/shenyu/springboot/starter/netty/ShenyuNettyWebServerConfiguration.java
index ce1a1683b..8d2cb692c 100644
--- a/shenyu-spring-boot-starter/shenyu-spring-boot-starter-gateway/src/main/java/org/apache/shenyu/springboot/starter/netty/ShenyuNettyWebServerConfiguration.java
+++ b/shenyu-spring-boot-starter/shenyu-spring-boot-starter-gateway/src/main/java/org/apache/shenyu/springboot/starter/netty/ShenyuNettyWebServerConfiguration.java
@@ -19,6 +19,12 @@ package org.apache.shenyu.springboot.starter.netty;
 
 import io.netty.channel.ChannelOption;
 import io.netty.channel.WriteBufferWaterMark;
+import org.apache.shenyu.common.config.NettyHttpProperties;
+import org.apache.shenyu.common.exception.ShenyuException;
+import org.apache.shenyu.common.config.ssl.ShenyuSniAsyncMapping;
+import org.apache.shenyu.common.config.ssl.SslCrtAndKeyFile;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.ObjectProvider;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.boot.context.properties.ConfigurationProperties;
@@ -28,14 +34,23 @@ import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import reactor.netty.http.server.HttpServer;
 import reactor.netty.resources.LoopResources;
+import reactor.netty.tcp.SslProvider;
+import reactor.netty.tcp.TcpSslContextSpec;
 
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+import java.util.Objects;
 import java.util.Optional;
+import java.util.function.Function;
 
 /**
  * The type shenyu netty web server factory.
  */
 @Configuration
 public class ShenyuNettyWebServerConfiguration {
+
+    private static final Logger LOG = LoggerFactory.getLogger(ShenyuNettyWebServerConfiguration.class);
     
     /**
      * Netty tcp config.
@@ -47,38 +62,96 @@ public class ShenyuNettyWebServerConfiguration {
     public NettyHttpProperties nettyTcpProperties() {
         return new NettyHttpProperties();
     }
+
+    /**
+     * AsyncMapping for dynamic configure ssl.
+     *
+     * @return ShenyuSniAsyncMapping
+     */
+    @Bean
+    @ConditionalOnProperty(value = {"shenyu.netty.http.web-server-factory-enabled", "shenyu.netty.http.sni.enabled"}, havingValue = "true")
+    public ShenyuSniAsyncMapping shenyuSniAsyncMapping() {
+        return new ShenyuSniAsyncMapping();
+    }
     
     /**
      * Netty reactive web server factory.
      *
      * @param properties the properties
+     * @param shenyuSniAsyncMappingProvider shenyuSniAsyncMapping
+     * @param tcpSslContextSpecs default tcpSslContextSpecs
      * @return the netty reactive web server factory
      */
     @Bean
     @ConditionalOnProperty(value = "shenyu.netty.http.web-server-factory-enabled", havingValue = "true", matchIfMissing = true)
-    public NettyReactiveWebServerFactory nettyReactiveWebServerFactory(final ObjectProvider<NettyHttpProperties> properties) {
+    public NettyReactiveWebServerFactory nettyReactiveWebServerFactory(final ObjectProvider<NettyHttpProperties> properties,
+                                                                       final ObjectProvider<ShenyuSniAsyncMapping> shenyuSniAsyncMappingProvider,
+                                                                       final ObjectProvider<TcpSslContextSpec> tcpSslContextSpecs) {
         NettyReactiveWebServerFactory webServerFactory = new NettyReactiveWebServerFactory();
         NettyHttpProperties nettyHttpProperties = Optional.ofNullable(properties.getIfAvailable()).orElse(new NettyHttpProperties());
-        webServerFactory.addServerCustomizers(new EventLoopNettyCustomizer(nettyHttpProperties));
+        webServerFactory.addServerCustomizers(new EventLoopNettyCustomizer(nettyHttpProperties, httpServer -> {
+            // Configure sni certificates
+            NettyHttpProperties.SniProperties sniProperties = nettyHttpProperties.getSni();
+            if (sniProperties.getEnabled()) {
+                ShenyuSniAsyncMapping shenyuSniAsyncMapping = shenyuSniAsyncMappingProvider.getIfAvailable();
+                if (shenyuSniAsyncMapping == null) {
+                    throw new ShenyuException("Can not find shenyuSniAsyncMapping bean");
+                }
+                if ("manual".equals(sniProperties.getMod())) {
+                    if (sniProperties.getCertificates() == null || sniProperties.getCertificates().isEmpty()) {
+                        throw new ShenyuException("At least one certificate is required");
+                    }
+
+                    // Use the first certificate as the default certificate (this default certificate will not actually be used)
+                    List<SslCrtAndKeyFile> certificates = sniProperties.getCertificates();
+                    for (SslCrtAndKeyFile certificate : certificates) {
+                        try {
+                            shenyuSniAsyncMapping.addSslCertificate(certificate);
+                        } catch (IOException e) {
+                            LOG.error("add certificate error", e);
+                        }
+                    }
+
+                    SslCrtAndKeyFile defaultCert = certificates.get(0);
+                    TcpSslContextSpec defaultSpec = TcpSslContextSpec.forServer(new File(defaultCert.getKeyCertChainFile()),
+                            new File(defaultCert.getKeyFile()));
+
+                    httpServer = httpServer.secure(spec -> spec.sslContext(defaultSpec)
+                                .setSniAsyncMappings(shenyuSniAsyncMapping), false);
+                } else if ("k8s".equals(sniProperties.getMod())) {
+                    TcpSslContextSpec defaultSpec = Objects.requireNonNull(tcpSslContextSpecs.getIfAvailable());
+                    httpServer = httpServer.secure(spec -> spec.sslContext(defaultSpec)
+                            .setSniAsyncMappings(shenyuSniAsyncMapping), false);
+                    shenyuSniAsyncMapping.addSslProvider("shenyu-default", SslProvider.builder().sslContext(defaultSpec).build());
+                } else {
+                    throw new ShenyuException("Cannot read the sni mod");
+                }
+            }
+            return httpServer;
+        }));
         return webServerFactory;
     }
 
     private static class EventLoopNettyCustomizer implements NettyServerCustomizer {
 
         private final NettyHttpProperties nettyHttpProperties;
+
+        private final Function<HttpServer, HttpServer> sniProcessor;
     
         /**
          * Instantiates a new Event loop netty customizer.
          *
          * @param nettyHttpProperties the netty tcp config
          */
-        EventLoopNettyCustomizer(final NettyHttpProperties nettyHttpProperties) {
+        EventLoopNettyCustomizer(final NettyHttpProperties nettyHttpProperties, final Function<HttpServer, HttpServer> sniProcessor) {
             this.nettyHttpProperties = nettyHttpProperties;
+            this.sniProcessor = sniProcessor;
         }
 
         @Override
         public HttpServer apply(final HttpServer httpServer) {
-            return httpServer.runOn(LoopResources.create("shenyu-netty", nettyHttpProperties.getSelectCount(), nettyHttpProperties.getWorkerCount(), true))
+            return sniProcessor.apply(httpServer)
+                    .runOn(LoopResources.create("shenyu-netty", nettyHttpProperties.getSelectCount(), nettyHttpProperties.getWorkerCount(), true))
                     .accessLog(nettyHttpProperties.getAccessLog())
                     // server socket channel parameters
                     .option(ChannelOption.SO_BACKLOG, nettyHttpProperties.getServerSocketChannel().getSoBacklog())
diff --git a/shenyu-spring-boot-starter/shenyu-spring-boot-starter-gateway/src/test/java/org/apache/shenyu/springboot/starter/netty/ShenyuNettyWebServerConfigurationTest.java b/shenyu-spring-boot-starter/shenyu-spring-boot-starter-gateway/src/test/java/org/apache/shenyu/springboot/starter/netty/ShenyuNettyWebServerConfigurationTest.java
index e8395bec9..634d4a1db 100644
--- a/shenyu-spring-boot-starter/shenyu-spring-boot-starter-gateway/src/test/java/org/apache/shenyu/springboot/starter/netty/ShenyuNettyWebServerConfigurationTest.java
+++ b/shenyu-spring-boot-starter/shenyu-spring-boot-starter-gateway/src/test/java/org/apache/shenyu/springboot/starter/netty/ShenyuNettyWebServerConfigurationTest.java
@@ -17,6 +17,7 @@
 
 package org.apache.shenyu.springboot.starter.netty;
 
+import org.apache.shenyu.common.config.NettyHttpProperties;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.springframework.boot.autoconfigure.AutoConfigurations;
diff --git a/shenyu-spring-boot-starter/pom.xml b/shenyu-spring-boot-starter/shenyu-spring-boot-starter-k8s/pom.xml
similarity index 57%
copy from shenyu-spring-boot-starter/pom.xml
copy to shenyu-spring-boot-starter/shenyu-spring-boot-starter-k8s/pom.xml
index 071894ae9..933002d3f 100644
--- a/shenyu-spring-boot-starter/pom.xml
+++ b/shenyu-spring-boot-starter/shenyu-spring-boot-starter-k8s/pom.xml
@@ -16,31 +16,27 @@
   ~ limitations under the License.
   -->
 
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
     <parent>
         <groupId>org.apache.shenyu</groupId>
-        <artifactId>shenyu</artifactId>
+        <artifactId>shenyu-spring-boot-starter</artifactId>
         <version>2.6.0-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
-    <artifactId>shenyu-spring-boot-starter</artifactId>
-    <packaging>pom</packaging>
-
-    <modules>
-        <module>shenyu-spring-boot-starter-gateway</module>
-        <module>shenyu-spring-boot-starter-plugin</module>
-        <module>shenyu-spring-boot-starter-sync-data-center</module>
-        <module>shenyu-spring-boot-starter-client</module>
-        <module>shenyu-spring-boot-starter-instance</module>
-        <module>shenyu-spring-boot-starter-sdk</module>
-    </modules>
+    <artifactId>shenyu-spring-boot-starter-k8s</artifactId>
 
     <dependencies>
+        <dependency>
+            <groupId>org.apache.shenyu</groupId>
+            <artifactId>shenyu-kubernetes-controller</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
         <dependency>
             <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-configuration-processor</artifactId>
-            <optional>true</optional>
+            <artifactId>spring-boot-starter</artifactId>
         </dependency>
     </dependencies>
-
-</project>
+</project>
\ No newline at end of file
diff --git a/shenyu-spring-boot-starter/shenyu-spring-boot-starter-k8s/src/main/java/org/apache/shenyu/springboot/starter/k8s/IngressControllerConfiguration.java b/shenyu-spring-boot-starter/shenyu-spring-boot-starter-k8s/src/main/java/org/apache/shenyu/springboot/starter/k8s/IngressControllerConfiguration.java
new file mode 100644
index 000000000..e54a42638
--- /dev/null
+++ b/shenyu-spring-boot-starter/shenyu-spring-boot-starter-k8s/src/main/java/org/apache/shenyu/springboot/starter/k8s/IngressControllerConfiguration.java
@@ -0,0 +1,264 @@
+/*
+ * 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 org.apache.shenyu.springboot.starter.k8s;
+
+import io.kubernetes.client.extended.controller.Controller;
+import io.kubernetes.client.extended.controller.ControllerManager;
+import io.kubernetes.client.extended.controller.builder.ControllerBuilder;
+import io.kubernetes.client.extended.controller.builder.DefaultControllerBuilder;
+import io.kubernetes.client.informer.SharedIndexInformer;
+import io.kubernetes.client.informer.SharedInformerFactory;
+import io.kubernetes.client.openapi.ApiClient;
+import io.kubernetes.client.openapi.ApiException;
+import io.kubernetes.client.openapi.apis.CoreV1Api;
+import io.kubernetes.client.openapi.models.V1Endpoints;
+import io.kubernetes.client.openapi.models.V1Ingress;
+import io.kubernetes.client.openapi.models.V1Secret;
+import io.kubernetes.client.openapi.models.V1Service;
+import io.kubernetes.client.openapi.models.V1SecretList;
+import io.kubernetes.client.openapi.models.V1ServiceList;
+import io.kubernetes.client.openapi.models.V1IngressList;
+import io.kubernetes.client.openapi.models.V1EndpointsList;
+import io.kubernetes.client.util.generic.GenericKubernetesApi;
+import org.apache.shenyu.common.config.NettyHttpProperties;
+import org.apache.shenyu.common.config.ssl.ShenyuSniAsyncMapping;
+import org.apache.shenyu.common.exception.ShenyuException;
+import org.apache.shenyu.k8s.parser.IngressParser;
+import org.apache.shenyu.k8s.reconciler.EndpointsReconciler;
+import org.apache.shenyu.k8s.reconciler.IngressReconciler;
+import org.apache.shenyu.k8s.repository.ShenyuCacheRepository;
+import org.apache.shenyu.sync.data.api.PluginDataSubscriber;
+import org.springframework.beans.factory.ObjectProvider;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import reactor.netty.tcp.TcpSslContextSpec;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.time.Duration;
+import java.util.Optional;
+import java.util.concurrent.Executors;
+
+/**
+ * The type shenyu ingress controller configuration.
+ */
+@Configuration
+public class IngressControllerConfiguration {
+
+    /**
+     * Controller Manager.
+     *
+     * @param sharedInformerFactory sharedInformerFactory
+     * @param ingressController ingressController
+     * @param endpointsController endpointsController
+     * @return Controller Manager
+     */
+    @Bean("controller-manager")
+    public ControllerManager controllerManager(final SharedInformerFactory sharedInformerFactory,
+                                               @Qualifier("ingress-controller") final Controller ingressController,
+                                               @Qualifier("endpoints-controller") final Controller endpointsController) {
+        ControllerManager controllerManager = new ControllerManager(sharedInformerFactory, ingressController, endpointsController);
+        Executors.newSingleThreadExecutor().submit(controllerManager);
+        return controllerManager;
+    }
+
+    /**
+     * Ingress Controller.
+     *
+     * @param sharedInformerFactory sharedInformerFactory
+     * @param ingressReconciler ingressReconciler
+     * @return Ingress Controller
+     */
+    @Bean("ingress-controller")
+    public Controller ingressController(final SharedInformerFactory sharedInformerFactory, final IngressReconciler ingressReconciler) {
+        DefaultControllerBuilder builder = ControllerBuilder.defaultBuilder(sharedInformerFactory);
+        builder = builder.watch(q -> ControllerBuilder.controllerWatchBuilder(V1Ingress.class, q)
+                                .withResyncPeriod(Duration.ofMinutes(1))
+                                .build());
+        // TODO support config in application.yaml
+        builder.withWorkerCount(2);
+//        builder.withReadyFunc(ingressReconciler::informerReady);
+        return builder.withReconciler(ingressReconciler).withName("ingressController").build();
+    }
+
+    /**
+     * Ingress Reconciler.
+     *
+     * @param ingressInformer ingress shared informer
+     * @param secretInformer secret shared informer
+     * @param shenyuCacheRepository ShenyuCacheRepository
+     * @param shenyuSniAsyncMappingProvider shenyuSniAsyncMappingProvider
+     * @param ingressParser IngressParser
+     * @param apiClient ApiClient
+     * @return Ingress Reconciler
+     */
+    @Bean
+    public IngressReconciler ingressReconciler(final SharedIndexInformer<V1Ingress> ingressInformer,
+                                               final SharedIndexInformer<V1Secret> secretInformer,
+                                               final ShenyuCacheRepository shenyuCacheRepository,
+                                               final ObjectProvider<ShenyuSniAsyncMapping> shenyuSniAsyncMappingProvider,
+                                               final IngressParser ingressParser,
+                                               final ApiClient apiClient) {
+        ShenyuSniAsyncMapping shenyuSniAsyncMapping = Optional.ofNullable(shenyuSniAsyncMappingProvider.getIfAvailable()).orElse(new ShenyuSniAsyncMapping());
+        return new IngressReconciler(ingressInformer, secretInformer, shenyuCacheRepository, shenyuSniAsyncMapping, ingressParser, apiClient);
+    }
+
+    /**
+     * Endpoints Controller.
+     *
+     * @param sharedInformerFactory sharedInformerFactory
+     * @param endpointsReconciler endpointsReconciler
+     * @return Endpoints Controller
+     */
+    @Bean("endpoints-controller")
+    public Controller endpointsController(final SharedInformerFactory sharedInformerFactory, final EndpointsReconciler endpointsReconciler) {
+        DefaultControllerBuilder builder = ControllerBuilder.defaultBuilder(sharedInformerFactory);
+        builder = builder.watch(q -> ControllerBuilder.controllerWatchBuilder(V1Endpoints.class, q)
+                .withResyncPeriod(Duration.ofMinutes(1))
+                .build());
+        builder.withWorkerCount(2);
+        return builder.withReconciler(endpointsReconciler).withName("ingressController").build();
+    }
+
+    /**
+     * EndpointsReconciler.
+     *
+     * @param ingressInformer ingressInformer
+     * @param endpointsInformer endpointsInformer
+     * @param shenyuCacheRepository shenyuCacheRepository
+     * @param apiClient apiClient
+     * @return EndpointsReconciler
+     */
+    @Bean
+    public EndpointsReconciler endpointsReconciler(final SharedIndexInformer<V1Ingress> ingressInformer,
+                                                   final SharedIndexInformer<V1Endpoints> endpointsInformer,
+                                                   final ShenyuCacheRepository shenyuCacheRepository,
+                                                   final ApiClient apiClient) {
+        return new EndpointsReconciler(ingressInformer, endpointsInformer, shenyuCacheRepository, apiClient);
+    }
+
+    /**
+     * ShenyuCacheRepository.
+     *
+     * @param subscriber PluginDataSubscriber
+     * @return ShenyuCacheRepository
+     */
+    @Bean
+    public ShenyuCacheRepository shenyuCacheRepository(final PluginDataSubscriber subscriber) {
+        return new ShenyuCacheRepository(subscriber);
+    }
+
+    /**
+     * IngressParser.
+     *
+     * @param serviceInformer serviceInformer
+     * @param endpointsInformer endpointsInformer
+     * @return IngressParser
+     */
+    @Bean
+    public IngressParser ingressParser(final SharedIndexInformer<V1Service> serviceInformer, final SharedIndexInformer<V1Endpoints> endpointsInformer) {
+        return new IngressParser(serviceInformer, endpointsInformer);
+    }
+
+    /**
+     * ServiceInformer.
+     *
+     * @param apiClient apiClient
+     * @param sharedInformerFactory sharedInformerFactory
+     * @return serviceInformer
+     */
+    @Bean
+    public SharedIndexInformer<V1Service> serviceInformer(final ApiClient apiClient, final SharedInformerFactory sharedInformerFactory) {
+        GenericKubernetesApi<V1Service, V1ServiceList> genericApi = new GenericKubernetesApi<>(V1Service.class,
+                V1ServiceList.class, "", "v1", "services", apiClient);
+        return sharedInformerFactory.sharedIndexInformerFor(genericApi, V1Service.class, 0);
+    }
+
+    /**
+     * EndpointsInformer.
+     *
+     * @param apiClient apiClient
+     * @param sharedInformerFactory sharedInformerFactory
+     * @return endpointsInformer
+     */
+    @Bean
+    public SharedIndexInformer<V1Endpoints> endpointsInformer(final ApiClient apiClient, final SharedInformerFactory sharedInformerFactory) {
+        GenericKubernetesApi<V1Endpoints, V1EndpointsList> genericApi = new GenericKubernetesApi<>(V1Endpoints.class,
+                V1EndpointsList.class, "", "v1", "endpoints", apiClient);
+        return sharedInformerFactory.sharedIndexInformerFor(genericApi, V1Endpoints.class, 0);
+    }
+
+    /**
+     * SecretInformer.
+     *
+     * @param apiClient apiClient
+     * @param sharedInformerFactory sharedInformerFactory
+     * @return secretInformer
+     */
+    @Bean
+    public SharedIndexInformer<V1Secret> secretInformer(final ApiClient apiClient, final SharedInformerFactory sharedInformerFactory) {
+        GenericKubernetesApi<V1Secret, V1SecretList> genericApi = new GenericKubernetesApi<>(V1Secret.class,
+                V1SecretList.class, "", "v1", "secrets", apiClient);
+        return sharedInformerFactory.sharedIndexInformerFor(genericApi, V1Secret.class, 0);
+    }
+
+    /**
+     * IngressInformer.
+     *
+     * @param apiClient apiClient
+     * @param sharedInformerFactory sharedInformerFactory
+     * @return ingressInformer
+     */
+    @Bean
+    public SharedIndexInformer<V1Ingress> ingressInformer(final ApiClient apiClient, final SharedInformerFactory sharedInformerFactory) {
+        GenericKubernetesApi<V1Ingress, V1IngressList> genericApi = new GenericKubernetesApi<>(V1Ingress.class,
+                V1IngressList.class, "networking.k8s.io", "v1", "ingresses", apiClient);
+        return sharedInformerFactory.sharedIndexInformerFor(genericApi, V1Ingress.class, 0);
+    }
+
+    /**
+     * TcpSslContextSpec.
+     *
+     * @param properties NettyHttpProperties
+     * @param apiClient ApiClient
+     * @return TcpSslContextSpec
+     * @throws ApiException the exception when use apiClient directly
+     */
+    @Bean
+    @ConditionalOnProperty(value = {"shenyu.netty.http.web-server-factory-enabled", "shenyu.netty.http.sni.enabled"}, havingValue = "true")
+    public TcpSslContextSpec tcpSslContextSpec(final ObjectProvider<NettyHttpProperties> properties, final ApiClient apiClient) throws ApiException {
+        NettyHttpProperties nettyHttpProperties = Optional.ofNullable(properties.getIfAvailable()).orElse(new NettyHttpProperties());
+        NettyHttpProperties.SniProperties sniProperties = nettyHttpProperties.getSni();
+        if (sniProperties != null && sniProperties.getEnabled() && "k8s".equals(sniProperties.getMod())) {
+            String defaultName = Optional.ofNullable(sniProperties.getDefaultK8sSecretName()).orElse("default-ingress-crt");
+            String defaultNamespace = Optional.ofNullable(sniProperties.getDefaultK8sSecretNamespace()).orElse("default");
+            CoreV1Api coreV1Api = new CoreV1Api(apiClient);
+            V1Secret secret = coreV1Api.readNamespacedSecret(defaultName, defaultNamespace, "true");
+            if (secret.getData() != null) {
+                InputStream crtStream = new ByteArrayInputStream(secret.getData().get("tls.crt"));
+                InputStream keyStream = new ByteArrayInputStream(secret.getData().get("tls.key"));
+                return TcpSslContextSpec.forServer(crtStream, keyStream);
+            } else {
+                throw new ShenyuException(String.format("Can not read cert and key from default secret %s/%s", defaultNamespace, defaultName));
+            }
+        }
+        return TcpSslContextSpec.forServer(new ByteArrayInputStream(new byte[]{}), new ByteArrayInputStream(new byte[]{}));
+    }
+}
diff --git a/shenyu-spring-boot-starter/shenyu-spring-boot-starter-k8s/src/main/resources/META-INF/spring.factories b/shenyu-spring-boot-starter/shenyu-spring-boot-starter-k8s/src/main/resources/META-INF/spring.factories
new file mode 100644
index 000000000..6899440f0
--- /dev/null
+++ b/shenyu-spring-boot-starter/shenyu-spring-boot-starter-k8s/src/main/resources/META-INF/spring.factories
@@ -0,0 +1,3 @@
+# Auto Configure
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
+org.apache.shenyu.springboot.starter.k8s.IngressControllerConfiguration
diff --git a/shenyu-spring-boot-starter/shenyu-spring-boot-starter-k8s/src/main/resources/META-INF/spring.provides b/shenyu-spring-boot-starter/shenyu-spring-boot-starter-k8s/src/main/resources/META-INF/spring.provides
new file mode 100644
index 000000000..db2bf2607
--- /dev/null
+++ b/shenyu-spring-boot-starter/shenyu-spring-boot-starter-k8s/src/main/resources/META-INF/spring.provides
@@ -0,0 +1 @@
+provides: shenyu-spring-boot-starter-ingress
\ No newline at end of file
diff --git a/shenyu-web/src/main/java/org/apache/shenyu/web/configuration/ShenyuThreadPoolConfiguration.java b/shenyu-web/src/main/java/org/apache/shenyu/web/configuration/ShenyuThreadPoolConfiguration.java
index 6e9c48e90..aeb567aa8 100644
--- a/shenyu-web/src/main/java/org/apache/shenyu/web/configuration/ShenyuThreadPoolConfiguration.java
+++ b/shenyu-web/src/main/java/org/apache/shenyu/web/configuration/ShenyuThreadPoolConfiguration.java
@@ -28,6 +28,7 @@ import org.apache.shenyu.common.constant.Constants;
 import org.apache.shenyu.common.exception.ShenyuException;
 import org.apache.shenyu.plugin.api.utils.SpringBeanUtils;
 import org.springframework.beans.factory.ObjectProvider;
+import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
@@ -54,6 +55,7 @@ public class ShenyuThreadPoolConfiguration {
      */
     @Bean
     @ConditionalOnMissingBean(TaskQueue.class)
+    @Qualifier("taskQueue")
     @ConditionalOnProperty("shenyu.shared-pool.max-work-queue-memory")
     public TaskQueue<Runnable> memoryLimitedTaskQueue(final ShenyuConfig shenyuConfig) {
         final Instrumentation instrumentation = ByteBuddyAgent.install();
@@ -73,6 +75,7 @@ public class ShenyuThreadPoolConfiguration {
      */
     @Bean
     @ConditionalOnMissingBean(TaskQueue.class)
+    @Qualifier("taskQueue")
     @ConditionalOnProperty("shenyu.shared-pool.max-free-memory")
     public TaskQueue<Runnable> memorySafeTaskQueue(final ShenyuConfig shenyuConfig) {
         final ShenyuConfig.SharedPool sharedPool = shenyuConfig.getSharedPool();
@@ -93,7 +96,7 @@ public class ShenyuThreadPoolConfiguration {
     @Bean
     @ConditionalOnProperty(name = "shenyu.shared-pool.enable", havingValue = "true", matchIfMissing = true)
     public ShenyuThreadPoolExecutor shenyuThreadPoolExecutor(final ShenyuConfig shenyuConfig,
-                                                             final ObjectProvider<TaskQueue<Runnable>> provider) {
+                                                             final @Qualifier("taskQueue") ObjectProvider<TaskQueue<Runnable>> provider) {
         final ShenyuConfig.SharedPool sharedPool = shenyuConfig.getSharedPool();
         final Integer corePoolSize = sharedPool.getCorePoolSize();
         final Integer maximumPoolSize = sharedPool.getMaximumPoolSize();