Repository: antrea-io/antrea
Pull request #3246 (2022-01-26 06:39:06)

Use a smaller nginx image for multicluster e2e test
Use Kubernetes upstream nginx image instead of offical nginx image, which makes multi-cluster e2e test faster.

Signed-off-by: hjiajing <hjiajing@vmware.com>

hjiajing: /test-multicluster-e2e
hjiajing: /test-multicluster-e2e
hjiajing: /test-multicluster-e2e

Patch:
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index b70493048..dd6fdc760 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -5,14 +5,10 @@ on:
     branches:
       - main
       - release-*
-      # TODO: remove this before merging feature branch to main
-      - feature/multi-cluster
   push:
     branches:
       - main
       - release-*
-      # TODO: remove this before merging feature branch to main
-      - feature/multi-cluster
 
 jobs:
   check-changes:
@@ -120,7 +116,7 @@ jobs:
     - name: Build antrea-mc-controller Docker image
       run: make antrea-mc-controller
     - name: Push antrea-mc-controller Docker image to registry
-      if: ${{ github.repository == 'antrea-io/antrea' && github.event_name == 'push' && github.ref == 'refs/heads/feature/multi-cluster' }}
+      if: ${{ github.repository == 'antrea-io/antrea' && github.event_name == 'push' && github.ref == 'refs/heads/main' }}
       env:
         DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
         DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml
index ec2f50fd1..523373395 100644
--- a/.github/workflows/go.yml
+++ b/.github/workflows/go.yml
@@ -4,12 +4,10 @@ on:
     branches:
     - main
     - release-*
-    - feature/multi-cluster
   push:
     branches:
     - main
     - release-*
-    - feature/multi-cluster
 
 env:
   go-cache-name: go
@@ -187,7 +185,7 @@ jobs:
         config-file: 'hack/.md_links_config.json'
     - name: Markdownlint
       run: |
-        sudo npm install -g markdownlint-cli
+        sudo npm install -g markdownlint-cli@0.31.1
         make markdownlint
 
   benchmark:
