Repository: kubernetes-sigs/aws-alb-ingress-controller
Pull request #1528 (2020-10-21 02:32:44)

optimize the memory usage and fix bugs
## This PR contains below changes:
1.  Optimize the memory usage by using a in-memory podInfo repository instead of Informer. 
    * On large clusters with large number of pod and big podSpec size, the pod Informer could consume tons of memory, but is unnecessary from our controller's perspective.
    * we change to use a in-memory podInfo repository which only contains necessary podInformation we needed.
2. Fix a race condition when readinessGate enabled.
    * when we receive the endpoints events, some unready endpoints's corresponding pod might still don't have containerReady status, thus cannot be registered. However, since they have readinessGate, they cannot turn health either and won't trigger endpoint change to give itself chance to be registered. (this can be more easily occur if APIServer is slow)
    * this PR introduced a new return value called `containsPotentialReadyEndpoints` when resolve PodEndpoints, which means whether there are unready endpoints but could potentially turn ready if we re-do a reconcile.
    * In the future, we might evaluate to use pod events directly.
3. Change the `RequeueAfterError` and `RequeueError` to be `RequeueNeededAfter` and `RequeueNeeded` to better match its semantic.
     * they are not errors, we use a reason instead of error for these two structure.

## Test done
1. On a 200 node cluster with 4k pods, each pod with 124LB spec size, the memory usage reduced from 2GB to 100MB.
2. Tested the above race condition no longer occur when enabled podReadinessGate with a slow APIServer environment
3. log message for requeue: `{"level":"debug","ts":1603295614.2622936,"logger":"controllers.targetGroupBinding","msg":"requeue after","duration":15,"reason":"monitor targetHealth"}`

k8s-ci-robot: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by: *<a href="https://github.com/kubernetes-sigs/aws-alb-ingress-controller/pull/1528#" title="Author self-approved">M00nF1sh</a>*
To complete the [pull request process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process), please assign  after the PR has been reviewed.
You can assign the PR to them by writing `/assign ` in a comment when ready.

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=kubernetes-sigs%2Faws-alb-ingress-controller).

<details open>
Needs approval from an approver in each of these files:

- **[OWNERS](https://github.com/kubernetes-sigs/aws-alb-ingress-controller/blob/main/OWNERS)**

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
k8s-ci-robot: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by: *<a href="https://github.com/kubernetes-sigs/aws-alb-ingress-controller/pull/1528#pullrequestreview-513846898" title="Approved">kishorj</a>*, *<a href="https://github.com/kubernetes-sigs/aws-alb-ingress-controller/pull/1528#" title="Author self-approved">M00nF1sh</a>*
To complete the [pull request process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process), please assign  after the PR has been reviewed.
You can assign the PR to them by writing `/assign ` in a comment when ready.

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=kubernetes-sigs%2Faws-alb-ingress-controller).

<details open>
Needs approval from an approver in each of these files:

- **[OWNERS](https://github.com/kubernetes-sigs/aws-alb-ingress-controller/blob/main/OWNERS)**

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->

Patch:
diff --git a/main.go b/main.go
index b56878b75..eae89fccb 100644
--- a/main.go
+++ b/main.go
@@ -22,6 +22,7 @@ import (
 	"github.com/spf13/pflag"
 	zapraw "go.uber.org/zap"
 	k8sruntime "k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/client-go/kubernetes"
 	clientgoscheme "k8s.io/client-go/kubernetes/scheme"
 	_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
 	"os"
@@ -38,9 +39,9 @@ import (
 	"sigs.k8s.io/aws-load-balancer-controller/pkg/runtime"
 	"sigs.k8s.io/aws-load-balancer-controller/pkg/targetgroupbinding"
 	"sigs.k8s.io/aws-load-balancer-controller/pkg/version"
+	ctrl "sigs.k8s.io/controller-runtime"
 	corewebhook "sigs.k8s.io/aws-load-balancer-controller/webhooks/core"
 	elbv2webhook "sigs.k8s.io/aws-load-balancer-controller/webhooks/elbv2"
-	ctrl "sigs.k8s.io/controller-runtime"
 	"sigs.k8s.io/controller-runtime/pkg/log/zap"
 	"sigs.k8s.io/controller-runtime/pkg/metrics"
 	// +kubebuilder:scaffold:imports
@@ -88,7 +89,12 @@ func main() {
 		setupLog.Error(err, "unable to start manager")
 		os.Exit(1)
 	}
-
+	clientSet, err := kubernetes.NewForConfig(mgr.GetConfig())
+	if err != nil {
+		setupLog.Error(err, "unable to obtain clientSet")
+		os.Exit(1)
+	}
+	podInfoRepo := k8s.NewDefaultPodInfoRepo(clientSet.CoreV1().RESTClient(), rtOpts.Namespace, ctrl.Log)
 	finalizerManager := k8s.NewDefaultFinalizerManager(mgr.GetClient(), ctrl.Log)
 	podENIResolver := networking.NewDefaultPodENIInfoResolver(cloud.EC2(), cloud.VpcID(), ctrl.Log)
 	nodeENIResolver := networking.NewDefaultNodeENIInfoResolver(cloud.EC2(), ctrl.Log)
@@ -96,7 +102,7 @@ func main() {
 	sgReconciler := networking.NewDefaultSecurityGroupReconciler(sgManager, ctrl.Log)
 	subnetResolver := networking.NewSubnetsResolver(cloud.EC2(), cloud.VpcID(), controllerCFG.ClusterName, ctrl.Log.WithName("subnets-resolver"))
 	tgbResManager := targetgroupbinding.NewDefaultResourceManager(mgr.GetClient(), cloud.ELBV2(),
-		podENIResolver, nodeENIResolver, sgManager, sgReconciler, cloud.VpcID(), controllerCFG.ClusterName, ctrl.Log)
+		podInfoRepo, podENIResolver, nodeENIResolver, sgManager, sgReconciler, cloud.VpcID(), controllerCFG.ClusterName, ctrl.Log)
 
 	ingGroupReconciler := ingress.NewGroupReconciler(cloud, mgr.GetClient(), mgr.GetEventRecorderFor("ingress"),
 		finalizerManager, sgManager, sgReconciler, subnetResolver,
@@ -125,10 +131,21 @@ func main() {
 	corewebhook.NewPodMutator(podReadinessGateInjector).SetupWithManager(mgr)
 	elbv2webhook.NewTargetGroupBindingMutator(cloud.ELBV2(), ctrl.Log).SetupWithManager(mgr)
 	elbv2webhook.NewTargetGroupBindingValidator(ctrl.Log).SetupWithManager(mgr)
-	// +kubebuilder:scaffold:builder
-
-	setupLog.Info("starting manager")
-	if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
+	//+kubebuilder:scaffold:builder
+
+	stopChan := ctrl.SetupSignalHandler()
+	go func() {
+		setupLog.Info("starting podInfo repo")
+		if err := podInfoRepo.Start(stopChan); err != nil {
+			setupLog.Error(err, "problem running podInfo repo")
+			os.Exit(1)
+		}
+	}()
+	if err := podInfoRepo.WaitForCacheSync(stopChan); err != nil {
+		setupLog.Error(err, "problem wait for podInfo repo sync")
+		os.Exit(1)
+	}
+	if err := mgr.Start(stopChan); err != nil {
 		setupLog.Error(err, "problem running manager")
 		os.Exit(1)
 	}
diff --git a/mocks/k8s/mock_repo_info_repo.go b/mocks/k8s/mock_repo_info_repo.go
new file mode 100644
index 000000000..ed0249338
--- /dev/null
+++ b/mocks/k8s/mock_repo_info_repo.go
@@ -0,0 +1,66 @@
+// Code generated by MockGen. DO NOT EDIT.
+// Source: sigs.k8s.io/aws-load-balancer-controller/pkg/k8s (interfaces: PodInfoRepo)
+
+// Package mock_k8s is a generated GoMock package.
+package mock_k8s
+
+import (
+	context "context"
+	gomock "github.com/golang/mock/gomock"
+	types "k8s.io/apimachinery/pkg/types"
+	reflect "reflect"
+	k8s "sigs.k8s.io/aws-load-balancer-controller/pkg/k8s"
+)
+
+// MockPodInfoRepo is a mock of PodInfoRepo interface
+type MockPodInfoRepo struct {
+	ctrl     *gomock.Controller
+	recorder *MockPodInfoRepoMockRecorder
+}
+
+// MockPodInfoRepoMockRecorder is the mock recorder for MockPodInfoRepo
+type MockPodInfoRepoMockRecorder struct {
+	mock *MockPodInfoRepo
+}
+
+// NewMockPodInfoRepo creates a new mock instance
+func NewMockPodInfoRepo(ctrl *gomock.Controller) *MockPodInfoRepo {
+	mock := &MockPodInfoRepo{ctrl: ctrl}
+	mock.recorder = &MockPodInfoRepoMockRecorder{mock}
+	return mock
+}
+
+// EXPECT returns an object that allows the caller to indicate expected use
+func (m *MockPodInfoRepo) EXPECT() *MockPodInfoRepoMockRecorder {
+	return m.recorder
+}
+
+// Get mocks base method
+func (m *MockPodInfoRepo) Get(arg0 context.Context, arg1 types.NamespacedName) (k8s.PodInfo, bool, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "Get", arg0, arg1)
+	ret0, _ := ret[0].(k8s.PodInfo)
+	ret1, _ := ret[1].(bool)
+	ret2, _ := ret[2].(error)
+	return ret0, ret1, ret2
+}
+
+// Get indicates an expected call of Get
+func (mr *MockPodInfoRepoMockRecorder) Get(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockPodInfoRepo)(nil).Get), arg0, arg1)
+}
+
+// ListKeys mocks base method
+func (m *MockPodInfoRepo) ListKeys(arg0 context.Context) []types.NamespacedName {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "ListKeys", arg0)
+	ret0, _ := ret[0].([]types.NamespacedName)
+	return ret0
+}
+
+// ListKeys indicates an expected call of ListKeys
+func (mr *MockPodInfoRepoMockRecorder) ListKeys(arg0 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListKeys", reflect.TypeOf((*MockPodInfoRepo)(nil).ListKeys), arg0)
+}
diff --git a/pkg/backend/endpoint_resolver.go b/pkg/backend/endpoint_resolver.go
index a4e933a1c..de606cd52 100644
--- a/pkg/backend/endpoint_resolver.go
+++ b/pkg/backend/endpoint_resolver.go
@@ -5,27 +5,35 @@ import (
 	"github.com/go-logr/logr"
 	"github.com/pkg/errors"
 	corev1 "k8s.io/api/core/v1"
-	"k8s.io/apimachinery/pkg/labels"
 	"k8s.io/apimachinery/pkg/types"
 	"k8s.io/apimachinery/pkg/util/intstr"
 	"sigs.k8s.io/aws-load-balancer-controller/pkg/k8s"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 )
 
-// EndpointResolver resolves the endpoints for specific service & service Port.
+// TODO: for pod endpoints, we currently rely on endpoints events, we might change to use pod events directly in the future.
+// under current implementation with pod readinessGate enabled, an unready endpoint but not match our inclusionCriteria won't be registered,
+// and it won't turn ready due to blocked by readinessGate, and no future endpoint events will trigger.
+// We solve this by requeue the TGB if unready endpoints have the potential to be ready if reconcile in later time.
+
+//  EndpointResolver resolves the endpoints for specific service & service Port.
 type EndpointResolver interface {
 	// ResolvePodEndpoints will resolve endpoints backed by pods directly.
-	ResolvePodEndpoints(ctx context.Context, svcKey types.NamespacedName, port intstr.IntOrString, opts ...EndpointResolveOption) ([]PodEndpoint, error)
+	// returns resolved podEndpoints and whether there are unready endpoints that can potentially turn ready in future reconciles.
+	ResolvePodEndpoints(ctx context.Context, svcKey types.NamespacedName, port intstr.IntOrString,
+		opts ...EndpointResolveOption) ([]PodEndpoint, bool, error)
 
 	// ResolveNodePortEndpoints will resolve endpoints backed by nodePort.
-	ResolveNodePortEndpoints(ctx context.Context, svcKey types.NamespacedName, port intstr.IntOrString, opts ...EndpointResolveOption) ([]NodePortEndpoint, error)
+	ResolveNodePortEndpoints(ctx context.Context, svcKey types.NamespacedName, port intstr.IntOrString,
+		opts ...EndpointResolveOption) ([]NodePortEndpoint, error)
 }
 
 // NewDefaultEndpointResolver constructs new defaultEndpointResolver