diff --git a/CHANGELOG/CHANGELOG-1.5.md b/CHANGELOG/CHANGELOG-1.5.md
index bfda6c555..0c2dc181d 100644
--- a/CHANGELOG/CHANGELOG-1.5.md
+++ b/CHANGELOG/CHANGELOG-1.5.md
@@ -16,7 +16,7 @@
 - Add support for SR-IOV secondary network. Antrea can now create secondary network interfaces for Pods using SR-IOV VFs on bare metal Nodes. ([#2651](https://github.com/antrea-io/antrea/pull/2651), [@arunvelayutham]) [Alpha - Feature Gate: `SecondaryNetwork`]
 - Add support for allocating external IPs for Services of type LoadBalancer from an ExternalIPPool. ([#3147](https://github.com/antrea-io/antrea/pull/3147) [@Shengkai2000]) [Alpha - Feature Gate: `ServiceExternalIP`]
 - Add support for antctl in the flow aggregator Pod. ([#2878](https://github.com/antrea-io/antrea/pull/2878), [@yanjunz97])
-  * Support `antctl get log-level` for changing log verbosity level
+  * Support `antctl log-level` for changing log verbosity level
   * Support `antctl get flowrecords [-o json]` for dumping flow records
   * Support `antctl get recordmetrics` for dumping flow records metrics
 - Add support for the "Pass" action in Antrea-native policies to skip evaluation of further Antrea-native policy rules and delegate evaluation to Kubernetes NetworkPolicy. ([#2964](https://github.com/antrea-io/antrea/pull/2964), [@Dyanngg])
diff --git a/CHANGELOG/README.md b/CHANGELOG/README.md
index 2075b67c4..a97ab1da8 100644
--- a/CHANGELOG/README.md
+++ b/CHANGELOG/README.md
@@ -10,6 +10,7 @@ stages](https://github.com/kubernetes/community/blob/master/contributors/devel/s
 Some experimental features can be enabled / disabled using [Feature
 Gates](../docs/feature-gates.md).
 
+- [CHANGELOG-1.5](CHANGELOG-1.5.md)
 - [CHANGELOG-1.4](CHANGELOG-1.4.md)
 - [CHANGELOG-1.3](CHANGELOG-1.3.md)
 - [CHANGELOG-1.2](CHANGELOG-1.2.md)
diff --git a/build/images/scripts/install_cni b/build/images/scripts/install_cni
index a464662ef..569523064 100755
--- a/build/images/scripts/install_cni
+++ b/build/images/scripts/install_cni
@@ -4,17 +4,9 @@ set -euo pipefail
 
 # Todo: check version and continue installation only for a newer version
 
-# Install Antrea configuration file
-install -m 644 /etc/antrea/antrea-cni.conflist /host/etc/cni/net.d/10-antrea.conflist
-
 # Install Antrea binary file
 install -m 755 /usr/local/bin/antrea-cni /host/opt/cni/bin/antrea
 
-# If more than one CNI config file exists, the file with the lowest name is
-# chosen i.e. existing 10-antrea.conf will be chosen over 10-antrea.conflist.
-# Hence, delete older 10-antrea.conf file.
-rm -f /host/etc/cni/net.d/10-antrea.conf
-
 # Install the loopback plugin.
 # It is required by kubelet on Linux when using docker as the container runtime.
 # We replace the binary files even they are already present on the Node to make
@@ -30,6 +22,16 @@ install -m 755 /opt/cni/bin/bandwidth /host/opt/cni/bin/bandwidth
 # Install whereabouts IPAM binary file. Required for global IPAM support specific to CNF use cases.
 install -m 755 /opt/cni/bin/whereabouts /host/opt/cni/bin/whereabouts
 
+# Install Antrea configuration file.
+# Note that it needs to be executed after installing the above binaries because container runtimes such as cri-o may
+# watch the conf directory and try to validate the config and binaries immediately once there is a change.
+install -m 644 /etc/antrea/antrea-cni.conflist /host/etc/cni/net.d/10-antrea.conflist
+
+# If more than one CNI config file exists, the file with the lowest name is
+# chosen i.e. existing 10-antrea.conf will be chosen over 10-antrea.conflist.
+# Hence, delete older 10-antrea.conf file.
+rm -f /host/etc/cni/net.d/10-antrea.conf
+
 # Load the OVS kernel module
 modprobe openvswitch || (echo "Failed to load the OVS kernel module from the container, try running 'modprobe openvswitch' on your Nodes"; exit 1)
 
diff --git a/cmd/antrea-agent/agent.go b/cmd/antrea-agent/agent.go
index c80035c4d..eecb52fce 100644
--- a/cmd/antrea-agent/agent.go
+++ b/cmd/antrea-agent/agent.go
@@ -451,7 +451,9 @@ func run(o *Options) error {
 
 	// Initialize localPodInformer for NPLAgent and AntreaIPAMController
 	var localPodInformer cache.SharedIndexInformer
-	if features.DefaultFeatureGate.Enabled(features.NodePortLocal) && o.config.NodePortLocal.Enable || features.DefaultFeatureGate.Enabled(features.AntreaIPAM) {
+	if features.DefaultFeatureGate.Enabled(features.NodePortLocal) && o.config.NodePortLocal.Enable ||
+		features.DefaultFeatureGate.Enabled(features.AntreaIPAM) ||
+		features.DefaultFeatureGate.Enabled(features.SecondaryNetwork) {
 		listOptions := func(options *metav1.ListOptions) {
 			options.FieldSelector = fields.OneTermEqualSelector("spec.nodeName", nodeConfig.Name).String()
 		}
@@ -463,6 +465,19 @@ func run(o *Options) error {
 			listOptions,
 		)
 	}
+
+	log.StartLogFileNumberMonitor(stopCh)
+
+	go routeClient.Run(stopCh)
+
+	go cniServer.Run(stopCh)
+
+	go antreaClientProvider.Run(stopCh)
+
+	go nodeRouteController.Run(stopCh)
+
+	go networkPolicyController.Run(stopCh)
+
 	// Initialize the NPL agent.
 	if features.DefaultFeatureGate.Enabled(features.NodePortLocal) && o.config.NodePortLocal.Enable {
 		nplController, err := npl.InitializeNPLAgent(
@@ -477,6 +492,7 @@ func run(o *Options) error {
 		}
 		go nplController.Run(stopCh)
 	}
+
 	// Initialize the Antrea IPAM agent.
 	if features.DefaultFeatureGate.Enabled(features.AntreaIPAM) {
 		ipamController, err := ipam.InitializeAntreaIPAMController(
@@ -490,25 +506,6 @@ func run(o *Options) error {
 		}
 		go ipamController.Run(stopCh)
 	}
-	//  Start the localPodInformer
-	if localPodInformer != nil {
-		go localPodInformer.Run(stopCh)
-	}
-
-	log.StartLogFileNumberMonitor(stopCh)
-
-	go routeClient.Run(stopCh)
-
-	go cniServer.Run(stopCh)
-
-	informerFactory.Start(stopCh)
-	crdInformerFactory.Start(stopCh)
-
-	go antreaClientProvider.Run(stopCh)
-
-	go nodeRouteController.Run(stopCh)
-
-	go networkPolicyController.Run(stopCh)
 
 	if features.DefaultFeatureGate.Enabled(features.SecondaryNetwork) {
 		// Create the NetworkAttachmentDefinition client, which handles access to secondary network object definition from the API Server.
@@ -516,20 +513,25 @@ func run(o *Options) error {
 		if err != nil {
 			return fmt.Errorf("NetworkAttachmentDefinition client creation failed. %v", err)
 		}
-		// Initialize podController to handle secondary network configuration for Pods with k8s.v1.cni.cncf.io/networks Annotation defined.
-		podWatchController, err := podwatch.InitializePodController(
+		// Create podController to handle secondary network configuration for Pods with k8s.v1.cni.cncf.io/networks Annotation defined.
+		podWatchController := podwatch.NewPodController(
 			k8sClient,
 			netAttachDefClient,
-			informerFactory,
+			localPodInformer,
 			nodeConfig.Name,
 			cniPodInfoStore,
 			cniServer)
-		if err != nil {
-			return fmt.Errorf("failed to initialize Pod Controller for secondary network interface management: %v", err)
-		}
 		go podWatchController.Run(stopCh)
 	}
 
+	//  Start the localPodInformer
+	if localPodInformer != nil {
+		go localPodInformer.Run(stopCh)
+	}
+
+	informerFactory.Start(stopCh)
+	crdInformerFactory.Start(stopCh)
+
 	if features.DefaultFeatureGate.Enabled(features.Egress) || features.DefaultFeatureGate.Enabled(features.ServiceExternalIP) {
 		go externalIPPoolController.Run(stopCh)
 		go localIPDetector.Run(stopCh)
diff --git a/docs/antrea-network-policy.md b/docs/antrea-network-policy.md
index e6b6e5a72..35eede834 100644
--- a/docs/antrea-network-policy.md
+++ b/docs/antrea-network-policy.md
@@ -836,7 +836,7 @@ status:
       lastTransitionTime: "2021-01-29T20:21:48Z"
 ```
 
-There are a few __restrictions__ on how ClusterGroups can be configured:
+There are a few **restrictions** on how ClusterGroups can be configured:
 
 - A ClusterGroup is a cluster-scoped resource and therefore can only be set in an Antrea
   ClusterNetworkPolicy's `appliedTo` and `to`/`from` peers.
diff --git a/docs/multicluster/getting-started.md b/docs/multicluster/getting-started.md
index 70eb5356d..8d1508646 100644
--- a/docs/multicluster/getting-started.md
+++ b/docs/multicluster/getting-started.md
@@ -36,7 +36,7 @@ run both member and leader controllers in one cluster.
      kubectl apply -f https://github.com/antrea-io/antrea/releases/download/<TAG>/antrea-multicluster-leader-global.yml
      ```
 
-   * To deploy the latest version (checkout and built from the `feature/multi-cluster` branch).
+   * To deploy the latest version (checkout and built from the `main` branch).
 
      ```bash
      kubectl apply -f multicluster/build/yamls/antrea-multicluster-leader-global.yml
@@ -54,7 +54,7 @@ manifest with new Namespace.
   sed 's/changeme/antrea-mcs-ns/g' antrea-multicluster-leader-namespaced.yml | kubectl apply -f -
   ```
   
-* To deploy the latest version (checkout and built from the `feature/multi-cluster` branch).
+* To deploy the latest version (checkout and built from the `main` branch).
   
   ```bash
   kubectl create ns antrea-mcs-ns
@@ -73,7 +73,7 @@ Namespace by default.
 kubectl apply -f https://github.com/antrea-io/antrea/releases/download/<TAG>/antrea-multicluster-member.yml
 ```
 
-* To deploy the latest version (checkout and built from the `feature/multi-cluster` branch).
+* To deploy the latest version (checkout and built from the `main` branch).
 
 ```bash
 kubectl apply -f multicluster/build/yamls/antrea-multicluster-member.yml
diff --git a/docs/node-port-local.md b/docs/node-port-local.md
index 03c2ab064..156a23361 100644
--- a/docs/node-port-local.md
+++ b/docs/node-port-local.md
@@ -121,6 +121,11 @@ metadata:
 This annotation indicates that port 8080 of the Pod can be reached through port
 61002 of the Node with IP Address 10.10.10.10.
 
+The `nodeportlocal.antrea.io` annotation is generated and managed by Antrea. It
+is not meant to be created or modified by users directly. A user-provided
+annotation is likely to be overwritten by Antrea, or may lead to unexpected
+behavior.
+
 NodePortLocal can only be used with Services of type `ClusterIP` or
 `LoadBalancer`. The `nodeportlocal.antrea.io` annotation has no effect for
 Services of type `NodePort` or `ExternalName`. The annotation also has no effect
diff --git a/docs/windows.md b/docs/windows.md
index bf1651c7b..6d35e84b3 100644
--- a/docs/windows.md
+++ b/docs/windows.md
@@ -184,6 +184,13 @@ spec:
       hostNetwork: true
 ```
 
+If the powershell version on your Window Node is earlier than v6.0,
+change the command from `pwsh` to `powershell` in kube-proxy.yml.
+
+```bash
+sed -i 's/pwsh/powershell/g' kube-proxy.yml
+```
+
 Then apply the `kube-proxy.yml`.
 
 ```bash
diff --git a/multicluster/build/yamls/antrea-multicluster-leader-namespaced.yml b/multicluster/build/yamls/antrea-multicluster-leader-namespaced.yml
index 2fa12f08a..eaf53082c 100644
--- a/multicluster/build/yamls/antrea-multicluster-leader-namespaced.yml
+++ b/multicluster/build/yamls/antrea-multicluster-leader-namespaced.yml
@@ -527,7 +527,7 @@ kind: Service
 metadata:
   labels:
     app: antrea
-    control-plane: antrea-mc-controller
+    component: antrea-mc-controller
   name: antrea-mc-controller-metrics-service
   namespace: changeme
 spec:
@@ -537,7 +537,7 @@ spec:
     targetPort: https
   selector:
     app: antrea
-    control-plane: antrea-mc-controller
+    component: antrea-mc-controller
 ---
 apiVersion: v1
 kind: Service
@@ -552,14 +552,14 @@ spec:
     targetPort: 9443
   selector:
     app: antrea
-    control-plane: antrea-mc-controller
+    component: antrea-mc-controller
 ---
 apiVersion: apps/v1
 kind: Deployment
 metadata:
   labels:
     app: antrea
-    control-plane: antrea-mc-controller
+    component: antrea-mc-controller
   name: antrea-mc-controller
   namespace: changeme
 spec:
@@ -567,12 +567,12 @@ spec:
   selector:
     matchLabels:
       app: antrea
-      control-plane: antrea-mc-controller
+      component: antrea-mc-controller
   template:
     metadata:
       labels:
         app: antrea
-        control-plane: antrea-mc-controller
+        component: antrea-mc-controller
     spec:
       containers:
       - args:
diff --git a/multicluster/build/yamls/antrea-multicluster-member.yml b/multicluster/build/yamls/antrea-multicluster-member.yml
index d5100e5ef..46ae7d71a 100644
--- a/multicluster/build/yamls/antrea-multicluster-member.yml
+++ b/multicluster/build/yamls/antrea-multicluster-member.yml
@@ -1718,7 +1718,7 @@ kind: Service
 metadata:
   labels:
     app: antrea
-    control-plane: antrea-mc-controller
+    component: antrea-mc-controller
   name: antrea-mc-controller-metrics-service
   namespace: kube-system
 spec:
@@ -1728,7 +1728,7 @@ spec:
     targetPort: https
   selector:
     app: antrea
-    control-plane: antrea-mc-controller
+    component: antrea-mc-controller
 ---
 apiVersion: v1
 kind: Service
@@ -1743,14 +1743,14 @@ spec:
     targetPort: 9443
   selector:
     app: antrea
-    control-plane: antrea-mc-controller
+    component: antrea-mc-controller
 ---
 apiVersion: apps/v1
 kind: Deployment
 metadata:
   labels:
     app: antrea
-    control-plane: antrea-mc-controller
+    component: antrea-mc-controller
   name: antrea-mc-controller
   namespace: kube-system
 spec:
@@ -1758,12 +1758,12 @@ spec:
   selector:
     matchLabels:
       app: antrea
-      control-plane: antrea-mc-controller
+      component: antrea-mc-controller
   template:
     metadata:
       labels:
         app: antrea
-        control-plane: antrea-mc-controller
+        component: antrea-mc-controller
     spec:
       containers:
       - args:
diff --git a/multicluster/config/manager/manager.yaml b/multicluster/config/manager/manager.yaml
index d42691fe1..58ecfc79f 100644
--- a/multicluster/config/manager/manager.yaml
+++ b/multicluster/config/manager/manager.yaml
@@ -4,16 +4,16 @@ metadata:
   name: controller
   namespace: system
   labels:
-    control-plane: antrea-mc-controller
+    component: antrea-mc-controller
 spec:
   selector:
     matchLabels:
-      control-plane: antrea-mc-controller
+      component: antrea-mc-controller
   replicas: 1
   template:
     metadata:
       labels:
-        control-plane: antrea-mc-controller
+        component: antrea-mc-controller
     spec:
       containers:
       - command:
diff --git a/multicluster/config/prometheus/monitor.yaml b/multicluster/config/prometheus/monitor.yaml
index 18d65b1ec..d35d97485 100644
--- a/multicluster/config/prometheus/monitor.yaml
+++ b/multicluster/config/prometheus/monitor.yaml
@@ -4,7 +4,7 @@ apiVersion: monitoring.coreos.com/v1
 kind: ServiceMonitor
 metadata:
   labels:
-    control-plane: antrea-mc-controller
+    component: antrea-mc-controller
   name: controller-metrics-monitor
   namespace: system
 spec:
@@ -17,4 +17,4 @@ spec:
         insecureSkipVerify: true
   selector:
     matchLabels:
-      control-plane: antrea-mc-controller
+      component: antrea-mc-controller
diff --git a/multicluster/config/rbac/auth_proxy_service.yaml b/multicluster/config/rbac/auth_proxy_service.yaml
index 91c1eb790..a0a5afc6b 100644
--- a/multicluster/config/rbac/auth_proxy_service.yaml
+++ b/multicluster/config/rbac/auth_proxy_service.yaml
@@ -2,7 +2,7 @@ apiVersion: v1
 kind: Service
 metadata:
   labels:
-    control-plane: antrea-mc-controller
+    component: antrea-mc-controller
   name: controller-metrics-service
   namespace: system
 spec:
@@ -11,4 +11,4 @@ spec:
     port: 8443
     targetPort: https
   selector:
-    control-plane: antrea-mc-controller
+    component: antrea-mc-controller
diff --git a/multicluster/config/webhook/service.yaml b/multicluster/config/webhook/service.yaml
index 6f2075f6e..9d89fe46a 100644
--- a/multicluster/config/webhook/service.yaml
+++ b/multicluster/config/webhook/service.yaml
@@ -9,4 +9,4 @@ spec:
     - port: 443
       targetPort: 9443
   selector:
-    control-plane: antrea-mc-controller
+    component: antrea-mc-controller
diff --git a/pkg/agent/controller/noderoute/node_route_controller.go b/pkg/agent/controller/noderoute/node_route_controller.go
index 543368775..fd9e96910 100644
--- a/pkg/agent/controller/noderoute/node_route_controller.go
+++ b/pkg/agent/controller/noderoute/node_route_controller.go
@@ -532,7 +532,7 @@ func (c *Controller) addNodeRoute(nodeName string, node *corev1.Node) error {
 
 		peerPodCIDRAddr, peerPodCIDR, err := net.ParseCIDR(podCIDR)
 		if err != nil {
-			klog.Errorf("Failed to parse PodCIDR %s for Node %s", node.Spec.PodCIDR, nodeName)
+			klog.Errorf("Failed to parse PodCIDR %s for Node %s", podCIDR, nodeName)
 			return nil
 		}
 		peerGatewayIP := ip.NextIP(peerPodCIDRAddr)
diff --git a/pkg/agent/controller/serviceexternalip/controller.go b/pkg/agent/controller/serviceexternalip/controller.go
index 0327ce933..7c28e236e 100644
--- a/pkg/agent/controller/serviceexternalip/controller.go
+++ b/pkg/agent/controller/serviceexternalip/controller.go
@@ -201,7 +201,11 @@ func (c *ServiceExternalIPController) enqueueServiceForEndpoints(obj interface{}
 	}
 	service, err := c.serviceLister.Services(endpoints.Namespace).Get(endpoints.Name)
 	if err != nil {
-		klog.ErrorS(err, "failed to get Service for Endpoint", "namespace", endpoints.Namespace, "name", endpoints.Name)
+		// The only possible error Lister.Get can return is NotFound.
+		// It's normal that some Endpoints don't have Service. For example, kube-scheduler and kube-controller-manager
+		// may use Endpoints for leader election. Even if the Endpoints should have a Service but it's not received yet,
+		// it's fine to ignore the error as the Service's add event will enqueue it.
+		klog.V(5).InfoS("Failed to get Service for Endpoints", "Endpoints", klog.KObj(endpoints), "err", err)
 		return
 	}
 	// we only care services with ServiceExternalTrafficPolicy setting to local.
diff --git a/pkg/agent/nodeportlocal/k8s/npl_controller.go b/pkg/agent/nodeportlocal/k8s/npl_controller.go
index 67225cc38..ba152354a 100644
--- a/pkg/agent/nodeportlocal/k8s/npl_controller.go
+++ b/pkg/agent/nodeportlocal/k8s/npl_controller.go
@@ -42,10 +42,14 @@ import (
 )
 
 const (
-	controllerName = "AntreaAgentNPLController"
+	controllerName = "NPLController"
 	minRetryDelay  = 2 * time.Second
 	maxRetryDelay  = 120 * time.Second
 	numWorkers     = 4
+
+	// Set resyncPeriod to 0 to disable resyncing.
+	// UpdateFunc event handler will be called only when the object is actually updated.
+	resyncPeriod = 0 * time.Minute
 )
 
 type NPLController struct {
@@ -63,7 +67,6 @@ type NPLController struct {
 func NewNPLController(kubeClient clientset.Interface,
 	podInformer cache.SharedIndexInformer,
 	svcInformer cache.SharedIndexInformer,
-	resyncPeriod time.Duration,
 	pt *portcache.PortTable,
 	nodeName string) *NPLController {
 	c := NPLController{
@@ -191,6 +194,15 @@ func (c *NPLController) checkDeletedSvc(obj interface{}) (*corev1.Service, error
 func validateNPLService(svc *corev1.Service) {
 	if svc.Spec.Type == corev1.ServiceTypeNodePort {
 		klog.InfoS("Service is of type NodePort and cannot be used for NodePortLocal, the NodePortLocal annotation will have no effect", "service", klog.KObj(svc))
+		return
+	}
+	if svc.Spec.Type == corev1.ServiceTypeExternalName {
+		klog.InfoS("Service is of type ExternalName and cannot be used for NodePortLocal, the NodePortLocal annotation will have no effect", "service", klog.KObj(svc))
+		return
+	}
+	if len(svc.Spec.Selector) == 0 {
+		klog.InfoS("Service does not have a selector, the NodePortLocal annotation will have no effect", "service", klog.KObj(svc))
+		return
 	}
 	for _, port := range svc.Spec.Ports {
 		if port.Protocol == corev1.ProtocolSCTP {
@@ -217,8 +229,8 @@ func (c *NPLController) enqueueSvcUpdate(oldObj, newObj interface{}) {
 	}
 
 	podKeys := sets.String{}
-	oldNPLEnabled := oldSvcAnnotation == "true" && oldSvc.Spec.Type != corev1.ServiceTypeNodePort
-	newNPLEnabled := newSvcAnnotation == "true" && newSvc.Spec.Type != corev1.ServiceTypeNodePort
+	oldNPLEnabled := oldSvcAnnotation == "true" && oldSvc.Spec.Type != corev1.ServiceTypeNodePort && oldSvc.Spec.Type != corev1.ServiceTypeExternalName
+	newNPLEnabled := newSvcAnnotation == "true" && newSvc.Spec.Type != corev1.ServiceTypeNodePort && newSvc.Spec.Type != corev1.ServiceTypeExternalName
 
 	if oldNPLEnabled != newNPLEnabled {
 		// Process Pods corresponding to Service with valid NPL annotation and Service type.
@@ -297,27 +309,27 @@ func (c *NPLController) getTargetPortsForServicesOfPod(obj interface{}) (sets.St
 	for _, service := range services {
 		svc, isSvc := service.(*corev1.Service)
 		// Selecting Services NOT of type NodePort, with Service selector matching Pod labels.
-		if isSvc && svc.Spec.Type != corev1.ServiceTypeNodePort {
-			if pod.Namespace == svc.Namespace &&
-				matchSvcSelectorPodLabels(svc.Spec.Selector, pod.GetLabels()) {
-				for _, port := range svc.Spec.Ports {
-					if port.Protocol == corev1.ProtocolSCTP {
-						// Not supported yet. A message is logged when the
-						// Service is processed.
-						continue
-					}
-					switch port.TargetPort.Type {
-					case intstr.Int:
-						// An entry of format <target-port>:<protocol> (e.g. 8080:TCP) is added for a target port in the set targetPortsInt.
-						// This is done to ensure that we can match with both port and protocol fields in container port of a Pod.
-						portProto := util.BuildPortProto(fmt.Sprint(port.TargetPort.IntVal), string(port.Protocol))
-						klog.V(4).Infof("Added target port in targetPortsInt set: %v", portProto)
-						targetPortsInt.Insert(portProto)
-					case intstr.String:
-						portProto := util.BuildPortProto(port.TargetPort.StrVal, string(port.Protocol))
-						klog.V(4).Infof("Added target port in targetPortsStr set: %v", portProto)
-						targetPortsStr.Insert(portProto)
-					}
+		if !isSvc || svc.Spec.Type == corev1.ServiceTypeNodePort || svc.Spec.Type == corev1.ServiceTypeExternalName {
+			continue
+		}
+		if pod.Namespace == svc.Namespace && matchSvcSelectorPodLabels(svc.Spec.Selector, pod.GetLabels()) {
+			for _, port := range svc.Spec.Ports {
+				if port.Protocol == corev1.ProtocolSCTP {
+					// Not supported yet. A message is logged when the
+					// Service is processed.
+					continue
+				}
+				switch port.TargetPort.Type {
+				case intstr.Int:
+					// An entry of format <target-port>:<protocol> (e.g. 8080:TCP) is added for a target port in the set targetPortsInt.
+					// This is done to ensure that we can match with both port and protocol fields in container port of a Pod.
+					portProto := util.BuildPortProto(fmt.Sprint(port.TargetPort.IntVal), string(port.Protocol))
+					klog.V(4).Infof("Added target port in targetPortsInt set: %v", portProto)
+					targetPortsInt.Insert(portProto)
+				case intstr.String:
+					portProto := util.BuildPortProto(port.TargetPort.StrVal, string(port.Protocol))
+					klog.V(4).Infof("Added target port in targetPortsStr set: %v", portProto)
+					targetPortsStr.Insert(portProto)
 				}
 			}
 		}
diff --git a/pkg/agent/nodeportlocal/npl_agent_init.go b/pkg/agent/nodeportlocal/npl_agent_init.go
index fb0336913..b2b09ef42 100644
--- a/pkg/agent/nodeportlocal/npl_agent_init.go
+++ b/pkg/agent/nodeportlocal/npl_agent_init.go
@@ -19,7 +19,6 @@ package nodeportlocal
 
 import (
 	"fmt"
-	"time"
 
 	nplk8s "antrea.io/antrea/pkg/agent/nodeportlocal/k8s"
 	"antrea.io/antrea/pkg/agent/nodeportlocal/portcache"
@@ -29,10 +28,6 @@ import (
 	"k8s.io/client-go/tools/cache"
 )
 
-// Set resyncPeriod to 0 to disable resyncing.
-// UpdateFunc event handler will be called only when the object is actually updated.
-const resyncPeriod = 0 * time.Minute
-
 // InitializeNPLAgent initializes the NodePortLocal agent.
 // It sets up event handlers to handle Pod add, update and delete events.
 // When a Pod gets created, a free Node port is obtained from the port table cache and a DNAT rule is added to NAT traffic to the Pod's ip:port.
@@ -49,20 +44,6 @@ func InitializeNPLAgent(
 		return nil, fmt.Errorf("error when initializing NodePortLocal port table: %v", err)
 	}
 
-	return InitController(kubeClient, informerFactory, portTable, nodeName, podInformer)
-}
-
-// InitController initializes the NPLController with appropriate Pod and Service Informers.
-// This function can be used independently while unit testing without using InitializeNPLAgent function.
-func InitController(kubeClient clientset.Interface, informerFactory informers.SharedInformerFactory, portTable *portcache.PortTable, nodeName string, podInformer cache.SharedIndexInformer) (*nplk8s.NPLController, error) {
 	svcInformer := informerFactory.Core().V1().Services().Informer()
-
-	c := nplk8s.NewNPLController(kubeClient,
-		podInformer,
-		svcInformer,
-		resyncPeriod,
-		portTable,
-		nodeName)
-
-	return c, nil
+	return nplk8s.NewNPLController(kubeClient, podInformer, svcInformer, portTable, nodeName), nil
 }
diff --git a/pkg/agent/route/route_linux.go b/pkg/agent/route/route_linux.go
index a62e8f721..67de65a80 100644
--- a/pkg/agent/route/route_linux.go
+++ b/pkg/agent/route/route_linux.go
@@ -694,7 +694,7 @@ func (c *Client) Reconcile(podCIDRs []string, svcIPs map[string]bool) error {
 			if desiredPodCIDRs.Has(entry) {
 				continue
 			}
-			klog.Infof("Deleting orphaned PodIP %s from ipset and route table", entry)
+			klog.Infof("Deleting orphaned Pod IP %s from ipset and route table", entry)
 			if err := ipset.DelEntry(ipsetName, entry); err != nil {
 				return err
 			}
@@ -748,7 +748,7 @@ func (c *Client) Reconcile(podCIDRs []string, svcIPs map[string]bool) error {
 		if desiredGWs.Has(neighIP) {
 			continue
 		}
-		// Don't delete the virtual Service IP neigh which is added by AntreaProxy.
+		// Don't delete the virtual Service IP neighbor which is added by AntreaProxy.
 		if actualNeigh.IP.Equal(config.VirtualServiceIPv6) {
 			continue
 		}
@@ -1157,7 +1157,7 @@ func (c *Client) AddClusterIPRoute(svcIP net.IP) error {
 			} else {
 				c.clusterIPv4CIDR = newClusterIPCIDR
 			}
-			klog.V(4).InfoS("Created a route with new CLusterIP CIDR to route the ClusterIP to Antrea gateway", "CLusterIP CIDR", newClusterIPCIDR, "clusterIP", svcIP)
+			klog.V(4).InfoS("Created a route with new ClusterIP CIDR to route the ClusterIP to Antrea gateway", "ClusterIP CIDR", newClusterIPCIDR, "clusterIP", svcIP)
 		} else {
 			klog.V(4).InfoS("Route with current ClusterIP CIDR can route the ClusterIP to Antrea gateway", "ClusterIP CIDR", curClusterIPCIDR, "clusterIP", svcIP)
 		}
diff --git a/pkg/agent/secondarynetwork/podwatch/controller.go b/pkg/agent/secondarynetwork/podwatch/controller.go
index bbf4746b6..f92441876 100644
--- a/pkg/agent/secondarynetwork/podwatch/controller.go
+++ b/pkg/agent/secondarynetwork/podwatch/controller.go
@@ -26,11 +26,8 @@ import (
 
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"k8s.io/apimachinery/pkg/fields"
 	"k8s.io/apimachinery/pkg/util/rand"
 	"k8s.io/apimachinery/pkg/util/wait"
-	"k8s.io/client-go/informers"
-	coreinformers "k8s.io/client-go/informers/core/v1"
 	clientset "k8s.io/client-go/kubernetes"
 	corelisters "k8s.io/client-go/listers/core/v1"
 	"k8s.io/client-go/tools/cache"
@@ -81,7 +78,6 @@ type PodController struct {
 func NewPodController(kubeClient clientset.Interface,
 	netAttachDefClient netdefclient.K8sCniCncfIoV1Interface,
 	podInformer cache.SharedIndexInformer,
-	resyncPeriod time.Duration,
 	nodeName string,
 	podCache cnipodcache.CNIPodInfoStore,
 	cniServer *cniserver.CNIServer) *PodController {
@@ -398,26 +394,3 @@ func parsePodSecondaryNetworkAnnotation(netObj string) ([]*SecondaryNetworkObjec
 	}
 	return secNetwork, nil
 }
-
-func InitializePodController(kubeClient clientset.Interface, netAttachDefClient netdefclient.K8sCniCncfIoV1Interface, informerFactory informers.SharedInformerFactory,
-	nodeName string, podInfoStore cnipodcache.CNIPodInfoStore, cniServer *cniserver.CNIServer) (*PodController, error) {
-	// Watch only the Pods which belong to the Node where the agent is running.
-	listOptions := func(options *metav1.ListOptions) {
-		options.FieldSelector = fields.OneTermEqualSelector("spec.nodeName", nodeName).String()
-	}
-	podInformer := coreinformers.NewFilteredPodInformer(
-		kubeClient,
-		metav1.NamespaceAll,
-		resyncPeriod,
-		cache.Indexers{},
-		listOptions,
-	)
-	c := NewPodController(kubeClient,
-		netAttachDefClient,
-		podInformer,
-		resyncPeriod,
-		nodeName,
-		podInfoStore,
-		cniServer)
-	return c, nil
-}


Test patch:
diff --git a/ci/jenkins/test-mc.sh b/ci/jenkins/test-mc.sh
index d63cb8115..d794146c2 100755
--- a/ci/jenkins/test-mc.sh
+++ b/ci/jenkins/test-mc.sh
@@ -33,6 +33,8 @@ LEADER_CLUSTER_CONFIG="--kubeconfig=$MULTICLUSTER_KUBECONFIG_PATH/leader"
 EAST_CLUSTER_CONFIG="--kubeconfig=$MULTICLUSTER_KUBECONFIG_PATH/east"
 WEST_CLUSTER_CONFIG="--kubeconfig=$MULTICLUSTER_KUBECONFIG_PATH/west"
 
+NGINX_IMAGE=projects.registry.vmware.com/antrea/nginx:1.21.6-alpine
+
 CONTROL_PLANE_NODE_ROLE="control-plane,master"
 
 multicluster_kubeconfigs=($EAST_CLUSTER_CONFIG $LEADER_CLUSTER_CONFIG $WEST_CLUSTER_CONFIG)
@@ -257,9 +259,8 @@ function run_multicluster_e2e {
 
     wait_for_multicluster_controller_ready
 
-    docker pull "${DOCKER_REGISTRY}/antrea/nginx"
-    docker tag "${DOCKER_REGISTRY}/antrea/nginx:latest" "nginx:latest"
-    docker save nginx:latest -o "${WORKDIR}"/nginx.tar
+    docker pull $NGINX_IMAGE
+    docker save $NGINX_IMAGE -o "${WORKDIR}"/nginx.tar
 
     docker pull "${DOCKER_REGISTRY}/antrea/agnhost:2.26"
     docker tag "${DOCKER_REGISTRY}/antrea/agnhost:2.26" "agnhost:2.26"
diff --git a/ci/kind/test-e2e-kind.sh b/ci/kind/test-e2e-kind.sh
index 0341637bd..e6820860b 100755
--- a/ci/kind/test-e2e-kind.sh
+++ b/ci/kind/test-e2e-kind.sh
@@ -127,7 +127,7 @@ fi
 
 COMMON_IMAGES_LIST=("k8s.gcr.io/e2e-test-images/agnhost:2.29" \
                     "projects.registry.vmware.com/library/busybox"  \
-                    "projects.registry.vmware.com/antrea/nginx" \
+                    "projects.registry.vmware.com/antrea/nginx:1.21.6-alpine" \
                     "projects.registry.vmware.com/antrea/perftool" \
                     "projects.registry.vmware.com/antrea/ipfix-collector:v0.5.11" \
                     "projects.registry.vmware.com/antrea/wireguard-go:0.0.20210424")
diff --git a/multicluster/test/e2e/framework.go b/multicluster/test/e2e/framework.go
index 1e7065a3b..404a13621 100644
--- a/multicluster/test/e2e/framework.go
+++ b/multicluster/test/e2e/framework.go
@@ -60,7 +60,7 @@ const (
 
 	nameSuffixLength int = 8
 
-	nginxImage   = "nginx:latest"
+	nginxImage   = "projects.registry.vmware.com/antrea/nginx:1.21.6-alpine"
 	agnhostImage = "agnhost:2.26"
 )
 
diff --git a/pkg/agent/nodeportlocal/npl_agent_test.go b/pkg/agent/nodeportlocal/npl_agent_test.go
index 8d71e807b..c577e7e99 100644
--- a/pkg/agent/nodeportlocal/npl_agent_test.go
+++ b/pkg/agent/nodeportlocal/npl_agent_test.go
@@ -238,6 +238,7 @@ func setUp(t *testing.T, tc *testConfig, objects ...runtime.Object) *testData {
 		portTable: newPortTable(mockIPTables, mockPortOpener),
 	}
 
+	resyncPeriod := 0 * time.Minute
 	// informerFactory is initialized and started from cmd/antrea-agent/agent.go
 	informerFactory := informers.NewSharedInformerFactory(data.k8sClient, resyncPeriod)
 	listOptions := func(options *metav1.ListOptions) {
@@ -250,9 +251,9 @@ func setUp(t *testing.T, tc *testConfig, objects ...runtime.Object) *testData {
 		cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, // NamespaceIndex is used in NPLController.
 		listOptions,
 	)
+	svcInformer := informerFactory.Core().V1().Services().Informer()
 
-	c, err := InitController(data.k8sClient, informerFactory, data.portTable, defaultNodeName, localPodInformer)
-	require.NoError(t, err)
+	c := nplk8s.NewNPLController(data.k8sClient, localPodInformer, svcInformer, data.portTable, defaultNodeName)
 
 	data.runWrapper(c)
 	informerFactory.Start(data.stopCh)
diff --git a/test/e2e/framework.go b/test/e2e/framework.go
index 9c52d320a..7df6b4fe4 100644
--- a/test/e2e/framework.go
+++ b/test/e2e/framework.go
@@ -119,7 +119,7 @@ const (
 	busyboxImage        = "projects.registry.vmware.com/library/busybox"
 	mcjoinImage         = "projects.registry.vmware.com/antrea/mcjoin:v2.9"
 	netshootImage       = "projects.registry.vmware.com/antrea/netshoot:v0.1"
-	nginxImage          = "projects.registry.vmware.com/antrea/nginx"
+	nginxImage          = "projects.registry.vmware.com/antrea/nginx:1.21.6-alpine"
 	perftoolImage       = "projects.registry.vmware.com/antrea/perftool"
 	ipfixCollectorImage = "projects.registry.vmware.com/antrea/ipfix-collector:v0.5.11"
 	ipfixCollectorPort  = "4739"
diff --git a/test/e2e/infra/vagrant/playbook/roles/common/tasks/containerd.yml b/test/e2e/infra/vagrant/playbook/roles/common/tasks/containerd.yml
index 3c2bd51c7..a186583c5 100644
--- a/test/e2e/infra/vagrant/playbook/roles/common/tasks/containerd.yml
+++ b/test/e2e/infra/vagrant/playbook/roles/common/tasks/containerd.yml
@@ -61,3 +61,8 @@
     daemon_reload: yes
     state: restarted
   when: containerd_config.changed
+
+- name: Configure crictl
+  template:
+    src: templates/crictl.yaml.j2
+    dest: /etc/crictl.yaml
diff --git a/test/e2e/infra/vagrant/playbook/roles/common/templates/crictl.yaml.j2 b/test/e2e/infra/vagrant/playbook/roles/common/templates/crictl.yaml.j2
new file mode 100644
index 000000000..6612ed7e4
--- /dev/null
+++ b/test/e2e/infra/vagrant/playbook/roles/common/templates/crictl.yaml.j2
@@ -0,0 +1,2 @@
+runtime-endpoint: unix:///run/containerd/containerd.sock
+image-endpoint: unix:///run/containerd/containerd.sock