-func NewDefaultEndpointResolver(k8sClient client.Client, logger logr.Logger) *defaultEndpointResolver {
+func NewDefaultEndpointResolver(k8sClient client.Client, podInfoRepo k8s.PodInfoRepo, logger logr.Logger) *defaultEndpointResolver {
 	return &defaultEndpointResolver{
-		k8sClient: k8sClient,
-		logger:    logger,
+		k8sClient:   k8sClient,
+		podInfoRepo: podInfoRepo,
+		logger:      logger,
 	}
 }
 
@@ -33,26 +41,27 @@ var _ EndpointResolver = &defaultEndpointResolver{}
 
 // default implementation for EndpointResolver
 type defaultEndpointResolver struct {
-	k8sClient client.Client
-	logger    logr.Logger
+	k8sClient   client.Client
+	podInfoRepo k8s.PodInfoRepo
+	logger      logr.Logger
 }
 
-func (r *defaultEndpointResolver) ResolvePodEndpoints(ctx context.Context, svcKey types.NamespacedName, port intstr.IntOrString, opts ...EndpointResolveOption) ([]PodEndpoint, error) {
-	resolveOpts := EndpointResolveOptions{
-		NodeSelector: labels.Nothing(),
-	}
+func (r *defaultEndpointResolver) ResolvePodEndpoints(ctx context.Context, svcKey types.NamespacedName, port intstr.IntOrString,
+	opts ...EndpointResolveOption) ([]PodEndpoint, bool, error) {
+	resolveOpts := defaultEndpointResolveOptions()
 	resolveOpts.ApplyOptions(opts)
+
 	svc, svcPort, err := r.findServiceAndServicePort(ctx, svcKey, port)
 	if err != nil {
-		return nil, err
+		return nil, false, err
 	}
-
 	epsKey := k8s.NamespacedName(svc) // k8s Endpoints have same name as k8s Service
 	eps := &corev1.Endpoints{}
 	if err := r.k8sClient.Get(ctx, epsKey, eps); err != nil {
-		return nil, err
+		return nil, false, err
 	}
 
+	containsPotentialReadyEndpoints := false
 	var endpoints []PodEndpoint
 	for _, epSubset := range eps.Subsets {
 		for _, epPort := range epSubset.Ports {
@@ -65,43 +74,53 @@ func (r *defaultEndpointResolver) ResolvePodEndpoints(ctx context.Context, svcKe
 				if epAddr.TargetRef == nil || epAddr.TargetRef.Kind != "Pod" {
 					continue
 				}
-				epPod, err := r.findPodByReference(ctx, svc.Namespace, *epAddr.TargetRef)
+				pod, exists, err := r.findPodByReference(ctx, svc.Namespace, *epAddr.TargetRef)
 				if err != nil {
-					return nil, err
+					return nil, false, err
 				}
-				endpoints = append(endpoints, buildPodEndpoint(epPod, epAddr, epPort))
+				if !exists {
+					return nil, false, errors.New("couldn't find podInfo for ready endpoint")
+				}
+				endpoints = append(endpoints, buildPodEndpoint(pod, epAddr, epPort))
 			}
 
-			if len(resolveOpts.UnreadyPodInclusionCriteria) != 0 {
+			if len(resolveOpts.PodReadinessGates) != 0 {
 				for _, epAddr := range epSubset.NotReadyAddresses {
 					if epAddr.TargetRef == nil || epAddr.TargetRef.Kind != "Pod" {
 						continue
 					}
-					epPod, err := r.findPodByReference(ctx, svc.Namespace, *epAddr.TargetRef)
+					pod, exists, err := r.findPodByReference(ctx, svc.Namespace, *epAddr.TargetRef)
 					if err != nil {
-						return nil, err
+						return nil, false, err
+					}
+					if !exists {
+						containsPotentialReadyEndpoints = true
+						continue
+					}
+					if !pod.HasAnyOfReadinessGates(resolveOpts.PodReadinessGates) {
+						continue
 					}
-					if isPodMeetCriteria(epPod, resolveOpts.UnreadyPodInclusionCriteria) {
-						endpoints = append(endpoints, buildPodEndpoint(epPod, epAddr, epPort))
+					if !pod.IsContainersReady() {
+						containsPotentialReadyEndpoints = true
+						continue
 					}
+					endpoints = append(endpoints, buildPodEndpoint(pod, epAddr, epPort))
 				}
 			}
 		}
 	}
 
-	return endpoints, nil
+	return endpoints, containsPotentialReadyEndpoints, nil
 }
 
 func (r *defaultEndpointResolver) ResolveNodePortEndpoints(ctx context.Context, svcKey types.NamespacedName, port intstr.IntOrString, opts ...EndpointResolveOption) ([]NodePortEndpoint, error) {
-	resolveOpts := EndpointResolveOptions{
-		NodeSelector: labels.Nothing(),
-	}
+	resolveOpts := defaultEndpointResolveOptions()
 	resolveOpts.ApplyOptions(opts)
+
 	svc, svcPort, err := r.findServiceAndServicePort(ctx, svcKey, port)
 	if err != nil {
 		return nil, err
 	}
-
 	if svc.Spec.Type != corev1.ServiceTypeNodePort && svc.Spec.Type != corev1.ServiceTypeLoadBalancer {
 		return nil, errors.Errorf("service type must be either 'NodePort' or 'LoadBalancer': %v", svcKey)
 	}
@@ -140,25 +159,12 @@ func (r *defaultEndpointResolver) findServiceAndServicePort(ctx context.Context,
 	return svc, svcPort, nil
 }
 
-func (r *defaultEndpointResolver) findPodByReference(ctx context.Context, namespace string, podRef corev1.ObjectReference) (*corev1.Pod, error) {
-	pod := &corev1.Pod{}
+func (r *defaultEndpointResolver) findPodByReference(ctx context.Context, namespace string, podRef corev1.ObjectReference) (k8s.PodInfo, bool, error) {
 	podKey := types.NamespacedName{Namespace: namespace, Name: podRef.Name}
-	if err := r.k8sClient.Get(ctx, podKey, pod); err != nil {
-		return nil, err
-	}
-	return pod, nil
-}
-
-func isPodMeetCriteria(pod *corev1.Pod, criteria []PodPredicate) bool {
-	for _, criterion := range criteria {
-		if !criterion(pod) {
-			return false
-		}
-	}
-	return true
+	return r.podInfoRepo.Get(ctx, podKey)
 }
 
-func buildPodEndpoint(pod *corev1.Pod, epAddr corev1.EndpointAddress, epPort corev1.EndpointPort) PodEndpoint {
+func buildPodEndpoint(pod k8s.PodInfo, epAddr corev1.EndpointAddress, epPort corev1.EndpointPort) PodEndpoint {
 	return PodEndpoint{
 		IP:   epAddr.IP,
 		Port: int64(epPort.Port),
diff --git a/pkg/backend/endpoint_types.go b/pkg/backend/endpoint_types.go
index b561e3537..ee8d58716 100644
--- a/pkg/backend/endpoint_types.go
+++ b/pkg/backend/endpoint_types.go
@@ -3,6 +3,7 @@ package backend
 import (
 	corev1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/labels"
+	"sigs.k8s.io/aws-load-balancer-controller/pkg/k8s"
 )
 
 // An endpoint provided by pod directly.
@@ -12,10 +13,10 @@ type PodEndpoint struct {
 	// Pod's container port.
 	Port int64
 	// Pod that provides this endpoint.
-	Pod *corev1.Pod
+	Pod k8s.PodInfo
 }
 
-// An endpoint provided by nodePort as trafficProxy.
+// An endpoint provided by nodePort as traffic proxy.
 type NodePortEndpoint struct {
 	// Node's instanceID.
 	InstanceID string
@@ -25,19 +26,15 @@ type NodePortEndpoint struct {
 	Node *corev1.Node
 }
 
-type PodPredicate func(pod *corev1.Pod) bool
-
 // options for Endpoints resolve APIs
 type EndpointResolveOptions struct {
-	// [Pod Endpoint] unready pods that have passed all these inclusion criteria will be included as well. (ready pods will always be included)
-	// If it's empty, unready pods won't be included.
-	// If it's non-empty, unready pods passed *all* criteria will be included.
-	// By default, it's empty.
-	UnreadyPodInclusionCriteria []PodPredicate
-
 	// [NodePort Endpoint] only nodes that are ready and matched by nodeSelector will be included.
 	// By default, no node will be selected.
 	NodeSelector labels.Selector
+
+	// [Pod Endpoint] If pod readinessGates is defined, then pods from unready addresses with any of these readinessGates and containersReady condition will be included as well.
+	// By default, no readinessGate is specified.
+	PodReadinessGates []corev1.PodConditionType
 }
 
 func (opts *EndpointResolveOptions) ApplyOptions(options []EndpointResolveOption) {
@@ -55,9 +52,17 @@ func WithNodeSelector(nodeSelector labels.Selector) EndpointResolveOption {
 	}
 }
 
-// WithUnreadyPodInclusionCriterion is a option that appends additional criterion into UnreadyPodInclusionCriteria.
-func WithUnreadyPodInclusionCriterion(predicate PodPredicate) EndpointResolveOption {
+// WithPodReadinessGate is a option that appends podReadinessGate into EndpointResolveOptions.
+func WithPodReadinessGate(cond corev1.PodConditionType) EndpointResolveOption {
 	return func(opts *EndpointResolveOptions) {
-		opts.UnreadyPodInclusionCriteria = append(opts.UnreadyPodInclusionCriteria, predicate)
+		opts.PodReadinessGates = append(opts.PodReadinessGates, cond)
+	}
+}
+
+// defaultEndpointResolveOptions returns the default value for EndpointResolveOptions.
+func defaultEndpointResolveOptions() EndpointResolveOptions {
+	return EndpointResolveOptions{
+		NodeSelector:      labels.Nothing(),
+		PodReadinessGates: nil,
 	}
 }
diff --git a/pkg/ingress/reference_indexer.go b/pkg/ingress/reference_indexer.go
index 9d47829da..94ed829d1 100644
--- a/pkg/ingress/reference_indexer.go
+++ b/pkg/ingress/reference_indexer.go
@@ -2,7 +2,6 @@ package ingress
 
 import (
 	"context"
-	"fmt"
 	"github.com/go-logr/logr"
 	networking "k8s.io/api/networking/v1beta1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -60,7 +59,6 @@ func (i *defaultReferenceIndexer) BuildServiceRefIndexes(ctx context.Context, in
 		if err != nil {
 			i.logger.Error(err, "failed to build Ingress indexes",
 				"indexKey", IndexKeyServiceRefName)
-			fmt.Println(err)
 			return nil
 		}
 		serviceNamesFromBackend := extractServiceNamesFromAction(enhancedBackend.Action)
diff --git a/pkg/k8s/conversion_store.go b/pkg/k8s/conversion_store.go
new file mode 100644
index 000000000..34deeb431
--- /dev/null
+++ b/pkg/k8s/conversion_store.go
@@ -0,0 +1,90 @@
+package k8s
+
+import (
+	"k8s.io/client-go/tools/cache"
+)
+
+type ConversionFunc func(obj interface{}) (interface{}, error)
+
+// NewConversionStore constructs new conversionStore
+func NewConversionStore(conversionFunc ConversionFunc, keyFunc cache.KeyFunc) *ConversionStore {
+	return &ConversionStore{
+		conversionFunc: conversionFunc,
+		store:          cache.NewStore(keyFunc),
+	}
+}
+
+var _ cache.Store = &ConversionStore{}
+
+// ConversionStore implements cache.Store.
+// It converts objects to another type using specified conversionFunc.
+// In store's term, the accumulator for this store is a convert that converts object's latest state into another object.
+type ConversionStore struct {
+	conversionFunc ConversionFunc
+	store          cache.Store
+}
+
+// Add adds the given object to the accumulator associated with the given object's key
+func (s *ConversionStore) Add(obj interface{}) error {
+	converted, err := s.conversionFunc(obj)
+	if err != nil {
+		return err
+	}
+	return s.store.Add(converted)
+}
+
+// Update updates the given object in the accumulator associated with the given object's key
+func (s *ConversionStore) Update(obj interface{}) error {
+	converted, err := s.conversionFunc(obj)
+	if err != nil {
+		return err
+	}
+	return s.store.Update(converted)
+}
+
+// Delete deletes the given object from the accumulator associated with the given object's key
+func (s *ConversionStore) Delete(obj interface{}) error {
+	converted, err := s.conversionFunc(obj)
+	if err != nil {
+		return err
+	}
+	return s.store.Delete(converted)
+}
+
+// Replace will delete the contents of the store, using instead the given list.
+func (s *ConversionStore) Replace(list []interface{}, resourceVersion string) error {
+	items := make([]interface{}, 0, len(list))
+	for _, item := range list {
+		converted, err := s.conversionFunc(item)
+		if err != nil {
+			return err
+		}
+		items = append(items, converted)
+	}
+	return s.store.Replace(items, resourceVersion)
+}
+
+// Resync is meaningless in the terms appearing here
+func (s *ConversionStore) Resync() error {
+	return s.store.Resync()
+}
+
+// List returns a list of all the currently non-empty accumulators
+func (s *ConversionStore) List() []interface{} {
+	return s.store.List()
+}
+
+// ListKeys returns a list of all the keys currently associated with non-empty accumulators
+func (s *ConversionStore) ListKeys() []string {
+	return s.store.ListKeys()
+}
+
+// Get returns the accumulator associated with the given object's key
+func (s *ConversionStore) Get(obj interface{}) (item interface{}, exists bool, err error) {
+	return s.store.Get(obj)
+}
+
+// GetByKey returns the accumulator associated with the given key
+func (s *ConversionStore) GetByKey(key string) (item interface{}, exists bool, err error) {
+	return s.store.GetByKey(key)
+}
diff --git a/pkg/k8s/pod_info.go b/pkg/k8s/pod_info.go
new file mode 100644
index 000000000..68959bbfd
--- /dev/null
+++ b/pkg/k8s/pod_info.go
@@ -0,0 +1,120 @@
+package k8s
+
+import (
+	"encoding/json"
+	"github.com/pkg/errors"
+	corev1 "k8s.io/api/core/v1"
+	"k8s.io/apimachinery/pkg/types"
+	"k8s.io/apimachinery/pkg/util/intstr"
+)
+
+const (
+	annotationKeyPodENIInfo = "vpc.amazonaws.com/pod-eni"
+)
+
+// PodInfo contains simplified pod information we cares about.
+// We do so to minimize memory usage.
+type PodInfo struct {
+	Key types.NamespacedName
+	UID types.UID
+
+	ContainerPorts []corev1.ContainerPort
+	ReadinessGates []corev1.PodReadinessGate
+	Conditions     []corev1.PodCondition
+	PodIP          string
+
+	ENIInfo *PodENIInfo
+}
+
+// PodENIInfo is a json convertible structure that stores the Branch ENI details that can be
+// used by the CNI plugin or the component consuming the resource
+type PodENIInfo struct {
+	// ENIID is the network interface id of the branch interface
+	ENIID string `json:"eniId"`
+}
+
+// HasAnyOfReadinessGates returns whether podInfo has any of these readinessGates
+func (i *PodInfo) HasAnyOfReadinessGates(conditionTypes []corev1.PodConditionType) bool {
+	for _, rg := range i.ReadinessGates {
+		for _, conditionType := range conditionTypes {
+			if rg.ConditionType == conditionType {
+				return true
+			}
+		}
+	}
+	return false
+}
+
+// IsContainersReady returns whether podInfo is ContainersReady.
+func (i *PodInfo) IsContainersReady() bool {
+	containersReadyCond, exists := i.GetPodCondition(corev1.ContainersReady)
+	return exists && containersReadyCond.Status == corev1.ConditionTrue
+}
+
+// GetPodCondition will get Pod's condition.
+func (i *PodInfo) GetPodCondition(conditionType corev1.PodConditionType) (corev1.PodCondition, bool) {
+	for _, cond := range i.Conditions {
+		if cond.Type == conditionType {
+			return cond, true
+		}
+	}
+
+	return corev1.PodCondition{}, false
+}
+
+// LookupContainerPort returns the numerical containerPort for specific port on Pod.
+func (i *PodInfo) LookupContainerPort(port intstr.IntOrString) (int64, error) {
+	switch port.Type {
+	case intstr.String:
+		for _, podPort := range i.ContainerPorts {
+			if podPort.Name == port.StrVal {
+				return int64(podPort.ContainerPort), nil
+			}
+		}
+	case intstr.Int:
+		return int64(port.IntVal), nil
+	}
+	return 0, errors.Errorf("unable to find port %s on pod %s", port.String(), i.Key)
+}
+
+// buildPodInfo will construct PodInfo for given pod.
+func buildPodInfo(pod *corev1.Pod) PodInfo {
+	podKey := NamespacedName(pod)
+
+	var podENIInfo *PodENIInfo
+	// we kept podENIInfo as nil if the eniInfo via annotation is malformed.
+	if eniInfo, err := buildPodENIInfo(pod); err == nil {
+		podENIInfo = eniInfo
+	}
+
+	var containerPorts []corev1.ContainerPort
+	for _, podContainer := range pod.Spec.Containers {
+		containerPorts = append(containerPorts, podContainer.Ports...)
+	}
+	return PodInfo{
+		Key: podKey,
+		UID: pod.UID,
+
+		ContainerPorts: containerPorts,
+		ReadinessGates: pod.Spec.ReadinessGates,
+		Conditions:     pod.Status.Conditions,
+		PodIP:          pod.Status.PodIP,
+
+		ENIInfo: podENIInfo,
+	}
+}
+
+// buildPodENIInfo will construct PodENIInfo for given pod if any.
+func buildPodENIInfo(pod *corev1.Pod) (*PodENIInfo, error) {
+	rawAnnotation, ok := pod.Annotations[annotationKeyPodENIInfo]
+	if !ok {
+		return nil, nil
+	}
+
+	var podENIInfo PodENIInfo
+	if err := json.Unmarshal([]byte(rawAnnotation), &podENIInfo); err != nil {
+		return nil, err
+	}
+
+	return &podENIInfo, nil
+}
diff --git a/pkg/k8s/pod_info_repo.go b/pkg/k8s/pod_info_repo.go
new file mode 100644
index 000000000..e096d69eb
--- /dev/null
+++ b/pkg/k8s/pod_info_repo.go
@@ -0,0 +1,119 @@
+package k8s
+
+import (
+	"context"
+	"errors"
+	"github.com/go-logr/logr"
+	corev1 "k8s.io/api/core/v1"
+	"k8s.io/apimachinery/pkg/fields"
+	"k8s.io/apimachinery/pkg/types"
+	"k8s.io/apimachinery/pkg/util/wait"
+	"k8s.io/client-go/tools/cache"
+	"sigs.k8s.io/controller-runtime/pkg/manager"
+	"time"
+)
+
+const (
+	resourceTypePods        = "pods"
+	waitCacheSyncPollPeriod = 2 * time.Second
+)
+
+// PodInfoRepo provides access to pod information within cluster.
+// We only store necessary pod information related to our controller to reduce memory usage.
+type PodInfoRepo interface {
+	// Get returns PodInfo specified with specific podKey, and whether it exists.
+	Get(ctx context.Context, key types.NamespacedName) (PodInfo, bool, error)
+
+	// ListKeys will list the pod keys in this repo.
+	ListKeys(ctx context.Context) []types.NamespacedName
+}
+
+// NewDefaultPodInfoRepo constructs new defaultPodInfoRepo.
+// * watchNamespace is the namespace to monitor pod spec.
+// 		* if watchNamespace is "", this repo monitors pods in all namespaces
+// 		* if watchNamespace is not "", this repo monitors pods in specific namespace
+func NewDefaultPodInfoRepo(getter cache.Getter, watchNamespace string, logger logr.Logger) *defaultPodInfoRepo {
+	store := NewConversionStore(podInfoConversionFunc, podInfoKeyFunc)
+	lw := cache.NewListWatchFromClient(getter, resourceTypePods, watchNamespace, fields.Everything())
+	rt := cache.NewReflector(lw, &corev1.Pod{}, store, 0)
+
+	repo := &defaultPodInfoRepo{
+		store:  store,
+		rt:     rt,
+		logger: logger,
+	}
+	return repo
+}
+
+var _ PodInfoRepo = &defaultPodInfoRepo{}
+var _ manager.Runnable = &defaultPodInfoRepo{}
+
+// default implementation for PodInfoRepo
+type defaultPodInfoRepo struct {
+	store  *ConversionStore
+	rt     *cache.Reflector
+	logger logr.Logger
+}
+
+// Get returns PodInfo specified with specific podKey, and whether it exists.
+func (r *defaultPodInfoRepo) Get(_ context.Context, key types.NamespacedName) (PodInfo, bool, error) {
+	pInfo := PodInfo{Key: key}
+	raw, exists, err := r.store.Get(&pInfo)
+	if err != nil {
+		return PodInfo{}, false, err
+	}
+	if !exists {
+		return PodInfo{}, false, nil
+	}
+	pInfo = *raw.(*PodInfo)
+	return pInfo, true, nil
+}
+
+// ListKeys will list the pod keys in this repo.
+func (r *defaultPodInfoRepo) ListKeys(_ context.Context) []types.NamespacedName {
+	storeKeys := r.store.ListKeys()
+	keys := make([]types.NamespacedName, 0, len(storeKeys))
+	for _, storeKey := range storeKeys {
+		namespace, name, _ := cache.SplitMetaNamespaceKey(storeKey)
+		key := types.NamespacedName{
+			Namespace: namespace,
+			Name:      name,
+		}
+		keys = append(keys, key)
+	}
+	return keys
+}
+
+// Start will start the repo.
+// It leverages ListWatch to keep pod info stored locally to be in-sync with Kubernetes.
+func (r *defaultPodInfoRepo) Start(stopChan <-chan struct{}) error {
+	r.rt.Run(stopChan)
+	return nil
+}
+
+// WaitForCacheSync waits for the initial sync of pod information repository.
+func (r *defaultPodInfoRepo) WaitForCacheSync(stopCh <-chan struct{}) error {
+	return wait.PollImmediateUntil(waitCacheSyncPollPeriod, func() (bool, error) {
+		lastSyncResourceVersion := r.rt.LastSyncResourceVersion()
+		return lastSyncResourceVersion != "", nil
+	}, stopCh)
+}
+
+// podInfoKeyFunc computes the store key per PodInfo object.
+func podInfoKeyFunc(obj interface{}) (string, error) {
+	info, ok := obj.(*PodInfo)
+	if !ok {
+		return "", errors.New("expect PodInfo object")
+	}
+	return info.Key.String(), nil
+}
+
+// podInfoConversionFunc computes the converted PodInfo per pod object.
+func podInfoConversionFunc(obj interface{}) (interface{}, error) {
+	pod, ok := obj.(*corev1.Pod)
+	if !ok {
+		return nil, errors.New("expect pod object")
+	}
+	podInfo := buildPodInfo(pod)
+	return &podInfo, nil
+}
diff --git a/pkg/networking/node_eni_info_resolver.go b/pkg/networking/node_eni_info_resolver.go
index a871eb798..622e657b3 100644
--- a/pkg/networking/node_eni_info_resolver.go
+++ b/pkg/networking/node_eni_info_resolver.go
@@ -52,22 +52,22 @@ type defaultNodeENIInfoResolver struct {
 
 func (r *defaultNodeENIInfoResolver) Resolve(ctx context.Context, nodes []*corev1.Node) (map[types.NamespacedName]ENIInfo, error) {
 	eniInfoByNodeKey := r.fetchENIInfosFromCache(nodes)
-	unresolvedNodes := computeNodesWithUnresolvedENIInfo(nodes, eniInfoByNodeKey)
-	eniInfoByNodeKeyViaLookup, err := r.resolveViaInstanceID(ctx, unresolvedNodes)
+	nodesWithoutENIInfo := computeNodesWithoutENIInfo(nodes, eniInfoByNodeKey)
+	eniInfoByNodeKeyViaLookup, err := r.resolveViaInstanceID(ctx, nodesWithoutENIInfo)
 	if err != nil {
 		return nil, err
 	}
 	if len(eniInfoByNodeKeyViaLookup) > 0 {
-		r.saveENIInfosToCache(unresolvedNodes, eniInfoByNodeKeyViaLookup)
+		r.saveENIInfosToCache(nodesWithoutENIInfo, eniInfoByNodeKeyViaLookup)
 		for nodeKey, eniInfo := range eniInfoByNodeKeyViaLookup {
 			eniInfoByNodeKey[nodeKey] = eniInfo
 		}
-		unresolvedNodes = computeNodesWithUnresolvedENIInfo(unresolvedNodes, eniInfoByNodeKeyViaLookup)
+		nodesWithoutENIInfo = computeNodesWithoutENIInfo(nodesWithoutENIInfo, eniInfoByNodeKeyViaLookup)
 	}
 
-	if len(unresolvedNodes) > 0 {
-		unresolvedNodeKeys := make([]types.NamespacedName, 0, len(unresolvedNodes))
-		for _, node := range unresolvedNodes {
+	if len(nodesWithoutENIInfo) > 0 {
+		unresolvedNodeKeys := make([]types.NamespacedName, 0, len(nodesWithoutENIInfo))
+		for _, node := range nodesWithoutENIInfo {
 			unresolvedNodeKeys = append(unresolvedNodeKeys, k8s.NamespacedName(node))
 		}
 		return nil, errors.Errorf("cannot resolve node ENI for nodes: %v", unresolvedNodeKeys)
@@ -80,7 +80,7 @@ type nodeENIInfoCacheKey struct {
 	nodeKey types.NamespacedName
 	// Node's UID.
 	// Note: we assume node's eni haven't changed as long as node UID is same.
-	nodeUID string
+	nodeUID types.UID
 }
 
 func (r *defaultNodeENIInfoResolver) fetchENIInfosFromCache(nodes []*corev1.Node) map[types.NamespacedName]ENIInfo {
@@ -164,12 +164,12 @@ func findInstancePrimaryENI(enis []*ec2sdk.InstanceNetworkInterface) (*ec2sdk.In
 func computeNodeENIInfoCacheKey(node *corev1.Node) nodeENIInfoCacheKey {
 	return nodeENIInfoCacheKey{
 		nodeKey: k8s.NamespacedName(node),
-		nodeUID: string(node.UID),
+		nodeUID: node.UID,
 	}
 }
 
-// computeNodesWithUnresolvedENIInfo computes nodes that don't have resolvedENIInfo.
-func computeNodesWithUnresolvedENIInfo(nodes []*corev1.Node, eniInfoByNodeKey map[types.NamespacedName]ENIInfo) []*corev1.Node {
+// computeNodesWithoutENIInfo computes nodes that don't have resolvedENIInfo.
+func computeNodesWithoutENIInfo(nodes []*corev1.Node, eniInfoByNodeKey map[types.NamespacedName]ENIInfo) []*corev1.Node {
 	unresolvedNodes := make([]*corev1.Node, 0, len(nodes)-len(eniInfoByNodeKey))
 	for _, node := range nodes {
 		nodeKey := k8s.NamespacedName(node)
diff --git a/pkg/networking/pod_eni_info_resolver.go b/pkg/networking/pod_eni_info_resolver.go
index 4332e144a..aad58e47e 100644
--- a/pkg/networking/pod_eni_info_resolver.go
+++ b/pkg/networking/pod_eni_info_resolver.go
@@ -2,12 +2,10 @@ package networking
 
 import (
 	"context"
-	"encoding/json"
 	awssdk "github.com/aws/aws-sdk-go/aws"
 	ec2sdk "github.com/aws/aws-sdk-go/service/ec2"
 	"github.com/go-logr/logr"
 	"github.com/pkg/errors"
-	corev1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/types"
 	"k8s.io/apimachinery/pkg/util/cache"
 	"k8s.io/apimachinery/pkg/util/sets"
@@ -19,8 +17,6 @@ import (
 )
 
 const (
-	annotationPodENI = "vpc.amazonaws.com/pod-eni"
-
 	defaultPodENIInfoCacheTTL = 10 * time.Minute
 	// EC2:DescribeNetworkInterface supports up to 200 filters per call.
 	describeNetworkInterfacesFiltersLimit = 200
@@ -29,7 +25,7 @@ const (
 // PodENIInfoResolver is responsible for resolve the AWS VPC ENI that supports pod network.
 type PodENIInfoResolver interface {
 	// Resolve resolves eniInfo for pods.
-	Resolve(ctx context.Context, pods []*corev1.Pod) (map[types.NamespacedName]ENIInfo, error)
+	Resolve(ctx context.Context, pods []k8s.PodInfo) (map[types.NamespacedName]ENIInfo, error)
 }
 
 // NewDefaultPodENIResolver constructs new defaultPodENIResolver.
@@ -68,28 +64,27 @@ type defaultPodENIInfoResolver struct {
 	describeNetworkInterfacesIPChunkSize int
 }
 
-func (r *defaultPodENIInfoResolver) Resolve(ctx context.Context, pods []*corev1.Pod) (map[types.NamespacedName]ENIInfo, error) {
+func (r *defaultPodENIInfoResolver) Resolve(ctx context.Context, pods []k8s.PodInfo) (map[types.NamespacedName]ENIInfo, error) {
 	eniInfoByPodKey := r.fetchENIInfosFromCache(pods)
-
-	unresolvedPods := computePodsWithUnresolvedENIInfo(pods, eniInfoByPodKey)
-	eniInfoByPodKeyViaLookup, err := r.resolveViaCascadedLookup(ctx, unresolvedPods)
+	podsWithoutENIInfo := computePodsWithoutENIInfo(pods, eniInfoByPodKey)
+	eniInfoByPodKeyViaLookup, err := r.resolveViaCascadedLookup(ctx, podsWithoutENIInfo)
 	if err != nil {
 		return nil, err
 	}
 	if len(eniInfoByPodKeyViaLookup) > 0 {
-		r.saveENIInfosToCache(unresolvedPods, eniInfoByPodKeyViaLookup)
+		r.saveENIInfosToCache(podsWithoutENIInfo, eniInfoByPodKeyViaLookup)
 		for podKey, eniInfo := range eniInfoByPodKeyViaLookup {
 			eniInfoByPodKey[podKey] = eniInfo
 		}
-		unresolvedPods = computePodsWithUnresolvedENIInfo(unresolvedPods, eniInfoByPodKeyViaLookup)
+		podsWithoutENIInfo = computePodsWithoutENIInfo(podsWithoutENIInfo, eniInfoByPodKeyViaLookup)
 	}
 
-	if len(unresolvedPods) > 0 {
-		unresolvedPodKeys := make([]types.NamespacedName, 0, len(unresolvedPods))
-		for _, pod := range unresolvedPods {
-			unresolvedPodKeys = append(unresolvedPodKeys, k8s.NamespacedName(pod))
+	if len(podsWithoutENIInfo) > 0 {
+		podKeysWithoutENIInfo := make([]types.NamespacedName, 0, len(podsWithoutENIInfo))
+		for _, pod := range podsWithoutENIInfo {
+			podKeysWithoutENIInfo = append(podKeysWithoutENIInfo, pod.Key)
 		}
-		return nil, errors.Errorf("cannot resolve pod ENI for pods: %v", unresolvedPodKeys)
+		return nil, errors.Errorf("cannot resolve pod ENI for pods: %v", podKeysWithoutENIInfo)
 	}
 	return eniInfoByPodKey, nil
 }
@@ -99,10 +94,10 @@ type podENIInfoCacheKey struct {
 	podKey types.NamespacedName
 	// Pod's UID.
 	// Note: we assume pod's eni haven't changed as long as pod UID is same.
-	podUID string
+	podUID types.UID
 }
 
-func (r *defaultPodENIInfoResolver) fetchENIInfosFromCache(pods []*corev1.Pod) map[types.NamespacedName]ENIInfo {
+func (r *defaultPodENIInfoResolver) fetchENIInfosFromCache(pods []k8s.PodInfo) map[types.NamespacedName]ENIInfo {
 	r.podENIInfoCacheMutex.RLock()
 	defer r.podENIInfoCacheMutex.RUnlock()
 
@@ -111,19 +106,19 @@ func (r *defaultPodENIInfoResolver) fetchENIInfosFromCache(pods []*corev1.Pod) m
 		cacheKey := computePodENIInfoCacheKey(pod)
 		if rawCacheItem, exists := r.podENIInfoCache.Get(cacheKey); exists {
 			eniInfo := rawCacheItem.(ENIInfo)
-			podKey := k8s.NamespacedName(pod)
+			podKey := pod.Key
 			eniInfoByPodKey[podKey] = eniInfo
 		}
 	}
 	return eniInfoByPodKey
 }
 
-func (r *defaultPodENIInfoResolver) saveENIInfosToCache(pods []*corev1.Pod, eniInfoByPodKey map[types.NamespacedName]ENIInfo) {
+func (r *defaultPodENIInfoResolver) saveENIInfosToCache(pods []k8s.PodInfo, eniInfoByPodKey map[types.NamespacedName]ENIInfo) {
 	r.podENIInfoCacheMutex.Lock()
 	defer r.podENIInfoCacheMutex.Unlock()
 
 	for _, pod := range pods {
-		podKey := k8s.NamespacedName(pod)
+		podKey := pod.Key
 		if eniInfo, exists := eniInfoByPodKey[podKey]; exists {
 			cacheKey := computePodENIInfoCacheKey(pod)
 			r.podENIInfoCache.Set(cacheKey, eniInfo, r.podENIInfoCacheTTL)
@@ -131,8 +126,8 @@ func (r *defaultPodENIInfoResolver) saveENIInfosToCache(pods []*corev1.Pod, eniI
 	}
 }
 
-func (r *defaultPodENIInfoResolver) resolveViaCascadedLookup(ctx context.Context, pods []*corev1.Pod) (map[types.NamespacedName]ENIInfo, error) {
-	resolveFuncs := []func(ctx context.Context, pods []*corev1.Pod) (map[types.NamespacedName]ENIInfo, error){
+func (r *defaultPodENIInfoResolver) resolveViaCascadedLookup(ctx context.Context, pods []k8s.PodInfo) (map[types.NamespacedName]ENIInfo, error) {
+	resolveFuncs := []func(ctx context.Context, pods []k8s.PodInfo) (map[types.NamespacedName]ENIInfo, error){
 		r.resolveViaPodENIAnnotation,
 		r.resolveViaVPCIPAddress,
 		// TODO, add support for kubenet CNI plugin(kops) by resolve via routeTable.
@@ -143,6 +138,7 @@ func (r *defaultPodENIInfoResolver) resolveViaCascadedLookup(ctx context.Context
 		if len(pods) == 0 {
 			break
 		}
+
 		resolvedENIInfoByPodKey, err := resolveFunc(ctx, pods)
 		if err != nil {
 			return nil, err
@@ -150,36 +146,22 @@ func (r *defaultPodENIInfoResolver) resolveViaCascadedLookup(ctx context.Context
 		for podKey, eniInfo := range resolvedENIInfoByPodKey {
 			eniInfoByPodKey[podKey] = eniInfo
 		}
-		pods = computePodsWithUnresolvedENIInfo(pods, resolvedENIInfoByPodKey)
+		pods = computePodsWithoutENIInfo(pods, resolvedENIInfoByPodKey)
 	}
 	return eniInfoByPodKey, nil
 }
 
-// annotationSchemaPodENI is a json convertible structure that stores the Branch ENI details that can be
-// used by the CNI plugin or the component consuming the resource
-type annotationSchemaPodENI struct {
-	// ENIID is the network interface id of the branch interface
-	ENIID string `json:"eniId"`
-	// PrivateIP is the primary IP of the branch Network interface
-	PrivateIP string `json:"privateIp"`
-	// SubnetCIDR is the CIDR block of the subnet
-	SubnetCIDR string `json:"subnetCidr"`
-}
-
 // resolveViaPodENIAnnotation tries to resolve a pod ENI via the branch ENI annotation.
-func (r *defaultPodENIInfoResolver) resolveViaPodENIAnnotation(ctx context.Context, pods []*corev1.Pod) (map[types.NamespacedName]ENIInfo, error) {
+func (r *defaultPodENIInfoResolver) resolveViaPodENIAnnotation(ctx context.Context, pods []k8s.PodInfo) (map[types.NamespacedName]ENIInfo, error) {
 	podKeysByENIID := make(map[string][]types.NamespacedName)
 	for _, pod := range pods {
-		podENIAnnotation, ok := pod.Annotations[annotationPodENI]
-		if !ok {
+		podENIInfo := pod.ENIInfo
+		if podENIInfo == nil {
 			continue
 		}
-		var schema annotationSchemaPodENI
-		if err := json.Unmarshal([]byte(podENIAnnotation), &schema); err != nil {
-			return nil, err
-		}
-		eniID := schema.ENIID
-		podKey := k8s.NamespacedName(pod)
+
+		podKey := pod.Key
+		eniID := podENIInfo.ENIID
 		podKeysByENIID[eniID] = append(podKeysByENIID[eniID], podKey)
 	}
 	if len(podKeysByENIID) == 0 {
@@ -206,10 +188,10 @@ func (r *defaultPodENIInfoResolver) resolveViaPodENIAnnotation(ctx context.Conte
 }
 
 // resolveViaVPCIPAddress tries to resolve pod ENI via the IPAddress within VPC.
-func (r *defaultPodENIInfoResolver) resolveViaVPCIPAddress(ctx context.Context, pods []*corev1.Pod) (map[types.NamespacedName]ENIInfo, error) {
+func (r *defaultPodENIInfoResolver) resolveViaVPCIPAddress(ctx context.Context, pods []k8s.PodInfo) (map[types.NamespacedName]ENIInfo, error) {
 	podKeysByIP := make(map[string][]types.NamespacedName, len(pods))
 	for _, pod := range pods {
-		podKeysByIP[pod.Status.PodIP] = append(podKeysByIP[pod.Status.PodIP], k8s.NamespacedName(pod))
+		podKeysByIP[pod.PodIP] = append(podKeysByIP[pod.PodIP], pod.Key)
 	}
 	if len(podKeysByIP) == 0 {
 		return nil, nil
@@ -255,21 +237,21 @@ func (r *defaultPodENIInfoResolver) resolveViaVPCIPAddress(ctx context.Context,
 }
 
 // computePodENIInfoCacheKey computes the cacheKey for pod's ENIInfo cache.
-func computePodENIInfoCacheKey(pod *corev1.Pod) podENIInfoCacheKey {
+func computePodENIInfoCacheKey(podInfo k8s.PodInfo) podENIInfoCacheKey {
 	return podENIInfoCacheKey{
-		podKey: k8s.NamespacedName(pod),
-		podUID: string(pod.UID),
+		podKey: podInfo.Key,
+		podUID: podInfo.UID,
 	}
 }
 
-// computePodsWithUnresolvedENIInfo computes pods that don't have resolvedENIInfo.
-func computePodsWithUnresolvedENIInfo(pods []*corev1.Pod, eniInfoByPodKey map[types.NamespacedName]ENIInfo) []*corev1.Pod {
-	unresolvedPods := make([]*corev1.Pod, 0, len(pods)-len(eniInfoByPodKey))
+// computePodsWithoutENIInfo computes pods that don't have a ENIInfo.
+func computePodsWithoutENIInfo(pods []k8s.PodInfo, eniInfoByPodKey map[types.NamespacedName]ENIInfo) []k8s.PodInfo {
+	podsWithoutENIInfo := make([]k8s.PodInfo, 0, len(pods)-len(eniInfoByPodKey))
 	for _, pod := range pods {
-		podKey := k8s.NamespacedName(pod)
+		podKey := pod.Key
 		if _, ok := eniInfoByPodKey[podKey]; !ok {
-			unresolvedPods = append(unresolvedPods, pod)
+			podsWithoutENIInfo = append(podsWithoutENIInfo, pod)
 		}
 	}
-	return unresolvedPods
+	return podsWithoutENIInfo
 }
diff --git a/pkg/runtime/errors.go b/pkg/runtime/errors.go
index 1dda68e6e..04dbba3fc 100644
--- a/pkg/runtime/errors.go
+++ b/pkg/runtime/errors.go
@@ -1,68 +1,63 @@
 package runtime
 
 import (
+	"fmt"
 	"time"
 )
 
-// NewRequeueError constructs new RequeueError to
+// NewRequeueNeeded constructs new RequeueError to
 // instruct controller-runtime to requeue the processing item without been logged as error.
-func NewRequeueError(err error) *RequeueError {
-	return &RequeueError{
-		err: err,
+func NewRequeueNeeded(reason string) *RequeueNeeded {
+	return &RequeueNeeded{
+		reason: reason,
 	}
 }
 
-// NewRequeueAfterError constructs new RequeueAfterError to
+// NewRequeueNeededAfter constructs new RequeueNeededAfter to
 // instruct controller-runtime to requeue the processing item after specified duration without been logged as error.
-func NewRequeueAfterError(err error, duration time.Duration) *RequeueAfterError {
-	return &RequeueAfterError{
-		err:      err,
+func NewRequeueNeededAfter(reason string, duration time.Duration) *RequeueNeededAfter {
+	return &RequeueNeededAfter{
+		reason:   reason,
 		duration: duration,
 	}
 }
 
-var _ error = &RequeueError{}
+var _ error = &RequeueNeeded{}
 
 // An error to instruct controller-runtime to requeue the processing item without been logged as error.
 // This should be used when a "error condition" occurrence is sort of expected and can be resolved by retry.
 // e.g. a dependency haven't been fulfilled yet.
-type RequeueError struct {
-	err error
+type RequeueNeeded struct {
+	reason string
 }
 
-func (e *RequeueError) Error() string {
-	if e.err == nil {
-		return ""
-	}
-	return e.err.Error()
+func (e *RequeueNeeded) Reason() string {
+	return e.reason
 }
 
-func (e *RequeueError) Unwrap() error {
-	return e.err
+func (e *RequeueNeeded) Error() string {
+	return fmt.Sprintf("requeue needed: %v", e.reason)
 }
 
-var _ error = &RequeueAfterError{}
+var _ error = &RequeueNeededAfter{}
 
 // An error to instruct controller-runtime to requeue the processing item after specified duration without been logged as error.
 // This should be used when a "error condition" occurrence is sort of expected and can be resolved by retry.
 // e.g. a dependency haven't been fulfilled yet, and expected it to be fulfilled after duration.
 // Note: use this with care,a simple wait might suits your use case better.
-type RequeueAfterError struct {
-	err      error
+type RequeueNeededAfter struct {
+	reason   string
 	duration time.Duration
 }
 
-func (e *RequeueAfterError) Error() string {
-	if e.err == nil {
-		return ""
-	}
-	return e.err.Error()
+func (e *RequeueNeededAfter) Reason() string {
+	return e.reason
 }
 
-func (e *RequeueAfterError) Duration() time.Duration {
+func (e *RequeueNeededAfter) Duration() time.Duration {
 	return e.duration
 }
 
-func (e *RequeueAfterError) Unwrap() error {
-	return e.err
+func (e *RequeueNeededAfter) Error() string {
+	return fmt.Sprintf("requeue needed after %v: %v", e.duration, e.reason)
 }
diff --git a/pkg/runtime/reconcile.go b/pkg/runtime/reconcile.go
index d9332e5c0..d37412072 100644
--- a/pkg/runtime/reconcile.go
+++ b/pkg/runtime/reconcile.go
@@ -12,15 +12,15 @@ func HandleReconcileError(err error, log logr.Logger) (ctrl.Result, error) {
 		return ctrl.Result{}, nil
 	}
 
-	var requeueAfterErr *RequeueAfterError
-	if errors.As(err, &requeueAfterErr) {
-		log.V(1).Info("requeue after due to error", "duration", requeueAfterErr.Duration(), "error", requeueAfterErr.Unwrap())
-		return ctrl.Result{RequeueAfter: requeueAfterErr.Duration()}, nil
+	var requeueNeededAfter *RequeueNeededAfter
+	if errors.As(err, &requeueNeededAfter) {
+		log.V(1).Info("requeue after", "duration", requeueNeededAfter.Duration(), "reason", requeueNeededAfter.Reason())
+		return ctrl.Result{RequeueAfter: requeueNeededAfter.Duration()}, nil
 	}
 
-	var requeueError *RequeueError
-	if errors.As(err, &requeueError) {
-		log.V(1).Info("requeue due to error", "error", requeueError.Unwrap())
+	var requeueNeeded *RequeueNeeded
+	if errors.As(err, &requeueNeeded) {
+		log.V(1).Info("requeue", "reason", requeueNeeded.Reason())
 		return ctrl.Result{Requeue: true}, nil
 	}
 
diff --git a/pkg/targetgroupbinding/networking_manager.go b/pkg/targetgroupbinding/networking_manager.go
index 2e0420012..a4e6aca38 100644
--- a/pkg/targetgroupbinding/networking_manager.go
+++ b/pkg/targetgroupbinding/networking_manager.go
@@ -115,18 +115,18 @@ func (m *defaultNetworkingManager) Cleanup(ctx context.Context, tgb *elbv2api.Ta
 }
 
 func (m *defaultNetworkingManager) computeIngressPermissionsPerSGWithPodEndpoints(ctx context.Context, tgbNetworking elbv2api.TargetGroupBindingNetworking, endpoints []backend.PodEndpoint) (map[string][]networking.IPPermissionInfo, error) {
-	pods := make([]*corev1.Pod, 0, len(endpoints))
-	podByPodKey := make(map[types.NamespacedName]*corev1.Pod, len(endpoints))
+	pods := make([]k8s.PodInfo, 0, len(endpoints))
+	podByPodKey := make(map[types.NamespacedName]k8s.PodInfo, len(endpoints))
 	for _, endpoint := range endpoints {
 		pods = append(pods, endpoint.Pod)
-		podByPodKey[k8s.NamespacedName(endpoint.Pod)] = endpoint.Pod
+		podByPodKey[endpoint.Pod.Key] = endpoint.Pod
 	}
 	eniInfoByPodKey, err := m.podENIResolver.Resolve(ctx, pods)
 	if err != nil {
 		return nil, err
 	}
 
-	podsBySG := make(map[string][]*corev1.Pod)
+	podsBySG := make(map[string][]k8s.PodInfo)
 	for podKey, eniInfo := range eniInfoByPodKey {
 		sgID, err := m.resolveEndpointSGForENI(ctx, eniInfo)
 		if err != nil {
@@ -250,7 +250,7 @@ func (m *defaultNetworkingManager) computeAggregatedIngressPermissionsPerSG(_ co
 
 // computeIngressPermissionsForTGBNetworking computes the needed Inbound IPPermissions for specified TargetGroupBinding.
 // an optional list of pods if provided if pod endpoints are used, and named ports will be resolved to the pod port.
-func (m *defaultNetworkingManager) computeIngressPermissionsForTGBNetworking(ctx context.Context, tgbNetworking elbv2api.TargetGroupBindingNetworking, pods []*corev1.Pod) ([]networking.IPPermissionInfo, error) {
+func (m *defaultNetworkingManager) computeIngressPermissionsForTGBNetworking(ctx context.Context, tgbNetworking elbv2api.TargetGroupBindingNetworking, pods []k8s.PodInfo) ([]networking.IPPermissionInfo, error) {
 	var permissions []networking.IPPermissionInfo
 	protocolTCP := elbv2api.NetworkingProtocolTCP
 	for _, rule := range tgbNetworking.Ingress {
@@ -285,7 +285,7 @@ type sdkFromToPortPair struct {
 
 // computePermissionsForPeerPort computes the needed Inbound IPPermissions for specified peer and port.
 // an optional list of pods if provided if pod endpoints are used, and named ports will be resolved to the pod port.
-func (m *defaultNetworkingManager) computePermissionsForPeerPort(ctx context.Context, peer elbv2api.NetworkingPeer, port elbv2api.NetworkingPort, pods []*corev1.Pod) ([]networking.IPPermissionInfo, error) {
+func (m *defaultNetworkingManager) computePermissionsForPeerPort(ctx context.Context, peer elbv2api.NetworkingPeer, port elbv2api.NetworkingPort, pods []k8s.PodInfo) ([]networking.IPPermissionInfo, error) {
 	sdkProtocol := "tcp"
 	if port.Protocol != nil {
 		switch *port.Protocol {
@@ -350,7 +350,7 @@ func (m *defaultNetworkingManager) computePermissionsForPeerPort(ctx context.Con
 
 // computeNumericalPorts computes the numerical ports if a named is used.
 // Note: multiple numerical ports can be returned since same named port might corresponding to different numerical ports on different pods.
-func (m *defaultNetworkingManager) computeNumericalPorts(_ context.Context, port intstr.IntOrString, pods []*corev1.Pod) ([]int64, error) {
+func (m *defaultNetworkingManager) computeNumericalPorts(_ context.Context, port intstr.IntOrString, pods []k8s.PodInfo) ([]int64, error) {
 	if port.Type == intstr.Int {
 		return []int64{int64(port.IntVal)}, nil
 	}
@@ -360,7 +360,7 @@ func (m *defaultNetworkingManager) computeNumericalPorts(_ context.Context, port
 
 	containerPorts := sets.NewInt64()
 	for _, pod := range pods {
-		containerPort, err := k8s.LookupContainerPort(pod, port)
+		containerPort, err := pod.LookupContainerPort(port)
 		if err != nil {
 			return nil, err
 		}
diff --git a/pkg/targetgroupbinding/resource_manager.go b/pkg/targetgroupbinding/resource_manager.go
index dd7c97bfa..9f38b5a9f 100644
--- a/pkg/targetgroupbinding/resource_manager.go
+++ b/pkg/targetgroupbinding/resource_manager.go
@@ -35,11 +35,11 @@ type ResourceManager interface {
 
 // NewDefaultResourceManager constructs new defaultResourceManager.
 func NewDefaultResourceManager(k8sClient client.Client, elbv2Client services.ELBV2,
-	podENIResolver networking.PodENIInfoResolver, nodeENIResolver networking.NodeENIInfoResolver,
+	podInfoRepo k8s.PodInfoRepo, podENIResolver networking.PodENIInfoResolver, nodeENIResolver networking.NodeENIInfoResolver,
 	sgManager networking.SecurityGroupManager, sgReconciler networking.SecurityGroupReconciler,
 	vpcID string, clusterName string, logger logr.Logger) *defaultResourceManager {
 	targetsManager := NewCachedTargetsManager(elbv2Client, logger)
-	endpointResolver := backend.NewDefaultEndpointResolver(k8sClient, logger)
+	endpointResolver := backend.NewDefaultEndpointResolver(k8sClient, podInfoRepo, logger)
 	networkingManager := NewDefaultNetworkingManager(k8sClient, podENIResolver, nodeENIResolver, sgManager, sgReconciler, vpcID, clusterName, logger)
 	return &defaultResourceManager{
 		k8sClient:         k8sClient,
@@ -87,13 +87,16 @@ func (m *defaultResourceManager) Cleanup(ctx context.Context, tgb *elbv2api.Targ
 
 func (m *defaultResourceManager) reconcileWithIPTargetType(ctx context.Context, tgb *elbv2api.TargetGroupBinding) error {
 	svcKey := buildServiceReferenceKey(tgb, tgb.Spec.ServiceRef)
+
+	targetHealthCondType := BuildTargetHealthPodConditionType(tgb)
 	resolveOpts := []backend.EndpointResolveOption{
-		backend.WithUnreadyPodInclusionCriterion(k8s.IsPodContainersReady),
+		backend.WithPodReadinessGate(targetHealthCondType),
 	}
-	endpoints, err := m.endpointResolver.ResolvePodEndpoints(ctx, svcKey, tgb.Spec.ServiceRef.Port, resolveOpts...)
+	endpoints, containsPotentialReadyEndpoints, err := m.endpointResolver.ResolvePodEndpoints(ctx, svcKey, tgb.Spec.ServiceRef.Port, resolveOpts...)
 	if err != nil {
 		return err
 	}
+
 	tgARN := tgb.Spec.TargetGroupARN
 	targets, err := m.targetsManager.ListTargets(ctx, tgARN)
 	if err != nil {
@@ -112,16 +115,22 @@ func (m *defaultResourceManager) reconcileWithIPTargetType(ctx context.Context,
 		return err
 	}
 
-	anyPodNeedFurtherProbe, err := m.updateTargetHealthPodCondition(ctx, tgb, matchedEndpointAndTargets, unmatchedEndpoints)
+	anyPodNeedFurtherProbe, err := m.updateTargetHealthPodCondition(ctx, targetHealthCondType, matchedEndpointAndTargets, unmatchedEndpoints)
 	if err != nil {
 		return err
 	}
+
 	if anyPodNeedFurtherProbe {
 		if containsTargetsInInitialState(matchedEndpointAndTargets) || len(unmatchedEndpoints) != 0 {
-			return runtime.NewRequeueAfterError(nil, m.targetHealthRequeueDuration)
+			return runtime.NewRequeueNeededAfter("monitor targetHealth", m.targetHealthRequeueDuration)
 		}
-		return runtime.NewRequeueError(nil)
+		return runtime.NewRequeueNeeded("monitor targetHealth")
 	}
+
+	if containsPotentialReadyEndpoints {
+		return runtime.NewRequeueNeeded("monitor potential ready endpoints")
+	}
+
 	_ = drainingTargets
 	return nil
 }
@@ -174,10 +183,8 @@ func (m *defaultResourceManager) cleanupTargets(ctx context.Context, tgb *elbv2a
 
 // updateTargetHealthPodCondition will updates pod's targetHealth condition for matchedEndpointAndTargets and unmatchedEndpoints.
 // returns whether further probe is needed or not
-func (m *defaultResourceManager) updateTargetHealthPodCondition(ctx context.Context, tgb *elbv2api.TargetGroupBinding,
+func (m *defaultResourceManager) updateTargetHealthPodCondition(ctx context.Context, targetHealthCondType corev1.PodConditionType,
 	matchedEndpointAndTargets []podEndpointAndTargetPair, unmatchedEndpoints []backend.PodEndpoint) (bool, error) {
-
-	targetHealthCondType := BuildTargetHealthPodConditionType(tgb)
 	anyPodNeedFurtherProbe := false
 
 	for _, endpointAndTarget := range matchedEndpointAndTargets {
@@ -212,12 +219,12 @@ func (m *defaultResourceManager) updateTargetHealthPodCondition(ctx context.Cont
 
 // updateTargetHealthPodConditionForPod updates pod's targetHealth condition for a single pod and its matched target.
 // returns whether further probe is needed or not.
-func (m *defaultResourceManager) updateTargetHealthPodConditionForPod(ctx context.Context, pod *corev1.Pod,
+func (m *defaultResourceManager) updateTargetHealthPodConditionForPod(ctx context.Context, pod k8s.PodInfo,
 	targetHealth *elbv2sdk.TargetHealth, targetHealthCondType corev1.PodConditionType) (bool, error) {
-
-	if !k8s.IsPodHasReadinessGate(pod, targetHealthCondType) {
+	if !pod.HasAnyOfReadinessGates([]corev1.PodConditionType{targetHealthCondType}) {
 		return false, nil
 	}
+
 	targetHealthCondStatus := corev1.ConditionUnknown
 	var reason, message string
 	if targetHealth != nil {
@@ -231,9 +238,10 @@ func (m *defaultResourceManager) updateTargetHealthPodConditionForPod(ctx contex
 		message = awssdk.StringValue(targetHealth.Description)
 	}
 	needFurtherProbe := targetHealthCondStatus != corev1.ConditionTrue
-	existingTargetHealthCond := k8s.GetPodCondition(pod, targetHealthCondType)
+
+	existingTargetHealthCond, exists := pod.GetPodCondition(targetHealthCondType)
 	// we skip patch pod if it matches current computed status/reason/message.
-	if existingTargetHealthCond != nil &&
+	if exists &&
 		existingTargetHealthCond.Status == targetHealthCondStatus &&
 		existingTargetHealthCond.Reason == reason &&
 		existingTargetHealthCond.Message == message {
@@ -246,17 +254,22 @@ func (m *defaultResourceManager) updateTargetHealthPodConditionForPod(ctx contex
 		Reason:  reason,
 		Message: message,
 	}
-	if existingTargetHealthCond == nil || existingTargetHealthCond.Status != targetHealthCondStatus {
+	if !exists || existingTargetHealthCond.Status != targetHealthCondStatus {
 		newTargetHealthCond.LastTransitionTime = metav1.Now()
 	}
 
-	oldPod := pod.DeepCopy()
-	k8s.UpdatePodCondition(pod, newTargetHealthCond)
-	patch, err := buildPodStrategicMergePatch(oldPod, pod)
+	patch, err := buildPodConditionPatch(pod, newTargetHealthCond)
 	if err != nil {
 		return false, err
 	}
-	if err := m.k8sClient.Status().Patch(ctx, pod, patch); err != nil {
+	k8sPod := &corev1.Pod{
+		ObjectMeta: metav1.ObjectMeta{
+			Namespace: pod.Key.Namespace,
+			Name:      pod.Key.Name,
+			UID:       pod.UID,
+		},
+	}
+	if err := m.k8sClient.Status().Patch(ctx, k8sPod, patch); err != nil {
 		if apierrors.IsNotFound(err) {
 			return false, nil
 		}
@@ -396,13 +409,21 @@ func matchNodePortEndpointWithTargets(endpoints []backend.NodePortEndpoint, targ
 	return matchedEndpointAndTargets, unmatchedEndpoints, unmatchedTargets
 }
 
-func buildPodStrategicMergePatch(oldPod *corev1.Pod, newPod *corev1.Pod) (client.Patch, error) {
-	oldData, err := json.Marshal(oldPod)
+func buildPodConditionPatch(pod k8s.PodInfo, condition corev1.PodCondition) (client.Patch, error) {
+	oldData, err := json.Marshal(corev1.Pod{
+		Status: corev1.PodStatus{
+			Conditions: nil,
+		},
+	})
 	if err != nil {
 		return nil, err
 	}
-
-	newData, err := json.Marshal(newPod)
+	newData, err := json.Marshal(corev1.Pod{
+		ObjectMeta: metav1.ObjectMeta{UID: pod.UID}, // only put the uid in the new object to ensure it appears in the patch as a precondition
+		Status: corev1.PodStatus{
+			Conditions: []corev1.PodCondition{condition},
+		},
+	})
 	if err != nil {
 		return nil, err
 	}
diff --git a/scripts/gen_mocks.sh b/scripts/gen_mocks.sh
index 57126b958..f81debec7 100644
--- a/scripts/gen_mocks.sh
+++ b/scripts/gen_mocks.sh
@@ -4,6 +4,7 @@ mockgen -destination=./mocks/aws/services/mock_ec2.go sigs.k8s.io/aws-load-balan
 mockgen -destination=./mocks/webhook/mock_mutator.go sigs.k8s.io/aws-load-balancer-controller/pkg/webhook Mutator
 mockgen -destination=./mocks/webhook/mock_validator.go sigs.k8s.io/aws-load-balancer-controller/pkg/webhook Validator
 mockgen -destination=./mocks/k8s/mock_finalizer.go sigs.k8s.io/aws-load-balancer-controller/pkg/k8s FinalizerManager
+mockgen -destination=./mocks/k8s/mock_repo_info_repo.go sigs.k8s.io/aws-load-balancer-controller/pkg/k8s PodInfoRepo
 mockgen -destination=./mocks/networking/mock_security_group_manager.go sigs.k8s.io/aws-load-balancer-controller/pkg/networking SecurityGroupManager
 mockgen -destination=./mocks/networking/mock_subnet_resolver.go sigs.k8s.io/aws-load-balancer-controller/pkg/networking SubnetsResolver
 mockgen -destination=./mocks/ingress/mock_cert_discovery.go sigs.k8s.io/aws-load-balancer-controller/pkg/ingress CertDiscovery


Test patch:
diff --git a/pkg/backend/endpoint_resolver_test.go b/pkg/backend/endpoint_resolver_test.go
index 46e8d8acc..05b5bb174 100644
--- a/pkg/backend/endpoint_resolver_test.go
+++ b/pkg/backend/endpoint_resolver_test.go
@@ -3,6 +3,7 @@ package backend
 import (
 	"context"
 	"errors"
+	"github.com/golang/mock/gomock"
 	"github.com/google/go-cmp/cmp"
 	"github.com/google/go-cmp/cmp/cmpopts"
 	"github.com/stretchr/testify/assert"
@@ -13,106 +14,103 @@ import (
 	"k8s.io/apimachinery/pkg/types"
 	"k8s.io/apimachinery/pkg/util/intstr"
 	clientgoscheme "k8s.io/client-go/kubernetes/scheme"
+	mock_k8s "sigs.k8s.io/aws-load-balancer-controller/mocks/k8s"
 	"sigs.k8s.io/aws-load-balancer-controller/pkg/equality"
 	"sigs.k8s.io/aws-load-balancer-controller/pkg/k8s"
 	ctrl "sigs.k8s.io/controller-runtime"
 	testclient "sigs.k8s.io/controller-runtime/pkg/client/fake"
+	"sigs.k8s.io/controller-runtime/pkg/log"
 	"testing"
 )
 
 func Test_defaultEndpointResolver_ResolvePodEndpoints(t *testing.T) {
 	testNS := "test-ns"
-	pod1 := &corev1.Pod{
-		ObjectMeta: metav1.ObjectMeta{
-			Namespace: testNS,
-			Name:      "pod-1",
-		},
-		Spec: corev1.PodSpec{
-			ReadinessGates: nil,
-		},
-		Status: corev1.PodStatus{
-			PodIP: "192.168.1.1",
-			Conditions: []corev1.PodCondition{
-				{
-					Type:   corev1.PodReady,
-					Status: corev1.ConditionTrue,
-				},
-				{
-					Type:   corev1.ContainersReady,
-					Status: corev1.ConditionTrue,
-				},
+	pod1 := k8s.PodInfo{
+		Key:            types.NamespacedName{Namespace: testNS, Name: "pod-1"},
+		UID:            "pod-uuid-1",
+		ReadinessGates: nil,
+		Conditions: []corev1.PodCondition{
+			{
+				Type:   corev1.PodReady,
+				Status: corev1.ConditionTrue,
+			},
+			{
+				Type:   corev1.ContainersReady,
+				Status: corev1.ConditionTrue,
 			},
 		},
+		PodIP: "192.168.1.1",
 	}
-	pod2 := &corev1.Pod{
-		ObjectMeta: metav1.ObjectMeta{
-			Namespace: testNS,
-			Name:      "pod-2",
-		},
-		Spec: corev1.PodSpec{
-			ReadinessGates: nil,
-		},
-		Status: corev1.PodStatus{
-			PodIP: "192.168.1.2",
-			Conditions: []corev1.PodCondition{
-				{
-					Type:   corev1.PodReady,
-					Status: corev1.ConditionTrue,
-				},
-				{
-					Type:   corev1.ContainersReady,
-					Status: corev1.ConditionTrue,
-				},
+	pod2 := k8s.PodInfo{
+		Key:            types.NamespacedName{Namespace: testNS, Name: "pod-2"},
+		UID:            "pod-uuid-2",
+		ReadinessGates: nil,
+		Conditions: []corev1.PodCondition{
+			{
+				Type:   corev1.PodReady,
+				Status: corev1.ConditionTrue,
+			},
+			{
+				Type:   corev1.ContainersReady,
+				Status: corev1.ConditionTrue,
 			},
 		},
+		PodIP: "192.168.1.2",
 	}
-	pod3 := &corev1.Pod{
-		ObjectMeta: metav1.ObjectMeta{
-			Namespace: testNS,
-			Name:      "pod-3",
+	pod3 := k8s.PodInfo{
+		Key: types.NamespacedName{Namespace: testNS, Name: "pod-3"},
+		UID: "pod-uuid-3",
+		Conditions: []corev1.PodCondition{
+			{
+				Type:   corev1.PodReady,
+				Status: corev1.ConditionFalse,
+			},
+			{
+				Type:   corev1.ContainersReady,
+				Status: corev1.ConditionTrue,
+			},
 		},
-		Spec: corev1.PodSpec{
-			ReadinessGates: nil,
+		PodIP: "192.168.1.3",
+	}
+	pod4 := k8s.PodInfo{
+		Key: types.NamespacedName{Namespace: testNS, Name: "pod-4"},
+		UID: "pod-uuid-4",
+		ReadinessGates: []corev1.PodReadinessGate{
+			{
+				ConditionType: "custom-condition",
+			},
 		},
-		Status: corev1.PodStatus{
-			PodIP: "192.168.1.3",
-			Conditions: []corev1.PodCondition{
-				{
-					Type:   corev1.PodReady,
-					Status: corev1.ConditionFalse,
-				},
-				{
-					Type:   corev1.ContainersReady,
-					Status: corev1.ConditionTrue,
-				},
+		Conditions: []corev1.PodCondition{
+			{
+				Type:   corev1.PodReady,
+				Status: corev1.ConditionFalse,
+			},
+			{
+				Type:   corev1.ContainersReady,
+				Status: corev1.ConditionTrue,
 			},
 		},
+		PodIP: "192.168.1.4",
 	}
-	pod4 := &corev1.Pod{
-		ObjectMeta: metav1.ObjectMeta{
-			Namespace: testNS,
-			Name:      "pod-4",
-		},
-		Spec: corev1.PodSpec{
-			ReadinessGates: []corev1.PodReadinessGate{
-				{
-					ConditionType: "custom-condition",
-				},
+	pod5 := k8s.PodInfo{
+		Key: types.NamespacedName{Namespace: testNS, Name: "pod-5"},
+		UID: "pod-uuid-5",
+		ReadinessGates: []corev1.PodReadinessGate{
+			{
+				ConditionType: "custom-condition",
 			},
 		},
-		Status: corev1.PodStatus{
-			PodIP: "192.168.1.4",
-			Conditions: []corev1.PodCondition{
-				{
-					Type:   corev1.PodReady,
-					Status: corev1.ConditionFalse,
-				},
-				{
-					Type:   corev1.ContainersReady,
-					Status: corev1.ConditionTrue,
-				},
+		Conditions: []corev1.PodCondition{
+			{
+				Type:   corev1.PodReady,
+				Status: corev1.ConditionFalse,
+			},
+			{
+				Type:   corev1.ContainersReady,
+				Status: corev1.ConditionFalse,
 			},
 		},
+		PodIP: "192.168.1.5",
 	}
 	svc1 := &corev1.Service{
 		ObjectMeta: metav1.ObjectMeta{
@@ -148,37 +146,37 @@ func Test_defaultEndpointResolver_ResolvePodEndpoints(t *testing.T) {
 				},
 				Addresses: []corev1.EndpointAddress{
 					{
-						IP: pod1.Status.PodIP,
+						IP: pod1.PodIP,
 						TargetRef: &corev1.ObjectReference{
 							Kind:      "Pod",
-							Namespace: pod1.Namespace,
-							Name:      pod1.Name,
+							Namespace: pod1.Key.Namespace,
+							Name:      pod1.Key.Name,
 						},
 					},
 					{
-						IP: pod2.Status.PodIP,
+						IP: pod2.PodIP,
 						TargetRef: &corev1.ObjectReference{
 							Kind:      "Pod",
-							Namespace: pod2.Namespace,
-							Name:      pod2.Name,
+							Namespace: pod2.Key.Namespace,
+							Name:      pod2.Key.Name,
 						},
 					},
 				},
 				NotReadyAddresses: []corev1.EndpointAddress{
 					{
-						IP: pod3.Status.PodIP,
+						IP: pod3.PodIP,
 						TargetRef: &corev1.ObjectReference{
 							Kind:      "Pod",
-							Namespace: pod3.Namespace,
-							Name:      pod3.Name,
+							Namespace: pod3.Key.Namespace,
+							Name:      pod3.Key.Name,
 						},
 					},
 					{
-						IP: pod4.Status.PodIP,
+						IP: pod4.PodIP,
 						TargetRef: &corev1.ObjectReference{
 							Kind:      "Pod",
-							Namespace: pod4.Namespace,
-							Name:      pod4.Name,
+							Namespace: pod4.Key.Namespace,
+							Name:      pod4.Key.Name,
 						},
 					},
 				},
@@ -200,21 +198,21 @@ func Test_defaultEndpointResolver_ResolvePodEndpoints(t *testing.T) {
 				},
 				Addresses: []corev1.EndpointAddress{
 					{
-						IP: pod1.Status.PodIP,
+						IP: pod1.PodIP,
 						TargetRef: &corev1.ObjectReference{
 							Kind:      "Pod",
-							Namespace: pod1.Namespace,
-							Name:      pod1.Name,
+							Namespace: pod1.Key.Namespace,
+							Name:      pod1.Key.Name,
 						},
 					},
 				},
 				NotReadyAddresses: []corev1.EndpointAddress{
 					{
-						IP: pod3.Status.PodIP,
+						IP: pod3.PodIP,
 						TargetRef: &corev1.ObjectReference{
 							Kind:      "Pod",
-							Namespace: pod3.Namespace,
-							Name:      pod3.Name,
+							Namespace: pod3.Key.Namespace,
+							Name:      pod3.Key.Name,
 						},
 					},
 				},
@@ -228,21 +226,21 @@ func Test_defaultEndpointResolver_ResolvePodEndpoints(t *testing.T) {
 				},
 				Addresses: []corev1.EndpointAddress{
 					{
-						IP: pod2.Status.PodIP,
+						IP: pod2.PodIP,
 						TargetRef: &corev1.ObjectReference{
 							Kind:      "Pod",
-							Namespace: pod2.Namespace,
-							Name:      pod2.Name,
+							Namespace: pod2.Key.Namespace,
+							Name:      pod2.Key.Name,
 						},
 					},
 				},
 				NotReadyAddresses: []corev1.EndpointAddress{
 					{
-						IP: pod4.Status.PodIP,
+						IP: pod4.PodIP,
 						TargetRef: &corev1.ObjectReference{
 							Kind:      "Pod",
-							Namespace: pod4.Namespace,
-							Name:      pod4.Name,
+							Namespace: pod4.Key.Namespace,
+							Name:      pod4.Key.Name,
 						},
 					},
 				},
@@ -268,37 +266,97 @@ func Test_defaultEndpointResolver_ResolvePodEndpoints(t *testing.T) {
 				},
 				Addresses: []corev1.EndpointAddress{
 					{
-						IP: pod1.Status.PodIP,
+						IP: pod1.PodIP,
 						TargetRef: &corev1.ObjectReference{
 							Kind:      "Pod",
-							Namespace: pod1.Namespace,
-							Name:      pod1.Name,
+							Namespace: pod1.Key.Namespace,
+							Name:      pod1.Key.Name,
 						},
 					},
 					{
-						IP: pod2.Status.PodIP,
+						IP: pod2.PodIP,
 						TargetRef: &corev1.ObjectReference{
 							Kind:      "Pod",
-							Namespace: pod2.Namespace,
-							Name:      pod2.Name,
+							Namespace: pod2.Key.Namespace,
+							Name:      pod2.Key.Name,
 						},
 					},
 				},
 				NotReadyAddresses: []corev1.EndpointAddress{
 					{
-						IP: pod3.Status.PodIP,
+						IP: pod3.PodIP,
 						TargetRef: &corev1.ObjectReference{
 							Kind:      "Pod",
-							Namespace: pod3.Namespace,
-							Name:      pod3.Name,
+							Namespace: pod3.Key.Namespace,
+							Name:      pod3.Key.Name,
 						},
 					},
 					{
-						IP: pod4.Status.PodIP,
+						IP: pod4.PodIP,
 						TargetRef: &corev1.ObjectReference{
 							Kind:      "Pod",
-							Namespace: pod4.Namespace,
-							Name:      pod4.Name,
+							Namespace: pod4.Key.Namespace,
+							Name:      pod4.Key.Name,
+						},
+					},
+				},
+			},
+		},
+	}
+	ep1D := &corev1.Endpoints{
+		ObjectMeta: metav1.ObjectMeta{
+			Namespace: testNS,
+			Name:      "svc-1",
+		},
+		Subsets: []corev1.EndpointSubset{
+			{
+				Ports: []corev1.EndpointPort{
+					{
+						Name: "http",
+						Port: 8080,
+					},
+				},
+				Addresses: []corev1.EndpointAddress{
+					{
+						IP: pod1.PodIP,
+						TargetRef: &corev1.ObjectReference{
+							Kind:      "Pod",
+							Namespace: pod1.Key.Namespace,
+							Name:      pod1.Key.Name,
+						},
+					},
+					{
+						IP: pod2.PodIP,
+						TargetRef: &corev1.ObjectReference{
+							Kind:      "Pod",
+							Namespace: pod2.Key.Namespace,
+							Name:      pod2.Key.Name,
+						},
+					},
+				},
+				NotReadyAddresses: []corev1.EndpointAddress{
+					{
+						IP: pod3.PodIP,
+						TargetRef: &corev1.ObjectReference{
+							Kind:      "Pod",
+							Namespace: pod3.Key.Namespace,
+							Name:      pod3.Key.Name,
+						},
+					},
+					{
+						IP: pod4.PodIP,
+						TargetRef: &corev1.ObjectReference{
+							Kind:      "Pod",
+							Namespace: pod4.Key.Namespace,
+							Name:      pod4.Key.Name,
+						},
+					},
+					{
+						IP: pod5.PodIP,
+						TargetRef: &corev1.ObjectReference{
+							Kind:      "Pod",
+							Namespace: pod5.Key.Namespace,
+							Name:      pod5.Key.Name,
 						},
 					},
 				},
@@ -306,30 +364,53 @@ func Test_defaultEndpointResolver_ResolvePodEndpoints(t *testing.T) {
 		},
 	}
 
+	type podInfoRepoGetCall struct {
+		key    types.NamespacedName
+		pod    k8s.PodInfo
+		exists bool
+		err    error
+	}
 	type env struct {
-		pods          []*corev1.Pod
 		services      []*corev1.Service
 		endpointsList []*corev1.Endpoints
 	}
+	type fields struct {
+		podInfoRepoGetCalls []podInfoRepoGetCall
+	}
 	type args struct {
 		svcKey types.NamespacedName
 		port   intstr.IntOrString
 		opts   []EndpointResolveOption
 	}
 	tests := []struct {
-		name    string
-		env     env
-		args    args
-		want    []PodEndpoint
-		wantErr error
+		name                                string
+		env                                 env
+		fields                              fields
+		args                                args
+		want                                []PodEndpoint
+		wantContainsPotentialReadyEndpoints bool
+		wantErr                             error
 	}{
 		{
 			name: "only ready pods will be included by default",
 			env: env{
-				pods:          []*corev1.Pod{pod1, pod2, pod3, pod4},
 				services:      []*corev1.Service{svc1},
 				endpointsList: []*corev1.Endpoints{ep1A},
 			},
+			fields: fields{
+				podInfoRepoGetCalls: []podInfoRepoGetCall{
+					{
+						key:    pod1.Key,
+						pod:    pod1,
+						exists: true,
+					},
+					{
+						key:    pod2.Key,
+						pod:    pod2,
+						exists: true,
+					},
+				},
+			},
 			args: args{
 				svcKey: k8s.NamespacedName(svc1),
 				port:   intstr.FromString("http"),
@@ -347,18 +428,42 @@ func Test_defaultEndpointResolver_ResolvePodEndpoints(t *testing.T) {
 					Pod:  pod2,
 				},
 			},
+			wantContainsPotentialReadyEndpoints: false,
 		},
 		{
-			name: "unready but containerReady pod will also be included",
+			name: "unready only be included if it have readinessGate and containerReady",
 			env: env{
-				pods:          []*corev1.Pod{pod1, pod2, pod3, pod4},
 				services:      []*corev1.Service{svc1},
 				endpointsList: []*corev1.Endpoints{ep1A},
 			},
+			fields: fields{
+				podInfoRepoGetCalls: []podInfoRepoGetCall{
+					{
+						key:    pod1.Key,
+						pod:    pod1,
+						exists: true,
+					},
+					{
+						key:    pod2.Key,
+						pod:    pod2,
+						exists: true,
+					},
+					{
+						key:    pod3.Key,
+						pod:    pod3,
+						exists: true,
+					},
+					{
+						key:    pod4.Key,
+						pod:    pod4,
+						exists: true,
+					},
+				},
+			},
 			args: args{
 				svcKey: k8s.NamespacedName(svc1),
 				port:   intstr.FromString("http"),
-				opts:   []EndpointResolveOption{WithUnreadyPodInclusionCriterion(k8s.IsPodContainersReady)},
+				opts:   []EndpointResolveOption{WithPodReadinessGate("custom-condition")},
 			},
 			want: []PodEndpoint{
 				{
@@ -371,34 +476,48 @@ func Test_defaultEndpointResolver_ResolvePodEndpoints(t *testing.T) {
 					Port: 8080,
 					Pod:  pod2,
 				},
-				{
-					IP:   "192.168.1.3",
-					Port: 8080,
-					Pod:  pod3,
-				},
 				{
 					IP:   "192.168.1.4",
 					Port: 8080,
 					Pod:  pod4,
 				},
 			},
+			wantContainsPotentialReadyEndpoints: false,
 		},
 		{
-			name: "unready but containerReady pod will also be included when contains specific readinessGate",
+			name: "endpoints with multiple subsets should work as expected",
 			env: env{
-				pods:          []*corev1.Pod{pod1, pod2, pod3, pod4},
 				services:      []*corev1.Service{svc1},
-				endpointsList: []*corev1.Endpoints{ep1A},
+				endpointsList: []*corev1.Endpoints{ep1B},
+			},
+			fields: fields{
+				podInfoRepoGetCalls: []podInfoRepoGetCall{
+					{
+						key:    pod1.Key,
+						pod:    pod1,
+						exists: true,
+					},
+					{
+						key:    pod2.Key,
+						pod:    pod2,
+						exists: true,
+					},
+					{
+						key:    pod3.Key,
+						pod:    pod3,
+						exists: true,
+					},
+					{
+						key:    pod4.Key,
+						pod:    pod4,
+						exists: true,
+					},
+				},
 			},
 			args: args{
 				svcKey: k8s.NamespacedName(svc1),
 				port:   intstr.FromString("http"),
-				opts: []EndpointResolveOption{
-					WithUnreadyPodInclusionCriterion(k8s.IsPodContainersReady),
-					WithUnreadyPodInclusionCriterion(func(pod *corev1.Pod) bool {
-						return k8s.IsPodHasReadinessGate(pod, "custom-condition")
-					}),
-				},
+				opts:   []EndpointResolveOption{WithPodReadinessGate("custom-condition")},
 			},
 			want: []PodEndpoint{
 				{
@@ -417,18 +536,42 @@ func Test_defaultEndpointResolver_ResolvePodEndpoints(t *testing.T) {
 					Pod:  pod4,
 				},
 			},
+			wantContainsPotentialReadyEndpoints: false,
 		},
 		{
-			name: "endpoints with multiple subsets should work as expected",
+			name: "endpoints with multiple ports should work as expected",
 			env: env{
-				pods:          []*corev1.Pod{pod1, pod2, pod3, pod4},
 				services:      []*corev1.Service{svc1},
-				endpointsList: []*corev1.Endpoints{ep1B},
+				endpointsList: []*corev1.Endpoints{ep1C},
+			},
+			fields: fields{
+				podInfoRepoGetCalls: []podInfoRepoGetCall{
+					{
+						key:    pod1.Key,
+						pod:    pod1,
+						exists: true,
+					},
+					{
+						key:    pod2.Key,
+						pod:    pod2,
+						exists: true,
+					},
+					{
+						key:    pod3.Key,
+						pod:    pod3,
+						exists: true,
+					},
+					{
+						key:    pod4.Key,
+						pod:    pod4,
+						exists: true,
+					},
+				},
 			},
 			args: args{
 				svcKey: k8s.NamespacedName(svc1),
 				port:   intstr.FromString("http"),
-				opts:   []EndpointResolveOption{WithUnreadyPodInclusionCriterion(k8s.IsPodContainersReady)},
+				opts:   []EndpointResolveOption{WithPodReadinessGate("custom-condition")},
 			},
 			want: []PodEndpoint{
 				{
@@ -441,29 +584,47 @@ func Test_defaultEndpointResolver_ResolvePodEndpoints(t *testing.T) {
 					Port: 8080,
 					Pod:  pod2,
 				},
-				{
-					IP:   "192.168.1.3",
-					Port: 8080,
-					Pod:  pod3,
-				},
 				{
 					IP:   "192.168.1.4",
 					Port: 8080,
 					Pod:  pod4,
 				},
 			},
+			wantContainsPotentialReadyEndpoints: false,
 		},
 		{
-			name: "endpoints with multiple ports should work as expected",
+			name: "unready but not found pod will be ignored, but signal potentialReadyEndpoints",
 			env: env{
-				pods:          []*corev1.Pod{pod1, pod2, pod3, pod4},
 				services:      []*corev1.Service{svc1},
-				endpointsList: []*corev1.Endpoints{ep1C},
+				endpointsList: []*corev1.Endpoints{ep1A},
+			},
+			fields: fields{
+				podInfoRepoGetCalls: []podInfoRepoGetCall{
+					{
+						key:    pod1.Key,
+						pod:    pod1,
+						exists: true,
+					},
+					{
+						key:    pod2.Key,
+						pod:    pod2,
+						exists: true,
+					},
+					{
+						key:    pod3.Key,
+						exists: false,
+					},
+					{
+						key:    pod4.Key,
+						pod:    pod4,
+						exists: true,
+					},
+				},
 			},
 			args: args{
 				svcKey: k8s.NamespacedName(svc1),
 				port:   intstr.FromString("http"),
-				opts:   []EndpointResolveOption{WithUnreadyPodInclusionCriterion(k8s.IsPodContainersReady)},
+				opts:   []EndpointResolveOption{WithPodReadinessGate("custom-condition")},
 			},
 			want: []PodEndpoint{
 				{
@@ -477,9 +638,63 @@ func Test_defaultEndpointResolver_ResolvePodEndpoints(t *testing.T) {
 					Pod:  pod2,
 				},
 				{
-					IP:   "192.168.1.3",
+					IP:   "192.168.1.4",
 					Port: 8080,
-					Pod:  pod3,
+					Pod:  pod4,
+				},
+			},
+			wantContainsPotentialReadyEndpoints: true,
+		},
+		{
+			name: "unready only be included if it have readinessGate and containerReady - not containerReady will signal containsPotentialReadyEndpoints",
+			env: env{
+				services:      []*corev1.Service{svc1},
+				endpointsList: []*corev1.Endpoints{ep1D},
+			},
+			fields: fields{
+				podInfoRepoGetCalls: []podInfoRepoGetCall{
+					{
+						key:    pod1.Key,
+						pod:    pod1,
+						exists: true,
+					},
+					{
+						key:    pod2.Key,
+						pod:    pod2,
+						exists: true,
+					},
+					{
+						key:    pod3.Key,
+						pod:    pod3,
+						exists: true,
+					},
+					{
+						key:    pod4.Key,
+						pod:    pod4,
+						exists: true,
+					},
+					{
+						key:    pod5.Key,
+						pod:    pod5,
+						exists: true,
+					},
+				},
+			},
+			args: args{
+				svcKey: k8s.NamespacedName(svc1),
+				port:   intstr.FromString("http"),
+				opts:   []EndpointResolveOption{WithPodReadinessGate("custom-condition")},
+			},
+			want: []PodEndpoint{
+				{
+					IP:   "192.168.1.1",
+					Port: 8080,
+					Pod:  pod1,
+				},
+				{
+					IP:   "192.168.1.2",
+					Port: 8080,
+					Pod:  pod2,
 				},
 				{
 					IP:   "192.168.1.4",
@@ -487,18 +702,24 @@ func Test_defaultEndpointResolver_ResolvePodEndpoints(t *testing.T) {
 					Pod:  pod4,
 				},
 			},
+			wantContainsPotentialReadyEndpoints: true,
 		},
 	}
 
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			ctx := context.Background()
+			ctrl := gomock.NewController(t)
+			defer ctrl.Finish()
+			podInfoRepo := mock_k8s.NewMockPodInfoRepo(ctrl)
+			for _, call := range tt.fields.podInfoRepoGetCalls {
+				podInfoRepo.EXPECT().Get(gomock.Any(), call.key).Return(call.pod, call.exists, call.err)
+			}
+
 			k8sSchema := runtime.NewScheme()
 			clientgoscheme.AddToScheme(k8sSchema)
 			k8sClient := testclient.NewFakeClientWithScheme(k8sSchema)
-			for _, pod := range tt.env.pods {
-				assert.NoError(t, k8sClient.Create(ctx, pod.DeepCopy()))
-			}
+
+			ctx := context.Background()
 			for _, svc := range tt.env.services {
 				assert.NoError(t, k8sClient.Create(ctx, svc.DeepCopy()))
 			}
@@ -507,10 +728,11 @@ func Test_defaultEndpointResolver_ResolvePodEndpoints(t *testing.T) {
 			}
 
 			r := &defaultEndpointResolver{
-				k8sClient: k8sClient,
-				logger:    ctrl.Log,
+				k8sClient:   k8sClient,
+				podInfoRepo: podInfoRepo,
+				logger:      &log.NullLogger{},
 			}
-			got, err := r.ResolvePodEndpoints(ctx, tt.args.svcKey, tt.args.port, tt.args.opts...)
+			got, gotContainsPotentialReadyEndpoints, err := r.ResolvePodEndpoints(ctx, tt.args.svcKey, tt.args.port, tt.args.opts...)
 			if tt.wantErr != nil {
 				assert.EqualError(t, err, tt.wantErr.Error())
 			} else {
@@ -523,6 +745,7 @@ func Test_defaultEndpointResolver_ResolvePodEndpoints(t *testing.T) {
 				}
 				assert.True(t, cmp.Equal(tt.want, got, opt),
 					"diff: %v", cmp.Diff(tt.want, got, opt))
+				assert.Equal(t, tt.wantContainsPotentialReadyEndpoints, gotContainsPotentialReadyEndpoints)
 			}
 		})
 	}
@@ -648,7 +871,6 @@ func Test_defaultEndpointResolver_ResolveNodePortEndpoints(t *testing.T) {
 			},
 		},
 	}
-	_ = svc2
 
 	type env struct {
 		nodes    []*corev1.Node
@@ -771,86 +993,3 @@ func Test_defaultEndpointResolver_ResolveNodePortEndpoints(t *testing.T) {
 		})
 	}
 }
-
-func Test_isPodMeetCriteria(t *testing.T) {
-	type args struct {
-		pod      *corev1.Pod
-		criteria []PodPredicate
-	}
-	tests := []struct {
-		name string
-		args args
-		want bool
-	}{
-		{
-			name: "pod is containerReady and with no readinessGate - meet criteria",
-			args: args{
-				pod: &corev1.Pod{
-					Spec: corev1.PodSpec{},
-					Status: corev1.PodStatus{
-						Conditions: []corev1.PodCondition{
-							{
-								Type:   corev1.ContainersReady,
-								Status: corev1.ConditionTrue,
-							},
-						},
-					},
-				},
-				criteria: []PodPredicate{k8s.IsPodContainersReady},
-			},
-			want: true,
-		},
-		{
-			name: "pod is containerReady and with no readinessGate - doesn't meet criteria",
-			args: args{
-				pod: &corev1.Pod{
-					Spec: corev1.PodSpec{},
-					Status: corev1.PodStatus{
-						Conditions: []corev1.PodCondition{
-							{
-								Type:   corev1.ContainersReady,
-								Status: corev1.ConditionTrue,
-							},
-						},
-					},
-				},
-				criteria: []PodPredicate{k8s.IsPodContainersReady, func(pod *corev1.Pod) bool {
-					return k8s.IsPodHasReadinessGate(pod, "alb.ingress.k8s.aws/condition")
-				}},
-			},
-			want: false,
-		},
-		{
-			name: "pod is containerReady and with readinessGate - meet criteria",
-			args: args{
-				pod: &corev1.Pod{
-					Spec: corev1.PodSpec{
-						ReadinessGates: []corev1.PodReadinessGate{
-							{
-								ConditionType: "alb.ingress.k8s.aws/condition",
-							},
-						},
-					},
-					Status: corev1.PodStatus{
-						Conditions: []corev1.PodCondition{
-							{
-								Type:   corev1.ContainersReady,
-								Status: corev1.ConditionTrue,
-							},
-						},
-					},
-				},
-				criteria: []PodPredicate{k8s.IsPodContainersReady, func(pod *corev1.Pod) bool {
-					return k8s.IsPodHasReadinessGate(pod, "alb.ingress.k8s.aws/condition")
-				}},
-			},
-			want: true,
-		},
-	}
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			got := isPodMeetCriteria(tt.args.pod, tt.args.criteria)
-			assert.Equal(t, tt.want, got)
-		})
-	}
-}
diff --git a/pkg/backend/endpoint_types_test.go b/pkg/backend/endpoint_types_test.go
index 4928a9c04..4e2466a7a 100644
--- a/pkg/backend/endpoint_types_test.go
+++ b/pkg/backend/endpoint_types_test.go
@@ -7,7 +7,7 @@ import (
 	"testing"
 )
 
-func TestWithNodeSelector(t *testing.T) {
+func TestEndpointResolveOptions_ApplyOptions(t *testing.T) {
 	tests := []struct {
 		name string
 		opts []EndpointResolveOption
@@ -20,58 +20,37 @@ func TestWithNodeSelector(t *testing.T) {
 				NodeSelector: labels.Everything(),
 			},
 		},
-	}
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			opts := EndpointResolveOptions{
-				NodeSelector: labels.Nothing(),
-			}
-			opts.ApplyOptions(tt.opts)
-			assert.Equal(t, tt.want.NodeSelector, opts.NodeSelector)
-		})
-	}
-}
-
-func TestWithUnreadyPodInclusionCriterion(t *testing.T) {
-	podPredicateTrue := func(pod *corev1.Pod) bool { return true }
-	podPredicateFalse := func(pod *corev1.Pod) bool { return false }
-	tests := []struct {
-		name string
-		opts []EndpointResolveOption
-		want EndpointResolveOptions
-	}{
 		{
-			name: "zero pod predict",
-			opts: []EndpointResolveOption{},
-			want: EndpointResolveOptions{
-				NodeSelector:                labels.Nothing(),
-				UnreadyPodInclusionCriteria: nil,
+			name: "set two readinessGate",
+			opts: []EndpointResolveOption{
+				WithPodReadinessGate("target-health.ingress.k8s.aws/some-tgb-1"),
+				WithPodReadinessGate("target-health.ingress.k8s.aws/some-tgb-2"),
 			},
-		},
-		{
-			name: "one pod predict",
-			opts: []EndpointResolveOption{WithUnreadyPodInclusionCriterion(podPredicateTrue)},
 			want: EndpointResolveOptions{
-				NodeSelector:                labels.Nothing(),
-				UnreadyPodInclusionCriteria: []PodPredicate{podPredicateTrue},
+				NodeSelector: labels.Nothing(),
+				PodReadinessGates: []corev1.PodConditionType{
+					"target-health.ingress.k8s.aws/some-tgb-1",
+					"target-health.ingress.k8s.aws/some-tgb-2",
+				},
 			},
 		},
 		{
-			name: "two pod predict",
-			opts: []EndpointResolveOption{WithUnreadyPodInclusionCriterion(podPredicateTrue), WithUnreadyPodInclusionCriterion(podPredicateFalse)},
+			name: "set labelSelector & readinessGate",
+			opts: []EndpointResolveOption{
+				WithNodeSelector(labels.Everything()),
+				WithPodReadinessGate("target-health.ingress.k8s.aws/some-tgb"),
+			},
 			want: EndpointResolveOptions{
-				NodeSelector:                labels.Nothing(),
-				UnreadyPodInclusionCriteria: []PodPredicate{podPredicateTrue, podPredicateFalse},
+				NodeSelector:      labels.Everything(),
+				PodReadinessGates: []corev1.PodConditionType{"target-health.ingress.k8s.aws/some-tgb"},
 			},
 		},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			opts := EndpointResolveOptions{
-				NodeSelector: labels.Nothing(),
-			}
+			opts := defaultEndpointResolveOptions()
 			opts.ApplyOptions(tt.opts)
-			assert.Equal(t, len(tt.want.UnreadyPodInclusionCriteria), len(opts.UnreadyPodInclusionCriteria))
+			assert.Equal(t, tt.want.NodeSelector, opts.NodeSelector)
 		})
 	}
 }
diff --git a/pkg/k8s/pod_info_repo_test.go b/pkg/k8s/pod_info_repo_test.go
new file mode 100644
index 000000000..a0e62884b
--- /dev/null
+++ b/pkg/k8s/pod_info_repo_test.go
@@ -0,0 +1,105 @@
+package k8s
+
+import (
+	"errors"
+	"github.com/stretchr/testify/assert"
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/types"
+	"testing"
+)
+
+func Test_podInfoKeyFunc(t *testing.T) {
+	type args struct {
+		obj interface{}
+	}
+	tests := []struct {
+		name    string
+		args    args
+		want    string
+		wantErr error
+	}{
+		{
+			name: "normal case",
+			args: args{
+				obj: &PodInfo{
+					Key: types.NamespacedName{Namespace: "ns-1", Name: "pod-a"},
+				},
+			},
+			want: "ns-1/pod-a",
+		},
+		{
+			name: "invalid type",
+			args: args{
+				obj: PodInfo{
+					Key: types.NamespacedName{Namespace: "ns-1", Name: "pod-a"},
+				},
+			},
+			wantErr: errors.New("expect PodInfo object"),
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got, err := podInfoKeyFunc(tt.args.obj)
+			if tt.wantErr != nil {
+				assert.EqualError(t, err, tt.wantErr.Error())
+			} else {
+				assert.NoError(t, err)
+				assert.Equal(t, tt.want, got)
+			}
+		})
+	}
+}
+
+func Test_podInfoConversionFunc(t *testing.T) {
+	type args struct {
+		obj interface{}
+	}
+	tests := []struct {
+		name    string
+		args    args
+		want    interface{}
+		wantErr error
+	}{
+		{
+			name: "normal case",
+			args: args{
+				obj: &corev1.Pod{
+					ObjectMeta: metav1.ObjectMeta{
+						Namespace: "ns-1",
+						Name:      "pod-a",
+						UID:       "pod-uuid",
+					},
+				},
+			},
+			want: &PodInfo{
+				Key: types.NamespacedName{Namespace: "ns-1", Name: "pod-a"},
+				UID: "pod-uuid",
+			},
+		},
+		{
+			name: "invalid type",
+			args: args{
+				obj: corev1.Pod{
+					ObjectMeta: metav1.ObjectMeta{
+						Namespace: "ns-1",
+						Name:      "pod-a",
+						UID:       "pod-uuid",
+					},
+				},
+			},
+			wantErr: errors.New("expect pod object"),
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got, err := podInfoConversionFunc(tt.args.obj)
+			if tt.wantErr != nil {
+				assert.EqualError(t, err, tt.wantErr.Error())
+			} else {
+				assert.NoError(t, err)
+				assert.Equal(t, tt.want, got)
+			}
+		})
+	}
+}
diff --git a/pkg/k8s/pod_info_test.go b/pkg/k8s/pod_info_test.go
new file mode 100644
index 000000000..581bdc828
--- /dev/null
+++ b/pkg/k8s/pod_info_test.go
@@ -0,0 +1,484 @@
+package k8s
+
+import (
+	"errors"
+	"github.com/stretchr/testify/assert"
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/types"
+	"k8s.io/apimachinery/pkg/util/intstr"
+	"testing"
+)
+
+func TestPodInfo_HasAnyOfReadinessGates(t *testing.T) {
+	type args struct {
+		conditionTypes []corev1.PodConditionType
+	}
+	tests := []struct {
+		name string
+		pod  PodInfo
+		args args
+		want bool
+	}{
+		{
+			name: "empty readinessGates passed-in",
+			pod: PodInfo{
+				Key: types.NamespacedName{Namespace: "ns-1", Name: "pod-1"},
+				ReadinessGates: []corev1.PodReadinessGate{
+					{
+						ConditionType: "ingress.k8s.aws/cond-1",
+					},
+				},
+			},
+			args: args{
+				conditionTypes: []corev1.PodConditionType{},
+			},
+			want: false,
+		},
+		{
+			name: "contains the readinessGates passed-in",
+			pod: PodInfo{
+				Key: types.NamespacedName{Namespace: "ns-1", Name: "pod-1"},
+				ReadinessGates: []corev1.PodReadinessGate{
+					{
+						ConditionType: "ingress.k8s.aws/cond-1",
+					},
+				},
+			},
+			args: args{
+				conditionTypes: []corev1.PodConditionType{
+					"ingress.k8s.aws/cond-1",
+				},
+			},
+			want: true,
+		},
+		{
+			name: "doesn't contain the readinessGates passed-in",
+			pod: PodInfo{
+				Key: types.NamespacedName{Namespace: "ns-1", Name: "pod-1"},
+				ReadinessGates: []corev1.PodReadinessGate{
+					{
+						ConditionType: "ingress.k8s.aws/cond-1",
+					},
+				},
+			},
+			args: args{
+				conditionTypes: []corev1.PodConditionType{
+					"ingress.k8s.aws/cond-2",
+				},
+			},
+			want: false,
+		},
+		{
+			name: "contains one of the readinessGates passed-in",
+			pod: PodInfo{
+				Key: types.NamespacedName{Namespace: "ns-1", Name: "pod-1"},
+				ReadinessGates: []corev1.PodReadinessGate{
+					{
+						ConditionType: "ingress.k8s.aws/cond-1",
+					},
+				},
+			},
+			args: args{
+				conditionTypes: []corev1.PodConditionType{
+					"ingress.k8s.aws/cond-1",
+					"ingress.k8s.aws/cond-2",
+				},
+			},
+			want: true,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got := tt.pod.HasAnyOfReadinessGates(tt.args.conditionTypes)
+			assert.Equal(t, tt.want, got)
+		})
+	}
+}
+
+func TestPodInfo_IsContainersReady(t *testing.T) {
+	tests := []struct {
+		name string
+		pod  PodInfo
+		want bool
+	}{
+		{
+			name: "pod have true containers ready condition",
+			pod: PodInfo{
+				Key: types.NamespacedName{Namespace: "ns-1", Name: "pod-1"},
+				Conditions: []corev1.PodCondition{
+					{
+						Type:   corev1.ContainersReady,
+						Status: corev1.ConditionTrue,
+					},
+				},
+			},
+			want: true,
+		},
+		{
+			name: "pod have false containers ready condition",
+			pod: PodInfo{
+				Key: types.NamespacedName{Namespace: "ns-1", Name: "pod-1"},
+				Conditions: []corev1.PodCondition{
+					{
+						Type:   corev1.ContainersReady,
+						Status: corev1.ConditionFalse,
+					},
+				},
+			},
+			want: false,
+		},
+		{
+			name: "pod don't have containers ready condition",
+			pod: PodInfo{
+				Key:        types.NamespacedName{Namespace: "ns-1", Name: "pod-1"},
+				Conditions: []corev1.PodCondition{},
+			},
+			want: false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got := tt.pod.IsContainersReady()
+			assert.Equal(t, tt.want, got)
+		})
+	}
+}
+
+func TestPodInfo_GetPodCondition(t *testing.T) {
+	type args struct {
+		conditionType corev1.PodConditionType
+	}
+	tests := []struct {
+		name       string
+		pod        PodInfo
+		args       args
+		want       corev1.PodCondition
+		wantExists bool
+	}{
+		{
+			name: "condition exists",
+			pod: PodInfo{
+				Key: types.NamespacedName{Namespace: "ns-1", Name: "pod-1"},
+				Conditions: []corev1.PodCondition{
+					{
+						Type:   corev1.ContainersReady,
+						Status: corev1.ConditionFalse,
+					},
+				},
+			},
+			args: args{
+				conditionType: corev1.ContainersReady,
+			},
+			want: corev1.PodCondition{
+				Type:   corev1.ContainersReady,
+				Status: corev1.ConditionFalse,
+			},
+			wantExists: true,
+		},
+		{
+			name: "condition doesn't exists",
+			pod: PodInfo{
+				Key: types.NamespacedName{Namespace: "ns-1", Name: "pod-1"},
+				Conditions: []corev1.PodCondition{
+					{
+						Type:   corev1.ContainersReady,
+						Status: corev1.ConditionFalse,
+					},
+				},
+			},
+			args: args{
+				conditionType: corev1.PodReady,
+			},
+			want:       corev1.PodCondition{},
+			wantExists: false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got, gotExists := tt.pod.GetPodCondition(tt.args.conditionType)
+			assert.Equal(t, tt.wantExists, gotExists)
+			assert.Equal(t, tt.want, got)
+		})
+	}
+}
+
+func TestPodInfo_LookupContainerPort(t *testing.T) {
+	type args struct {
+		port intstr.IntOrString
+	}
+	tests := []struct {
+		name    string
+		pod     PodInfo
+		args    args
+		want    int64
+		wantErr error
+	}{
+		{
+			name: "str container port exists",
+			pod: PodInfo{
+				Key: types.NamespacedName{Namespace: "ns-1", Name: "pod-1"},
+				ContainerPorts: []corev1.ContainerPort{
+					{
+						Name:          "http",
+						ContainerPort: 8080,
+					},
+					{
+						Name:          "https",
+						ContainerPort: 8443,
+					},
+				},
+			},
+			args: args{
+				port: intstr.FromString("https"),
+			},
+			want: 8443,
+		},
+		{
+			name: "str container port don't exists",
+			pod: PodInfo{
+				Key: types.NamespacedName{Namespace: "ns-1", Name: "pod-1"},
+				ContainerPorts: []corev1.ContainerPort{
+					{
+						Name:          "http",
+						ContainerPort: 8080,
+					},
+					{
+						Name:          "https",
+						ContainerPort: 8443,
+					},
+				},
+			},
+			args: args{
+				port: intstr.FromString("ssh"),
+			},
+			wantErr: errors.New("unable to find port ssh on pod ns-1/pod-1"),
+		},
+		{
+			name: "int container port exists",
+			pod: PodInfo{
+				Key: types.NamespacedName{Namespace: "ns-1", Name: "pod-1"},
+				ContainerPorts: []corev1.ContainerPort{
+					{
+						Name:          "http",
+						ContainerPort: 8080,
+					},
+					{
+						Name:          "https",
+						ContainerPort: 8443,
+					},
+				},
+			},
+			args: args{
+				port: intstr.FromInt(8080),
+			},
+			want: 8080,
+		},
+		{
+			name: "int container port don't exists",
+			pod: PodInfo{
+				Key: types.NamespacedName{Namespace: "ns-1", Name: "pod-1"},
+				ContainerPorts: []corev1.ContainerPort{
+					{
+						Name:          "http",
+						ContainerPort: 8080,
+					},
+					{
+						Name:          "https",
+						ContainerPort: 8443,
+					},
+				},
+			},
+			args: args{
+				port: intstr.FromInt(9090),
+			},
+			want: 9090,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got, err := tt.pod.LookupContainerPort(tt.args.port)
+			if tt.wantErr != nil {
+				assert.EqualError(t, err, tt.wantErr.Error())
+			} else {
+				assert.NoError(t, err)
+				assert.Equal(t, tt.want, got)
+			}
+		})
+	}
+}
+
+func Test_buildPodInfo(t *testing.T) {
+	type args struct {
+		pod *corev1.Pod
+	}
+	tests := []struct {
+		name string
+		args args
+		want PodInfo
+	}{
+		{
+			name: "standard case",
+			args: args{
+				pod: &corev1.Pod{
+					ObjectMeta: metav1.ObjectMeta{
+						Namespace: "my-ns",
+						Name:      "pod-1",
+						UID:       "pod-uuid",
+					},
+					Spec: corev1.PodSpec{
+						Containers: []corev1.Container{
+							{
+								Ports: []corev1.ContainerPort{
+									{
+										Name:          "ssh",
+										ContainerPort: 22,
+									},
+									{
+										Name:          "http",
+										ContainerPort: 8080,
+									},
+								},
+							},
+							{
+								Ports: []corev1.ContainerPort{
+									{
+										Name:          "https",
+										ContainerPort: 8443,
+									},
+								},
+							},
+						},
+						ReadinessGates: []corev1.PodReadinessGate{
+							{
+								ConditionType: "ingress.k8s.aws/cond-1",
+							},
+							{
+								ConditionType: "ingress.k8s.aws/cond-2",
+							},
+						},
+					},
+					Status: corev1.PodStatus{
+						Conditions: []corev1.PodCondition{
+							{
+								Type:   corev1.ContainersReady,
+								Status: corev1.ConditionFalse,
+							},
+							{
+								Type:   "ingress.k8s.aws/cond-2",
+								Status: corev1.ConditionTrue,
+							},
+						},
+						PodIP: "192.168.1.1",
+					},
+				},
+			},
+			want: PodInfo{
+				Key: types.NamespacedName{Namespace: "my-ns", Name: "pod-1"},
+				UID: "pod-uuid",
+				ContainerPorts: []corev1.ContainerPort{
+					{
+						Name:          "ssh",
+						ContainerPort: 22,
+					},
+					{
+						Name:          "http",
+						ContainerPort: 8080,
+					},
+					{
+						Name:          "https",
+						ContainerPort: 8443,
+					},
+				},
+				ReadinessGates: []corev1.PodReadinessGate{
+					{
+						ConditionType: "ingress.k8s.aws/cond-1",
+					},
+					{
+						ConditionType: "ingress.k8s.aws/cond-2",
+					},
+				},
+				Conditions: []corev1.PodCondition{
+					{
+						Type:   corev1.ContainersReady,
+						Status: corev1.ConditionFalse,
+					},
+					{
+						Type:   "ingress.k8s.aws/cond-2",
+						Status: corev1.ConditionTrue,
+					},
+				},
+				PodIP: "192.168.1.1",
+			},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got := buildPodInfo(tt.args.pod)
+			assert.Equal(t, tt.want, got)
+		})
+	}
+}
+
+func Test_buildPodENIInfo(t *testing.T) {
+	type args struct {
+		pod *corev1.Pod
+	}
+	tests := []struct {
+		name    string
+		args    args
+		want    *PodENIInfo
+		wantErr error
+	}{
+		//{
+		//	name: "pod-eni annotation exists and valid",
+		//	args: args{
+		//		pod: &corev1.Pod{
+		//			ObjectMeta: metav1.ObjectMeta{
+		//				Annotations: map[string]string{
+		//					"vpc.amazonaws.com/pod-eni": `[{"eniId":"eni-04df112df841b9465","ifAddress":"02:2d:47:ec:a5:e3","privateIp":"192.168.129.175","vlanId":1,"subnetCidr":"192.168.128.0/19"}]`,
+		//				},
+		//			},
+		//		},
+		//	},
+		//	want: &PodENIInfo{
+		//		ENIID: "eni-04df112df841b9465",
+		//	},
+		//},
+		{
+			name: "pod-eni annotation didn't exist",
+			args: args{
+				pod: &corev1.Pod{
+					ObjectMeta: metav1.ObjectMeta{
+						Annotations: map[string]string{},
+					},
+				},
+			},
+			want: nil,
+		},
+		{
+			name: "pod-eni annotation invalid",
+			args: args{
+				pod: &corev1.Pod{
+					ObjectMeta: metav1.ObjectMeta{
+						Annotations: map[string]string{
+							"vpc.amazonaws.com/pod-eni": ``,
+						},
+					},
+				},
+			},
+			wantErr: errors.New("unexpected end of JSON input"),
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got, err := buildPodENIInfo(tt.args.pod)
+			if tt.wantErr != nil {
+				assert.EqualError(t, err, tt.wantErr.Error())
+			} else {
+				assert.NoError(t, err)
+				assert.Equal(t, tt.want, got)
+			}
+		})
+	}
+}
diff --git a/pkg/networking/node_eni_info_resolver_test.go b/pkg/networking/node_eni_info_resolver_test.go
new file mode 100644
index 000000000..04499c0f3
--- /dev/null
+++ b/pkg/networking/node_eni_info_resolver_test.go
@@ -0,0 +1,189 @@
+package networking
+
+import (
+	"github.com/stretchr/testify/assert"
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/types"
+	"testing"
+)
+
+func Test_computeNodeENIInfoCacheKey(t *testing.T) {
+	type args struct {
+		node *corev1.Node
+	}
+	tests := []struct {
+		name string
+		args args
+		want nodeENIInfoCacheKey
+	}{
+		{
+			name: "node UID should be included as cacheKey",
+			args: args{
+				node: &corev1.Node{
+					ObjectMeta: metav1.ObjectMeta{
+						Namespace: "ns-1",
+						Name:      "node-1",
+						UID:       "uuid",
+					},
+				},
+			},
+			want: nodeENIInfoCacheKey{
+				nodeKey: types.NamespacedName{Namespace: "ns-1", Name: "node-1"},
+				nodeUID: "uuid",
+			},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got := computeNodeENIInfoCacheKey(tt.args.node)
+			assert.Equal(t, tt.want, got)
+		})
+	}
+}
+
+func Test_computeNodesWithoutENIInfo(t *testing.T) {
+	type args struct {
+		nodes            []*corev1.Node
+		eniInfoByNodeKey map[types.NamespacedName]ENIInfo
+	}
+	tests := []struct {
+		name string
+		args args
+		want []*corev1.Node
+	}{
+		{
+			name: "all nodes are resolved",
+			args: args{
+				nodes: []*corev1.Node{
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Namespace: "ns-1",
+							Name:      "node-1",
+						},
+					},
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Namespace: "ns-1",
+							Name:      "node-2",
+						},
+					},
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Namespace: "ns-2",
+							Name:      "node-1",
+						},
+					},
+				},
+				eniInfoByNodeKey: map[types.NamespacedName]ENIInfo{
+					types.NamespacedName{Namespace: "ns-1", Name: "node-1"}: {
+						NetworkInterfaceID: "eni-xx",
+					},
+					types.NamespacedName{Namespace: "ns-1", Name: "node-2"}: {
+						NetworkInterfaceID: "eni-xx",
+					},
+					types.NamespacedName{Namespace: "ns-2", Name: "node-1"}: {
+						NetworkInterfaceID: "eni-xx",
+					},
+				},
+			},
+			want: []*corev1.Node{},
+		},
+		{
+			name: "some nodes are resolved",
+			args: args{
+				nodes: []*corev1.Node{
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Namespace: "ns-1",
+							Name:      "node-1",
+						},
+					},
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Namespace: "ns-1",
+							Name:      "node-3",
+						},
+					},
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Namespace: "ns-2",
+							Name:      "node-1",
+						},
+					},
+				},
+				eniInfoByNodeKey: map[types.NamespacedName]ENIInfo{
+					types.NamespacedName{Namespace: "ns-1", Name: "node-1"}: {
+						NetworkInterfaceID: "eni-xx",
+					},
+					types.NamespacedName{Namespace: "ns-1", Name: "node-2"}: {
+						NetworkInterfaceID: "eni-xx",
+					},
+					types.NamespacedName{Namespace: "ns-2", Name: "node-1"}: {
+						NetworkInterfaceID: "eni-xx",
+					},
+				},
+			},
+			want: []*corev1.Node{
+				{
+					ObjectMeta: metav1.ObjectMeta{
+						Namespace: "ns-1",
+						Name:      "node-3",
+					},
+				},
+			},
+		},
+		{
+			name: "no nodes are resolved",
+			args: args{
+				nodes: []*corev1.Node{
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Namespace: "ns-1",
+							Name:      "node-1",
+						},
+					},
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Namespace: "ns-1",
+							Name:      "node-3",
+						},
+					},
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Namespace: "ns-2",
+							Name:      "node-1",
+						},
+					},
+				},
+				eniInfoByNodeKey: map[types.NamespacedName]ENIInfo{},
+			},
+			want: []*corev1.Node{
+				{
+					ObjectMeta: metav1.ObjectMeta{
+						Namespace: "ns-1",
+						Name:      "node-1",
+					},
+				},
+				{
+					ObjectMeta: metav1.ObjectMeta{
+						Namespace: "ns-1",
+						Name:      "node-3",
+					},
+				},
+				{
+					ObjectMeta: metav1.ObjectMeta{
+						Namespace: "ns-2",
+						Name:      "node-1",
+					},
+				},
+			},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got := computeNodesWithoutENIInfo(tt.args.nodes, tt.args.eniInfoByNodeKey)
+			assert.Equal(t, tt.want, got)
+		})
+	}
+}
diff --git a/pkg/networking/pod_eni_info_resolver_test.go b/pkg/networking/pod_eni_info_resolver_test.go
new file mode 100644
index 000000000..9100b3e55
--- /dev/null
+++ b/pkg/networking/pod_eni_info_resolver_test.go
@@ -0,0 +1,146 @@
+package networking
+
+import (
+	"github.com/stretchr/testify/assert"
+	"k8s.io/apimachinery/pkg/types"
+	"sigs.k8s.io/aws-load-balancer-controller/pkg/k8s"
+	"testing"
+)
+
+func Test_computePodENIInfoCacheKey(t *testing.T) {
+	type args struct {
+		pod k8s.PodInfo
+	}
+	tests := []struct {
+		name string
+		args args
+		want podENIInfoCacheKey
+	}{
+		{
+			name: "pods UID should be included as cacheKey",
+			args: args{
+				pod: k8s.PodInfo{
+					Key: types.NamespacedName{Namespace: "ns-1", Name: "pod-1"},
+					UID: types.UID("uuid"),
+				},
+			},
+			want: podENIInfoCacheKey{
+				podKey: types.NamespacedName{Namespace: "ns-1", Name: "pod-1"},
+				podUID: "uuid",
+			},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got := computePodENIInfoCacheKey(tt.args.pod)
+			assert.Equal(t, tt.want, got)
+		})
+	}
+}
+
+func Test_computePodsWithoutENIInfo(t *testing.T) {
+	type args struct {
+		pods            []k8s.PodInfo
+		eniInfoByPodKey map[types.NamespacedName]ENIInfo
+	}
+	tests := []struct {
+		name string
+		args args
+		want []k8s.PodInfo
+	}{
+		{
+			name: "all pods are resolved",
+			args: args{
+				pods: []k8s.PodInfo{
+					{
+						Key: types.NamespacedName{Namespace: "ns-1", Name: "pod-1"},
+					},
+					{
+						Key: types.NamespacedName{Namespace: "ns-1", Name: "pod-2"},
+					},
+					{
+						Key: types.NamespacedName{Namespace: "ns-2", Name: "pod-1"},
+					},
+				},
+				eniInfoByPodKey: map[types.NamespacedName]ENIInfo{
+					types.NamespacedName{Namespace: "ns-1", Name: "pod-1"}: {
+						NetworkInterfaceID: "eni-xx",
+					},
+					types.NamespacedName{Namespace: "ns-1", Name: "pod-2"}: {
+						NetworkInterfaceID: "eni-xx",
+					},
+					types.NamespacedName{Namespace: "ns-2", Name: "pod-1"}: {
+						NetworkInterfaceID: "eni-xx",
+					},
+				},
+			},
+			want: []k8s.PodInfo{},
+		},
+		{
+			name: "some pods are resolved",
+			args: args{
+				pods: []k8s.PodInfo{
+					{
+						Key: types.NamespacedName{Namespace: "ns-1", Name: "pod-1"},
+					},
+					{
+						Key: types.NamespacedName{Namespace: "ns-1", Name: "pod-3"},
+					},
+					{
+						Key: types.NamespacedName{Namespace: "ns-2", Name: "pod-1"},
+					},
+				},
+				eniInfoByPodKey: map[types.NamespacedName]ENIInfo{
+					types.NamespacedName{Namespace: "ns-1", Name: "pod-1"}: {
+						NetworkInterfaceID: "eni-xx",
+					},
+					types.NamespacedName{Namespace: "ns-1", Name: "pod-2"}: {
+						NetworkInterfaceID: "eni-xx",
+					},
+					types.NamespacedName{Namespace: "ns-2", Name: "pod-1"}: {
+						NetworkInterfaceID: "eni-xx",
+					},
+				},
+			},
+			want: []k8s.PodInfo{
+				{
+					Key: types.NamespacedName{Namespace: "ns-1", Name: "pod-3"},
+				},
+			},
+		},
+		{
+			name: "no pods are resolved",
+			args: args{
+				pods: []k8s.PodInfo{
+					{
+						Key: types.NamespacedName{Namespace: "ns-1", Name: "pod-1"},
+					},
+					{
+						Key: types.NamespacedName{Namespace: "ns-1", Name: "pod-3"},
+					},
+					{
+						Key: types.NamespacedName{Namespace: "ns-2", Name: "pod-1"},
+					},
+				},
+				eniInfoByPodKey: map[types.NamespacedName]ENIInfo{},
+			},
+			want: []k8s.PodInfo{
+				{
+					Key: types.NamespacedName{Namespace: "ns-1", Name: "pod-1"},
+				},
+				{
+					Key: types.NamespacedName{Namespace: "ns-1", Name: "pod-3"},
+				},
+				{
+					Key: types.NamespacedName{Namespace: "ns-2", Name: "pod-1"},
+				},
+			},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got := computePodsWithoutENIInfo(tt.args.pods, tt.args.eniInfoByPodKey)
+			assert.Equal(t, tt.want, got)
+		})
+	}
+}
diff --git a/pkg/runtime/errors_test.go b/pkg/runtime/errors_test.go
index 1aa032d57..b8c034bd4 100644
--- a/pkg/runtime/errors_test.go
+++ b/pkg/runtime/errors_test.go
@@ -1,94 +1,61 @@
 package runtime
 
 import (
-	"github.com/pkg/errors"
 	"github.com/stretchr/testify/assert"
 	"testing"
 	"time"
 )
 
-func TestNewRequeueError(t *testing.T) {
+func TestNewRequeueNeeded(t *testing.T) {
 	type args struct {
-		err error
+		msg string
 	}
 	tests := []struct {
 		name       string
 		args       args
-		wantErr    string
-		wantUnwrap error
+		wantReason string
 	}{
 		{
-			name: "wraps non-nil error",
+			name: "standard case",
 			args: args{
-				err: errors.New("some error"),
+				msg: "some message",
 			},
-			wantErr:    "some error",
-			wantUnwrap: errors.New("some error"),
-		},
-		{
-			name: "wraps nil error",
-			args: args{
-				err: nil,
-			},
-			wantErr:    "",
-			wantUnwrap: nil,
+			wantReason: "some message",
 		},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			got := NewRequeueError(tt.args.err)
-			assert.Equal(t, tt.wantErr, got.Error())
-			if tt.wantUnwrap != nil {
-				assert.EqualError(t, got.Unwrap(), tt.wantUnwrap.Error())
-			} else {
-				assert.NoError(t, got.Unwrap())
-			}
+			got := NewRequeueNeeded(tt.args.msg)
+			assert.Equal(t, tt.wantReason, got.Reason())
 		})
 	}
 }
 
-func TestNewRequeueAfterError(t *testing.T) {
+func TestNewRequeueNeededAfter(t *testing.T) {
 	type args struct {
-		err      error
+		msg      string
 		duration time.Duration
 	}
 	tests := []struct {
 		name         string
 		args         args
-		wantErr      string
-		wantUnwrap   error
+		wantReason   string
 		wantDuration time.Duration
 	}{
 		{
-			name: "wraps non-nil error",
-			args: args{
-				err:      errors.New("some error"),
-				duration: 3 * time.Second,
-			},
-			wantErr:      "some error",
-			wantUnwrap:   errors.New("some error"),
-			wantDuration: 3 * time.Second,
-		},
-		{
-			name: "wraps nil error",
+			name: "standard case",
 			args: args{
-				err:      nil,
+				msg:      "some message",
 				duration: 3 * time.Second,
 			},
-			wantErr:      "",
-			wantUnwrap:   nil,
+			wantReason:   "some message",
 			wantDuration: 3 * time.Second,
 		},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			got := NewRequeueAfterError(tt.args.err, tt.args.duration)
-			assert.Equal(t, tt.wantErr, got.Error())
-			if tt.wantUnwrap != nil {
-				assert.EqualError(t, got.Unwrap(), tt.wantUnwrap.Error())
-			} else {
-				assert.NoError(t, got.Unwrap())
-			}
+			got := NewRequeueNeededAfter(tt.args.msg, tt.args.duration)
+			assert.Equal(t, tt.wantReason, got.Reason())
 			assert.Equal(t, 3*time.Second, got.Duration())
 		})
 	}
diff --git a/pkg/runtime/reconcile_test.go b/pkg/runtime/reconcile_test.go
index 09ec35e22..3b1767302 100644
--- a/pkg/runtime/reconcile_test.go
+++ b/pkg/runtime/reconcile_test.go
@@ -28,9 +28,9 @@ func TestHandleReconcileError(t *testing.T) {
 			wantErr: nil,
 		},
 		{
-			name: "input err is RequeueAfterError",
+			name: "input err is RequeueNeededAfter",
 			args: args{
-				err: NewRequeueAfterError(errors.New("some error"), 3*time.Second),
+				err: NewRequeueNeededAfter("some error", 3*time.Second),
 			},
 			want: ctrl.Result{
 				RequeueAfter: 3 * time.Second,
@@ -38,9 +38,9 @@ func TestHandleReconcileError(t *testing.T) {
 			wantErr: nil,
 		},
 		{
-			name: "input err is RequeueError",
+			name: "input err is RequeueNeeded",
 			args: args{
-				err: NewRequeueError(errors.New("some error")),
+				err: NewRequeueNeeded("some error"),
 			},
 			want: ctrl.Result{
 				Requeue: true,
diff --git a/pkg/targetgroupbinding/networking_manager_test.go b/pkg/targetgroupbinding/networking_manager_test.go
index 6c895052e..9536b7024 100644
--- a/pkg/targetgroupbinding/networking_manager_test.go
+++ b/pkg/targetgroupbinding/networking_manager_test.go
@@ -2,14 +2,15 @@ package targetgroupbinding
 
 import (
 	"context"
+	"errors"
 	awssdk "github.com/aws/aws-sdk-go/aws"
 	ec2sdk "github.com/aws/aws-sdk-go/service/ec2"
-	"github.com/pkg/errors"
 	"github.com/stretchr/testify/assert"
 	corev1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/types"
 	"k8s.io/apimachinery/pkg/util/intstr"
 	elbv2api "sigs.k8s.io/aws-load-balancer-controller/apis/elbv2/v1beta1"
+	"sigs.k8s.io/aws-load-balancer-controller/pkg/k8s"
 	"sigs.k8s.io/aws-load-balancer-controller/pkg/networking"
 	"testing"
 )
@@ -19,7 +20,7 @@ func Test_defaultNetworkingManager_computeIngressPermissionsForTGBNetworking(t *
 	port8443 := intstr.FromInt(8443)
 	type args struct {
 		tgbNetworking elbv2api.TargetGroupBindingNetworking
-		pods          []*corev1.Pod
+		pods          []k8s.PodInfo
 	}
 	tests := []struct {
 		name    string
@@ -244,7 +245,7 @@ func Test_defaultNetworkingManager_computePermissionsForPeerPort(t *testing.T) {
 	type args struct {
 		peer elbv2api.NetworkingPeer
 		port elbv2api.NetworkingPort
-		pods []*corev1.Pod
+		pods []k8s.PodInfo
 	}
 	tests := []struct {
 		name    string
@@ -357,32 +358,22 @@ func Test_defaultNetworkingManager_computePermissionsForPeerPort(t *testing.T) {
 					Protocol: &protocolUDP,
 					Port:     &portHTTP,
 				},
-				pods: []*corev1.Pod{
+				pods: []k8s.PodInfo{
 					{
-						Spec: corev1.PodSpec{
-							Containers: []corev1.Container{
-								{
-									Ports: []corev1.ContainerPort{
-										{
-											Name:          "http",
-											ContainerPort: 80,
-										},
-									},
-								},
+						Key: types.NamespacedName{Namespace: "ns-1", Name: "pod-1"},
+						ContainerPorts: []corev1.ContainerPort{
+							{
+								Name:          "http",
+								ContainerPort: 80,
 							},
 						},
 					},
 					{
-						Spec: corev1.PodSpec{
-							Containers: []corev1.Container{
-								{
-									Ports: []corev1.ContainerPort{
-										{
-											Name:          "http",
-											ContainerPort: 8080,
-										},
-									},
-								},
+						Key: types.NamespacedName{Namespace: "ns-1", Name: "pod-2"},
+						ContainerPorts: []corev1.ContainerPort{
+							{
+								Name:          "http",
+								ContainerPort: 8080,
 							},
 						},
 					},
@@ -498,7 +489,7 @@ func Test_defaultNetworkingManager_computePermissionsForPeerPort(t *testing.T) {
 func Test_defaultNetworkingManager_computeNumericalPorts(t *testing.T) {
 	type args struct {
 		port intstr.IntOrString
-		pods []*corev1.Pod
+		pods []k8s.PodInfo
 	}
 	tests := []struct {
 		name    string
@@ -518,32 +509,22 @@ func Test_defaultNetworkingManager_computeNumericalPorts(t *testing.T) {
 			name: "named port resolves to same numerical port",
 			args: args{
 				port: intstr.FromString("http"),
-				pods: []*corev1.Pod{
+				pods: []k8s.PodInfo{
 					{
-						Spec: corev1.PodSpec{
-							Containers: []corev1.Container{
-								{
-									Ports: []corev1.ContainerPort{
-										{
-											Name:          "http",
-											ContainerPort: 80,
-										},
-									},
-								},
+						Key: types.NamespacedName{Namespace: "ns-1", Name: "pod-1"},
+						ContainerPorts: []corev1.ContainerPort{
+							{
+								Name:          "http",
+								ContainerPort: 80,
 							},
 						},
 					},
 					{
-						Spec: corev1.PodSpec{
-							Containers: []corev1.Container{
-								{
-									Ports: []corev1.ContainerPort{
-										{
-											Name:          "http",
-											ContainerPort: 80,
-										},
-									},
-								},
+						Key: types.NamespacedName{Namespace: "ns-1", Name: "pod-2"},
+						ContainerPorts: []corev1.ContainerPort{
+							{
+								Name:          "http",
+								ContainerPort: 80,
 							},
 						},
 					},
@@ -555,32 +536,22 @@ func Test_defaultNetworkingManager_computeNumericalPorts(t *testing.T) {
 			name: "named port resolves to different numerical port",
 			args: args{
 				port: intstr.FromString("http"),
-				pods: []*corev1.Pod{
+				pods: []k8s.PodInfo{
 					{
-						Spec: corev1.PodSpec{
-							Containers: []corev1.Container{
-								{
-									Ports: []corev1.ContainerPort{
-										{
-											Name:          "http",
-											ContainerPort: 80,
-										},
-									},
-								},
+						Key: types.NamespacedName{Namespace: "ns-1", Name: "pod-1"},
+						ContainerPorts: []corev1.ContainerPort{
+							{
+								Name:          "http",
+								ContainerPort: 80,
 							},
 						},
 					},
 					{
-						Spec: corev1.PodSpec{
-							Containers: []corev1.Container{
-								{
-									Ports: []corev1.ContainerPort{
-										{
-											Name:          "http",
-											ContainerPort: 8080,
-										},
-									},
-								},
+						Key: types.NamespacedName{Namespace: "ns-1", Name: "pod-2"},
+						ContainerPorts: []corev1.ContainerPort{
+							{
+								Name:          "http",
+								ContainerPort: 8080,
 							},
 						},
 					},
diff --git a/pkg/targetgroupbinding/resource_manager_test.go b/pkg/targetgroupbinding/resource_manager_test.go
index bb51a1a69..c7de3581f 100644
--- a/pkg/targetgroupbinding/resource_manager_test.go
+++ b/pkg/targetgroupbinding/resource_manager_test.go
@@ -10,6 +10,7 @@ import (
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/apimachinery/pkg/types"
 	clientgoscheme "k8s.io/client-go/kubernetes/scheme"
 	"sigs.k8s.io/aws-load-balancer-controller/pkg/equality"
 	"sigs.k8s.io/aws-load-balancer-controller/pkg/k8s"
@@ -19,14 +20,19 @@ import (
 )
 
 func Test_defaultResourceManager_updateTargetHealthPodConditionForPod(t *testing.T) {
+	type env struct {
+		pods []*corev1.Pod
+	}
+
 	type args struct {
-		pod                  *corev1.Pod
+		pod                  k8s.PodInfo
 		targetHealth         *elbv2sdk.TargetHealth
 		targetHealthCondType corev1.PodConditionType
 	}
 
 	tests := []struct {
 		name    string
+		env     env
 		args    args
 		want    bool
 		wantPod *corev1.Pod
@@ -34,28 +40,48 @@ func Test_defaultResourceManager_updateTargetHealthPodConditionForPod(t *testing
 	}{
 		{
 			name: "pod contains readinessGate and targetHealth is healthy - add pod condition",
-			args: args{
-				pod: &corev1.Pod{
-					ObjectMeta: metav1.ObjectMeta{
-						Namespace: "default",
-						Name:      "my-pod",
-					},
-					Spec: corev1.PodSpec{
-						ReadinessGates: []corev1.PodReadinessGate{
-							{
-								ConditionType: "target-health.elbv2.k8s.aws/my-tgb",
+			env: env{
+				pods: []*corev1.Pod{
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Namespace: "default",
+							Name:      "my-pod",
+							UID:       "my-pod-uuid",
+						},
+						Spec: corev1.PodSpec{
+							ReadinessGates: []corev1.PodReadinessGate{
+								{
+									ConditionType: "target-health.elbv2.k8s.aws/my-tgb",
+								},
 							},
 						},
-					},
-					Status: corev1.PodStatus{
-						Conditions: []corev1.PodCondition{
-							{
-								Type:   corev1.ContainersReady,
-								Status: corev1.ConditionTrue,
+						Status: corev1.PodStatus{
+							Conditions: []corev1.PodCondition{
+								{
+									Type:   corev1.ContainersReady,
+									Status: corev1.ConditionTrue,
+								},
 							},
 						},
 					},
 				},
+			},
+			args: args{
+				pod: k8s.PodInfo{
+					Key: types.NamespacedName{Namespace: "default", Name: "my-pod"},
+					UID: "my-pod-uuid",
+					ReadinessGates: []corev1.PodReadinessGate{
+						{
+							ConditionType: "target-health.elbv2.k8s.aws/my-tgb",
+						},
+					},
+					Conditions: []corev1.PodCondition{
+						{
+							Type:   corev1.ContainersReady,
+							Status: corev1.ConditionTrue,
+						},
+					},
+				},
 				targetHealth: &elbv2sdk.TargetHealth{
 					State: awssdk.String(elbv2sdk.TargetHealthStateEnumHealthy),
 				},
@@ -66,6 +92,7 @@ func Test_defaultResourceManager_updateTargetHealthPodConditionForPod(t *testing
 				ObjectMeta: metav1.ObjectMeta{
 					Namespace: "default",
 					Name:      "my-pod",
+					UID:       "my-pod-uuid",
 				},
 				Spec: corev1.PodSpec{
 					ReadinessGates: []corev1.PodReadinessGate{
@@ -77,11 +104,11 @@ func Test_defaultResourceManager_updateTargetHealthPodConditionForPod(t *testing
 				Status: corev1.PodStatus{
 					Conditions: []corev1.PodCondition{
 						{
-							Type:   corev1.ContainersReady,
+							Type:   "target-health.elbv2.k8s.aws/my-tgb",
 							Status: corev1.ConditionTrue,
 						},
 						{
-							Type:   "target-health.elbv2.k8s.aws/my-tgb",
+							Type:   corev1.ContainersReady,
 							Status: corev1.ConditionTrue,
 						},
 					},
@@ -90,34 +117,56 @@ func Test_defaultResourceManager_updateTargetHealthPodConditionForPod(t *testing
 		},
 		{
 			name: "pod contains readinessGate and targetHealth is healthy - update pod condition",
-			args: args{
-				pod: &corev1.Pod{
-					ObjectMeta: metav1.ObjectMeta{
-						Namespace: "default",
-						Name:      "my-pod",
-					},
-					Spec: corev1.PodSpec{
-						ReadinessGates: []corev1.PodReadinessGate{
-							{
-								ConditionType: "target-health.elbv2.k8s.aws/my-tgb",
-							},
+			env: env{
+				pods: []*corev1.Pod{
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Namespace: "default",
+							Name:      "my-pod",
+							UID:       "my-pod-uuid",
 						},
-					},
-					Status: corev1.PodStatus{
-						Conditions: []corev1.PodCondition{
-							{
-								Type:   corev1.ContainersReady,
-								Status: corev1.ConditionTrue,
+						Spec: corev1.PodSpec{
+							ReadinessGates: []corev1.PodReadinessGate{
+								{
+									ConditionType: "target-health.elbv2.k8s.aws/my-tgb",
+								},
 							},
-							{
-								Type:    "target-health.elbv2.k8s.aws/my-tgb",
-								Status:  corev1.ConditionFalse,
-								Reason:  elbv2sdk.TargetHealthReasonEnumElbRegistrationInProgress,
-								Message: "Target registration is in progress",
+						},
+						Status: corev1.PodStatus{
+							Conditions: []corev1.PodCondition{
+								{
+									Type:   "target-health.elbv2.k8s.aws/my-tgb",
+									Status: corev1.ConditionFalse,
+								},
+								{
+									Type:   corev1.ContainersReady,
+									Status: corev1.ConditionTrue,
+								},
 							},
 						},
 					},
 				},
+			},
+			args: args{
+				pod: k8s.PodInfo{
+					Key: types.NamespacedName{Namespace: "default", Name: "my-pod"},
+					UID: "my-pod-uuid",
+					ReadinessGates: []corev1.PodReadinessGate{
+						{
+							ConditionType: "target-health.elbv2.k8s.aws/my-tgb",
+						},
+					},
+					Conditions: []corev1.PodCondition{
+						{
+							Type:   "target-health.elbv2.k8s.aws/my-tgb",
+							Status: corev1.ConditionFalse,
+						},
+						{
+							Type:   corev1.ContainersReady,
+							Status: corev1.ConditionTrue,
+						},
+					},
+				},
 				targetHealth: &elbv2sdk.TargetHealth{
 					State: awssdk.String(elbv2sdk.TargetHealthStateEnumHealthy),
 				},
@@ -128,6 +177,7 @@ func Test_defaultResourceManager_updateTargetHealthPodConditionForPod(t *testing
 				ObjectMeta: metav1.ObjectMeta{
 					Namespace: "default",
 					Name:      "my-pod",
+					UID:       "my-pod-uuid",
 				},
 				Spec: corev1.PodSpec{
 					ReadinessGates: []corev1.PodReadinessGate{
@@ -139,11 +189,11 @@ func Test_defaultResourceManager_updateTargetHealthPodConditionForPod(t *testing
 				Status: corev1.PodStatus{
 					Conditions: []corev1.PodCondition{
 						{
-							Type:   corev1.ContainersReady,
+							Type:   "target-health.elbv2.k8s.aws/my-tgb",
 							Status: corev1.ConditionTrue,
 						},
 						{
-							Type:   "target-health.elbv2.k8s.aws/my-tgb",
+							Type:   corev1.ContainersReady,
 							Status: corev1.ConditionTrue,
 						},
 					},
@@ -151,35 +201,61 @@ func Test_defaultResourceManager_updateTargetHealthPodConditionForPod(t *testing
 			},
 		},
 		{
-			name: "pod contains readinessGate and targetHealth is unhealthy - add pod condition",
-			args: args{
-				pod: &corev1.Pod{
-					ObjectMeta: metav1.ObjectMeta{
-						Namespace: "default",
-						Name:      "my-pod",
-					},
-					Spec: corev1.PodSpec{
-						ReadinessGates: []corev1.PodReadinessGate{
-							{
-								ConditionType: "target-health.elbv2.k8s.aws/my-tgb",
-							},
+			name: "pod contains readinessGate and targetHealth is unhealthy - update pod condition",
+			env: env{
+				pods: []*corev1.Pod{
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Namespace: "default",
+							Name:      "my-pod",
+							UID:       "my-pod-uuid",
 						},
-					},
-					Status: corev1.PodStatus{
-						Conditions: []corev1.PodCondition{
-							{
-								Type:   corev1.ContainersReady,
-								Status: corev1.ConditionTrue,
+						Spec: corev1.PodSpec{
+							ReadinessGates: []corev1.PodReadinessGate{
+								{
+									ConditionType: "target-health.elbv2.k8s.aws/my-tgb",
+								},
 							},
-							{
-								Type:    "target-health.elbv2.k8s.aws/my-tgb",
-								Status:  corev1.ConditionFalse,
-								Reason:  elbv2sdk.TargetHealthReasonEnumElbRegistrationInProgress,
-								Message: "Target registration is in progress",
+						},
+						Status: corev1.PodStatus{
+							Conditions: []corev1.PodCondition{
+								{
+									Type:    "target-health.elbv2.k8s.aws/my-tgb",
+									Status:  corev1.ConditionFalse,
+									Reason:  elbv2sdk.TargetHealthReasonEnumElbRegistrationInProgress,
+									Message: "Target registration is in progress",
+								},
+								{
+									Type:   corev1.ContainersReady,
+									Status: corev1.ConditionTrue,
+								},
 							},
 						},
 					},
 				},
+			},
+			args: args{
+				pod: k8s.PodInfo{
+					Key: types.NamespacedName{Namespace: "default", Name: "my-pod"},
+					UID: "my-pod-uuid",
+					ReadinessGates: []corev1.PodReadinessGate{
+						{
+							ConditionType: "target-health.elbv2.k8s.aws/my-tgb",
+						},
+					},
+					Conditions: []corev1.PodCondition{
+						{
+							Type:    "target-health.elbv2.k8s.aws/my-tgb",
+							Status:  corev1.ConditionFalse,
+							Reason:  elbv2sdk.TargetHealthReasonEnumElbRegistrationInProgress,
+							Message: "Target registration is in progress",
+						},
+						{
+							Type:   corev1.ContainersReady,
+							Status: corev1.ConditionTrue,
+						},
+					},
+				},
 				targetHealth: &elbv2sdk.TargetHealth{
 					State:       awssdk.String(elbv2sdk.TargetHealthStateEnumUnhealthy),
 					Reason:      awssdk.String(elbv2sdk.TargetHealthReasonEnumTargetFailedHealthChecks),
@@ -192,6 +268,7 @@ func Test_defaultResourceManager_updateTargetHealthPodConditionForPod(t *testing
 				ObjectMeta: metav1.ObjectMeta{
 					Namespace: "default",
 					Name:      "my-pod",
+					UID:       "my-pod-uuid",
 				},
 				Spec: corev1.PodSpec{
 					ReadinessGates: []corev1.PodReadinessGate{
@@ -202,44 +279,64 @@ func Test_defaultResourceManager_updateTargetHealthPodConditionForPod(t *testing
 				},
 				Status: corev1.PodStatus{
 					Conditions: []corev1.PodCondition{
-						{
-							Type:   corev1.ContainersReady,
-							Status: corev1.ConditionTrue,
-						},
 						{
 							Type:    "target-health.elbv2.k8s.aws/my-tgb",
 							Status:  corev1.ConditionFalse,
 							Reason:  elbv2sdk.TargetHealthReasonEnumTargetFailedHealthChecks,
 							Message: "Health checks failed",
 						},
+						{
+							Type:   corev1.ContainersReady,
+							Status: corev1.ConditionTrue,
+						},
 					},
 				},
 			},
 		},
 		{
-			name: "pod contains readinessGate and targetHealth is nil - currentPod has no condition",
-			args: args{
-				pod: &corev1.Pod{
-					ObjectMeta: metav1.ObjectMeta{
-						Namespace: "default",
-						Name:      "my-pod",
-					},
-					Spec: corev1.PodSpec{
-						ReadinessGates: []corev1.PodReadinessGate{
-							{
-								ConditionType: "target-health.elbv2.k8s.aws/my-tgb",
+			name: "pod contains readinessGate and targetHealth is nil",
+			env: env{
+				pods: []*corev1.Pod{
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Namespace: "default",
+							Name:      "my-pod",
+							UID:       "my-pod-uuid",
+						},
+						Spec: corev1.PodSpec{
+							ReadinessGates: []corev1.PodReadinessGate{
+								{
+									ConditionType: "target-health.elbv2.k8s.aws/my-tgb",
+								},
 							},
 						},
-					},
-					Status: corev1.PodStatus{
-						Conditions: []corev1.PodCondition{
-							{
-								Type:   corev1.ContainersReady,
-								Status: corev1.ConditionTrue,
+						Status: corev1.PodStatus{
+							Conditions: []corev1.PodCondition{
+								{
+									Type:   corev1.ContainersReady,
+									Status: corev1.ConditionTrue,
+								},
 							},
 						},
 					},
 				},
+			},
+			args: args{
+				pod: k8s.PodInfo{
+					Key: types.NamespacedName{Namespace: "default", Name: "my-pod"},
+					UID: "my-pod-uuid",
+					ReadinessGates: []corev1.PodReadinessGate{
+						{
+							ConditionType: "target-health.elbv2.k8s.aws/my-tgb",
+						},
+					},
+					Conditions: []corev1.PodCondition{
+						{
+							Type:   corev1.ContainersReady,
+							Status: corev1.ConditionTrue,
+						},
+					},
+				},
 				targetHealth:         nil,
 				targetHealthCondType: "target-health.elbv2.k8s.aws/my-tgb",
 			},
@@ -248,6 +345,7 @@ func Test_defaultResourceManager_updateTargetHealthPodConditionForPod(t *testing
 				ObjectMeta: metav1.ObjectMeta{
 					Namespace: "default",
 					Name:      "my-pod",
+					UID:       "my-pod-uuid",
 				},
 				Spec: corev1.PodSpec{
 					ReadinessGates: []corev1.PodReadinessGate{
@@ -258,14 +356,14 @@ func Test_defaultResourceManager_updateTargetHealthPodConditionForPod(t *testing
 				},
 				Status: corev1.PodStatus{
 					Conditions: []corev1.PodCondition{
-						{
-							Type:   corev1.ContainersReady,
-							Status: corev1.ConditionTrue,
-						},
 						{
 							Type:   "target-health.elbv2.k8s.aws/my-tgb",
 							Status: corev1.ConditionUnknown,
 						},
+						{
+							Type:   corev1.ContainersReady,
+							Status: corev1.ConditionTrue,
+						},
 					},
 				},
 			},
@@ -283,12 +381,13 @@ func Test_defaultResourceManager_updateTargetHealthPodConditionForPod(t *testing
 			}
 
 			ctx := context.Background()
-			pod := tt.args.pod.DeepCopy()
-			err := k8sClient.Create(ctx, pod)
-			assert.NoError(t, err)
+			for _, pod := range tt.env.pods {
+				err := k8sClient.Create(ctx, pod.DeepCopy())
+				assert.NoError(t, err)
+			}
 
 			got, err := m.updateTargetHealthPodConditionForPod(context.Background(),
-				pod, tt.args.targetHealth, tt.args.targetHealthCondType)
+				tt.args.pod, tt.args.targetHealth, tt.args.targetHealthCondType)
 			if tt.wantErr != nil {
 				assert.EqualError(t, err, tt.wantErr.Error())
 			} else {
@@ -296,7 +395,7 @@ func Test_defaultResourceManager_updateTargetHealthPodConditionForPod(t *testing
 				assert.Equal(t, tt.want, got)
 
 				updatedPod := &corev1.Pod{}
-				err := m.k8sClient.Get(ctx, k8s.NamespacedName(pod), updatedPod)
+				err := m.k8sClient.Get(ctx, tt.args.pod.Key, updatedPod)
 				assert.NoError(t, err)
 
 				opts := cmp.Options{
@@ -358,3 +457,46 @@ func Test_containsTargetsInInitialState(t *testing.T) {
 		})
 	}
 }
+
+func Test_buildPodConditionPatch(t *testing.T) {
+	type args struct {
+		pod       k8s.PodInfo
+		condition corev1.PodCondition
+	}
+	tests := []struct {
+		name      string
+		args      args
+		wantPatch []byte
+		wantErr   error
+	}{
+		{
+			name: "standard case",
+			args: args{
+				pod: k8s.PodInfo{
+					Key: types.NamespacedName{Namespace: "ns-1", Name: "pod-1"},
+					UID: "pod-uuid",
+				},
+				condition: corev1.PodCondition{
+					Type:    "custom-condition",
+					Status:  corev1.ConditionTrue,
+					Reason:  "some-reason",
+					Message: "some-msg",
+				},
+			},
+			wantPatch: []byte(`{"metadata":{"uid":"pod-uuid"},"status":{"conditions":[{"lastProbeTime":null,"lastTransitionTime":null,"message":"some-msg","reason":"some-reason","status":"True","type":"custom-condition"}]}}`),
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got, err := buildPodConditionPatch(tt.args.pod, tt.args.condition)
+			if tt.wantErr != nil {
+				assert.EqualError(t, err, tt.wantErr.Error())
+			} else {
+				assert.NoError(t, err)
+				gotPatch, _ := got.Data(nil)
+				assert.Equal(t, tt.wantPatch, gotPatch)
+				assert.Equal(t, types.StrategicMergePatchType, got.Type())
+			}
+		})
+	}
+}