Repository: openyurtio/openyurt
Pull request #2165 (2024-10-11 02:44:01)

Add yurthub service env updater filter
<!--  Thanks for sending a pull request!  Here are some tips for you:
https://github.com/openyurtio/openyurt/blob/master/CONTRIBUTING.md 
-->

#### What type of PR is this?
> Uncomment only one `/kind <>` line, hit enter to put that in a new line, and remove leading whitespace from that line:
> /kind bug
> /kind documentation
> /kind enhancement
> /kind good-first-issue
> /kind feature
> /kind question
> /kind design
> /sig ai
> /sig iot
> /sig network
> /sig storage

/kind feature

#### What this PR does / why we need it:

Adds a filter in Yurthub that sets `KUBERNETES_SERVICE_HOST` and  `KUBERNETES_SERVICE_PORT` environment variable to the Yurthub proxy host and port. This enhancement aims to ensure node autonomy for edge nodes when they are operating offline.

Incluster client testing using image built from this PR:
![image](https://github.com/user-attachments/assets/e78fffa4-92ae-4fd1-aead-e16861e6fcd5)
We can see that both  `KUBERNETES_SERVICE_HOST` and  `KUBERNETES_SERVICE_PORT` are updated with the correct values

#### Which issue(s) this PR fixes:
<!--
*Automatically closes linked issue when PR is merged.
Usage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.
-->
Fixes https://github.com/openyurtio/openyurt/issues/2158

#### Special notes for your reviewer:
<!--
use this label to assign your reviewer
/assign @your_reviewer
-->

/assign @rambohe-ch 

#### Does this PR introduce a user-facing change?
<!--
If no, just write "NONE" in the release-note block below.
If yes, a release note is required:
Enter your extended release note in the block below. If the PR requires additional action from users switching to the new release, include the string "action required".

-->
```release-note

NONE

```

#### other Note
<!--
If your current PR is still working in process, start the PR title name with [WIP], such as: [WIP] add new crd for yurt-app-manager
If the PR title name begins with [WIP], OpenYurt-bot automatically adds a do-not-merge/work-in-progress label for your pr 
-->

I noticed that documentation on new features are added to the changelog.md when a new version is created eg. https://github.com/openyurtio/openyurt/pull/1425/files. But if the maintainers prefer that this is added as a part of this PR please let me know,

sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=openyurtio_openyurt&pullRequest=2165) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=openyurtio_openyurt&pullRequest=2165&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=openyurtio_openyurt&pullRequest=2165&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=openyurtio_openyurt&pullRequest=2165&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=openyurtio_openyurt&pullRequest=2165&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=openyurtio_openyurt&pullRequest=2165&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=openyurtio_openyurt&pullRequest=2165)


codecov[bot]: ## [Codecov](https://app.codecov.io/gh/openyurtio/openyurt/pull/2165?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=openyurtio) Report
Attention: Patch coverage is `92.30769%` with `3 lines` in your changes missing coverage. Please review.
> Project coverage is 58.99%. Comparing base [(`3713163`)](https://app.codecov.io/gh/openyurtio/openyurt/commit/3713163bb07e957f5a0ffd59e6489beeb53451dc?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=openyurtio) to head [(`f0e22c4`)](https://app.codecov.io/gh/openyurtio/openyurt/commit/f0e22c4cd99c576d5fa1fbc7cba7e0a5b5d87e00?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=openyurtio).
> Report is 37 commits behind head on master.

| [Files with missing lines](https://app.codecov.io/gh/openyurtio/openyurt/pull/2165?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=openyurtio) | Patch % | Lines |
|---|---|---|
| [pkg/yurthub/filter/serviceenvupdater/filter.go](https://app.codecov.io/gh/openyurtio/openyurt/pull/2165?src=pr&el=tree&filepath=pkg%2Fyurthub%2Ffilter%2Fserviceenvupdater%2Ffilter.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=openyurtio#diff-cGtnL3l1cnRodWIvZmlsdGVyL3NlcnZpY2VlbnZ1cGRhdGVyL2ZpbHRlci5nbw==) | 92.10% | [2 Missing and 1 partial :warning: ](https://app.codecov.io/gh/openyurtio/openyurt/pull/2165?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=openyurtio) |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master    #2165      +/-   ##
==========================================
+ Coverage   56.09%   58.99%   +2.89%     
==========================================
  Files         186      211      +25     
  Lines       18092    19007     +915     
==========================================
+ Hits        10149    11213    +1064     
+ Misses       6910     6710     -200     
- Partials     1033     1084      +51     
```

| [Flag](https://app.codecov.io/gh/openyurtio/openyurt/pull/2165/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=openyurtio) | Coverage Δ | |
|---|---|---|
| [unittests](https://app.codecov.io/gh/openyurtio/openyurt/pull/2165/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=openyurtio) | `58.99% <92.30%> (+2.89%)` | :arrow_up: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=openyurtio#carryforward-flags-in-the-pull-request-comment) to find out more.

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/openyurtio/openyurt/pull/2165?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=openyurtio).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=openyurtio).

rambohe-ch: /LGTM

Patch:
diff --git a/cmd/yurthub/app/options/filters.go b/cmd/yurthub/app/options/filters.go
index b2a93512..1e344daa 100644
--- a/cmd/yurthub/app/options/filters.go
+++ b/cmd/yurthub/app/options/filters.go
@@ -23,12 +23,13 @@ import (
 	"github.com/openyurtio/openyurt/pkg/yurthub/filter/inclusterconfig"
 	"github.com/openyurtio/openyurt/pkg/yurthub/filter/masterservice"
 	"github.com/openyurtio/openyurt/pkg/yurthub/filter/nodeportisolation"
+	"github.com/openyurtio/openyurt/pkg/yurthub/filter/serviceenvupdater"
 	"github.com/openyurtio/openyurt/pkg/yurthub/filter/servicetopology"
 )
 
 var (
 	// DisabledInCloudMode contains the filters that should be disabled when yurthub is working in cloud mode.
-	DisabledInCloudMode = []string{discardcloudservice.FilterName, forwardkubesvctraffic.FilterName}
+	DisabledInCloudMode = []string{discardcloudservice.FilterName, forwardkubesvctraffic.FilterName, serviceenvupdater.FilterName}
 
 	// SupportedComponentsForFilter is used for specifying which components are supported by filters as default setting.
 	SupportedComponentsForFilter = map[string]string{
@@ -38,6 +39,7 @@ var (
 		inclusterconfig.FilterName:       "kubelet",
 		nodeportisolation.FilterName:     "kube-proxy",
 		forwardkubesvctraffic.FilterName: "kube-proxy",
+		serviceenvupdater.FilterName:     "kubelet",
 	}
 )
 
@@ -52,4 +54,5 @@ func RegisterAllFilters(filters *base.Filters) {
 	inclusterconfig.Register(filters)
 	nodeportisolation.Register(filters)
 	forwardkubesvctraffic.Register(filters)
+	serviceenvupdater.Register(filters)
 }
diff --git a/pkg/yurthub/filter/masterservice/filter.go b/pkg/yurthub/filter/masterservice/filter.go
index eab0450f..1f32ba78 100644
--- a/pkg/yurthub/filter/masterservice/filter.go
+++ b/pkg/yurthub/filter/masterservice/filter.go
@@ -67,7 +67,6 @@ func (msf *masterServiceFilter) SupportedResourceAndVerbs() map[string]sets.Set[
 func (msf *masterServiceFilter) SetMasterServiceHost(host string) error {
 	msf.host = host
 	return nil
-
 }
 
 func (msf *masterServiceFilter) SetMasterServicePort(portStr string) error {
diff --git a/pkg/yurthub/filter/serviceenvupdater/filter.go b/pkg/yurthub/filter/serviceenvupdater/filter.go
new file mode 100644
index 00000000..6ca73517
--- /dev/null
+++ b/pkg/yurthub/filter/serviceenvupdater/filter.go
@@ -0,0 +1,103 @@
+/*
+Copyright 2024 The OpenYurt Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package serviceenvupdater
+
+import (
+	corev1 "k8s.io/api/core/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/apimachinery/pkg/util/sets"
+
+	"github.com/openyurtio/openyurt/pkg/yurthub/filter"
+	"github.com/openyurtio/openyurt/pkg/yurthub/filter/base"
+)
+
+const (
+	// FilterName filter is used to mutate the Kubernetes service host and port
+	// in order for pods on edge nodes to access kube-apiserver via Yurthub proxy
+	FilterName = "serviceenvupdater"
+
+	envVarServiceHost = "KUBERNETES_SERVICE_HOST"
+	envVarServicePort = "KUBERNETES_SERVICE_PORT"
+)
+
+// Register registers a filter
+func Register(filters *base.Filters) {
+	filters.Register(FilterName, func() (filter.ObjectFilter, error) {
+		return NewServiceEnvUpdaterFilter()
+	})
+}
+
+type serviceEnvUpdaterFilter struct {
+	host string
+	port string
+}
+
+func NewServiceEnvUpdaterFilter() (*serviceEnvUpdaterFilter, error) {
+	return &serviceEnvUpdaterFilter{}, nil
+}
+
+func (sef *serviceEnvUpdaterFilter) Name() string {
+	return FilterName
+}
+
+func (sef *serviceEnvUpdaterFilter) SupportedResourceAndVerbs() map[string]sets.Set[string] {
+	return map[string]sets.Set[string]{
+		"pods": sets.New("list", "watch", "get", "patch"),
+	}
+}
+
+func (sef *serviceEnvUpdaterFilter) SetMasterServiceHost(host string) error {
+	sef.host = host
+	return nil
+}
+
+func (sef *serviceEnvUpdaterFilter) SetMasterServicePort(port string) error {
+	sef.port = port
+	return nil
+}
+
+func (sef *serviceEnvUpdaterFilter) Filter(obj runtime.Object, _ <-chan struct{}) runtime.Object {
+	switch v := obj.(type) {
+	case *corev1.Pod:
+		return sef.mutatePodEnv(v)
+	default:
+		return v
+	}
+}
+
+func (sef *serviceEnvUpdaterFilter) mutatePodEnv(req *corev1.Pod) *corev1.Pod {
+	for i := range req.Spec.Containers {
+		foundHost := false
+		foundPort := false
+
+		for j, envVar := range req.Spec.Containers[i].Env {
+			switch envVar.Name {
+			case envVarServiceHost:
+				req.Spec.Containers[i].Env[j].Value = sef.host
+				foundHost = true
+			case envVarServicePort:
+				req.Spec.Containers[i].Env[j].Value = sef.port
+				foundPort = true
+			}
+
+			if foundHost && foundPort {
+				break
+			}
+		}
+	}
+	return req
+}


Test patch:
diff --git a/pkg/yurthub/filter/responsefilter/filter_test.go b/pkg/yurthub/filter/responsefilter/filter_test.go
index 02a21df3..3aab9ccc 100644
--- a/pkg/yurthub/filter/responsefilter/filter_test.go
+++ b/pkg/yurthub/filter/responsefilter/filter_test.go
@@ -2220,6 +2220,368 @@ func TestResponseFilterForListRequest(t *testing.T) {
 				},
 			},
 		},
+		"serviceenvupdater: updates service host and service port env vars in multiple pods": {
+			masterHost: masterHost,
+			masterPort: masterPort,
+			kubeClient: &k8sfake.Clientset{},
+			yurtClient: &fake.FakeDynamicClient{},
+			poolName:   poolName,
+			group:      "",
+			version:    "v1",
+			resource:   "pods",
+			userAgent:  "kubelet",
+			verb:       "GET",
+			path:       "/api/v1/pods",
+			accept:     "application/json",
+			inputObj: &corev1.PodList{
+				TypeMeta: metav1.TypeMeta{
+					Kind:       "PodList",
+					APIVersion: "v1",
+				},
+				Items: []corev1.Pod{
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Name:      "pod1",
+							Namespace: "kube-system",
+						},
+						Spec: corev1.PodSpec{
+							Containers: []corev1.Container{
+								{
+									Name: "test-container",
+									Env: []corev1.EnvVar{
+										{Name: "KUBERNETES_SERVICE_HOST", Value: "192.0.2.1"},
+										{Name: "KUBERNETES_SERVICE_PORT", Value: "1234"},
+										{Name: "OTHER_ENV_VAR", Value: "some-value"},
+									},
+								},
+							},
+						},
+					},
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Name:      "pod2",
+							Namespace: "default",
+						},
+						Spec: corev1.PodSpec{
+							Containers: []corev1.Container{
+								{
+									Name: "test-container",
+									Env: []corev1.EnvVar{
+										{Name: "OTHER_ENV_VAR", Value: "some-value"},
+										{Name: "KUBERNETES_SERVICE_HOST", Value: "192.0.2.1"},
+										{Name: "KUBERNETES_SERVICE_PORT", Value: "1234"},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+			expectedObj: &corev1.PodList{
+				TypeMeta: metav1.TypeMeta{
+					Kind:       "PodList",
+					APIVersion: "v1",
+				},
+				Items: []corev1.Pod{
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Name:      "pod1",
+							Namespace: "kube-system",
+						},
+						Spec: corev1.PodSpec{
+							Containers: []corev1.Container{
+								{
+									Name: "test-container",
+									Env: []corev1.EnvVar{
+										{Name: "KUBERNETES_SERVICE_HOST", Value: masterHost},
+										{Name: "KUBERNETES_SERVICE_PORT", Value: masterPort},
+										{Name: "OTHER_ENV_VAR", Value: "some-value"},
+									},
+								},
+							},
+						},
+					},
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Name:      "pod2",
+							Namespace: "default",
+						},
+						Spec: corev1.PodSpec{
+							Containers: []corev1.Container{
+								{
+									Name: "test-container",
+									Env: []corev1.EnvVar{
+										{Name: "OTHER_ENV_VAR", Value: "some-value"},
+										{Name: "KUBERNETES_SERVICE_HOST", Value: masterHost},
+										{Name: "KUBERNETES_SERVICE_PORT", Value: masterPort},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+		"serviceenvupdater: updates service host and service port env vars in multiple containers per pod": {
+			masterHost: masterHost,
+			masterPort: masterPort,
+			kubeClient: &k8sfake.Clientset{},
+			yurtClient: &fake.FakeDynamicClient{},
+			poolName:   poolName,
+			group:      "",
+			version:    "v1",
+			resource:   "pods",
+			userAgent:  "kubelet",
+			verb:       "GET",
+			path:       "/api/v1/pods",
+			accept:     "application/json",
+			inputObj: &corev1.PodList{
+				TypeMeta: metav1.TypeMeta{
+					Kind:       "PodList",
+					APIVersion: "v1",
+				},
+				Items: []corev1.Pod{
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Name:      "pod1",
+							Namespace: "kube-system",
+						},
+						Spec: corev1.PodSpec{
+							Containers: []corev1.Container{
+								{
+									Name: "test-container",
+									Env: []corev1.EnvVar{
+										{Name: "KUBERNETES_SERVICE_HOST", Value: "192.0.2.1"},
+										{Name: "KUBERNETES_SERVICE_PORT", Value: "1234"},
+										{Name: "OTHER_ENV_VAR", Value: "some-value"},
+									},
+								},
+								{
+									Name: "test-container1",
+									Env: []corev1.EnvVar{
+										{Name: "KUBERNETES_SERVICE_HOST", Value: "192.0.2.1"},
+										{Name: "KUBERNETES_SERVICE_PORT", Value: "1234"},
+										{Name: "OTHER_ENV_VAR", Value: "some-value"},
+									},
+								},
+							},
+						},
+					},
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Name:      "pod2",
+							Namespace: "default",
+						},
+						Spec: corev1.PodSpec{
+							Containers: []corev1.Container{
+								{
+									Name: "test-container",
+									Env: []corev1.EnvVar{
+										{Name: "OTHER_ENV_VAR", Value: "some-value"},
+										{Name: "KUBERNETES_SERVICE_HOST", Value: "192.0.2.1"},
+										{Name: "KUBERNETES_SERVICE_PORT", Value: "1234"},
+									},
+								},
+								{
+									Name: "test-container1",
+									Env: []corev1.EnvVar{
+										{Name: "OTHER_ENV_VAR", Value: "some-value"},
+										{Name: "KUBERNETES_SERVICE_HOST", Value: "192.0.2.1"},
+										{Name: "KUBERNETES_SERVICE_PORT", Value: "1234"},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+			expectedObj: &corev1.PodList{
+				TypeMeta: metav1.TypeMeta{
+					Kind:       "PodList",
+					APIVersion: "v1",
+				},
+				Items: []corev1.Pod{
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Name:      "pod1",
+							Namespace: "kube-system",
+						},
+						Spec: corev1.PodSpec{
+							Containers: []corev1.Container{
+								{
+									Name: "test-container",
+									Env: []corev1.EnvVar{
+										{Name: "KUBERNETES_SERVICE_HOST", Value: masterHost},
+										{Name: "KUBERNETES_SERVICE_PORT", Value: masterPort},
+										{Name: "OTHER_ENV_VAR", Value: "some-value"},
+									},
+								},
+								{
+									Name: "test-container1",
+									Env: []corev1.EnvVar{
+										{Name: "KUBERNETES_SERVICE_HOST", Value: masterHost},
+										{Name: "KUBERNETES_SERVICE_PORT", Value: masterPort},
+										{Name: "OTHER_ENV_VAR", Value: "some-value"},
+									},
+								},
+							},
+						},
+					},
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Name:      "pod2",
+							Namespace: "default",
+						},
+						Spec: corev1.PodSpec{
+							Containers: []corev1.Container{
+								{
+									Name: "test-container",
+									Env: []corev1.EnvVar{
+										{Name: "OTHER_ENV_VAR", Value: "some-value"},
+										{Name: "KUBERNETES_SERVICE_HOST", Value: masterHost},
+										{Name: "KUBERNETES_SERVICE_PORT", Value: masterPort},
+									},
+								},
+								{
+									Name: "test-container1",
+									Env: []corev1.EnvVar{
+										{Name: "OTHER_ENV_VAR", Value: "some-value"},
+										{Name: "KUBERNETES_SERVICE_HOST", Value: masterHost},
+										{Name: "KUBERNETES_SERVICE_PORT", Value: masterPort},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+		"serviceenvupdater: updates service host env var - service port env var does not exist": {
+			masterHost: masterHost,
+			masterPort: masterPort,
+			kubeClient: &k8sfake.Clientset{},
+			yurtClient: &fake.FakeDynamicClient{},
+			poolName:   poolName,
+			group:      "",
+			version:    "v1",
+			resource:   "pods",
+			userAgent:  "kubelet",
+			verb:       "GET",
+			path:       "/api/v1/pods",
+			accept:     "application/json",
+			inputObj: &corev1.PodList{
+				TypeMeta: metav1.TypeMeta{
+					Kind:       "PodList",
+					APIVersion: "v1",
+				},
+				Items: []corev1.Pod{
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Name:      "pod1",
+							Namespace: "kube-system",
+						},
+						Spec: corev1.PodSpec{
+							Containers: []corev1.Container{
+								{
+									Name: "test-container",
+									Env: []corev1.EnvVar{
+										{Name: "KUBERNETES_SERVICE_HOST", Value: "192.0.2.1"},
+										{Name: "OTHER_ENV_VAR", Value: "some-value"},
+									},
+								},
+								{
+									Name: "test-container1",
+									Env: []corev1.EnvVar{
+										{Name: "KUBERNETES_SERVICE_HOST", Value: "192.0.2.1"},
+										{Name: "OTHER_ENV_VAR", Value: "some-value"},
+									},
+								},
+							},
+						},
+					},
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Name:      "pod2",
+							Namespace: "default",
+						},
+						Spec: corev1.PodSpec{
+							Containers: []corev1.Container{
+								{
+									Name: "test-container",
+									Env: []corev1.EnvVar{
+										{Name: "OTHER_ENV_VAR", Value: "some-value"},
+										{Name: "KUBERNETES_SERVICE_HOST", Value: "192.0.2.1"},
+									},
+								},
+								{
+									Name: "test-container1",
+									Env: []corev1.EnvVar{
+										{Name: "OTHER_ENV_VAR", Value: "some-value"},
+										{Name: "KUBERNETES_SERVICE_HOST", Value: "192.0.2.1"},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+			expectedObj: &corev1.PodList{
+				TypeMeta: metav1.TypeMeta{
+					Kind:       "PodList",
+					APIVersion: "v1",
+				},
+				Items: []corev1.Pod{
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Name:      "pod1",
+							Namespace: "kube-system",
+						},
+						Spec: corev1.PodSpec{
+							Containers: []corev1.Container{
+								{
+									Name: "test-container",
+									Env: []corev1.EnvVar{
+										{Name: "KUBERNETES_SERVICE_HOST", Value: masterHost},
+										{Name: "OTHER_ENV_VAR", Value: "some-value"},
+									},
+								},
+								{
+									Name: "test-container1",
+									Env: []corev1.EnvVar{
+										{Name: "KUBERNETES_SERVICE_HOST", Value: masterHost},
+										{Name: "OTHER_ENV_VAR", Value: "some-value"},
+									},
+								},
+							},
+						},
+					},
+					{
+						ObjectMeta: metav1.ObjectMeta{
+							Name:      "pod2",
+							Namespace: "default",
+						},
+						Spec: corev1.PodSpec{
+							Containers: []corev1.Container{
+								{
+									Name: "test-container",
+									Env: []corev1.EnvVar{
+										{Name: "OTHER_ENV_VAR", Value: "some-value"},
+										{Name: "KUBERNETES_SERVICE_HOST", Value: masterHost},
+									},
+								},
+								{
+									Name: "test-container1",
+									Env: []corev1.EnvVar{
+										{Name: "OTHER_ENV_VAR", Value: "some-value"},
+										{Name: "KUBERNETES_SERVICE_HOST", Value: masterHost},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
 	}
 
 	resolver := newTestRequestInfoResolver()
diff --git a/pkg/yurthub/filter/serviceenvupdater/filter_test.go b/pkg/yurthub/filter/serviceenvupdater/filter_test.go
new file mode 100644
index 00000000..72c80d18
--- /dev/null
+++ b/pkg/yurthub/filter/serviceenvupdater/filter_test.go
@@ -0,0 +1,306 @@
+/*
+Copyright 2024 The OpenYurt Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package serviceenvupdater
+
+import (
+	"reflect"
+	"testing"
+
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/apimachinery/pkg/util/sets"
+
+	"github.com/openyurtio/openyurt/pkg/util"
+	"github.com/openyurtio/openyurt/pkg/yurthub/filter/base"
+)
+
+const (
+	masterHost = "169.254.2.1"
+	masterPort = "10261"
+)
+
+func TestRegister(t *testing.T) {
+	filters := base.NewFilters([]string{})
+	Register(filters)
+	if !filters.Enabled(FilterName) {
+		t.Errorf("couldn't register %s filter", FilterName)
+	}
+}
+
+func TestName(t *testing.T) {
+	nif, _ := NewServiceEnvUpdaterFilter()
+	if nif.Name() != FilterName {
+		t.Errorf("expect %s, but got %s", FilterName, nif.Name())
+	}
+}
+
+func TestSupportedResourceAndVerbs(t *testing.T) {
+	nif, _ := NewServiceEnvUpdaterFilter()
+	rvs := nif.SupportedResourceAndVerbs()
+	if len(rvs) != 1 {
+		t.Errorf("supported more than one resources, %v", rvs)
+	}
+	for resource, verbs := range rvs {
+		if resource != "pods" {
+			t.Errorf("expect resource is pods, but got %s", resource)
+		}
+		if !verbs.Equal(sets.New("list", "watch", "get", "patch")) {
+			t.Errorf("expect verbs are list/watch, but got %v", verbs.UnsortedList())
+		}
+	}
+}
+
+func TestFilterServiceEnvUpdater(t *testing.T) {
+	tests := []struct {
+		name        string
+		requestObj  runtime.Object
+		expectedObj runtime.Object
+	}{
+		{
+			name: "service host and service port set to original value",
+			requestObj: &corev1.Pod{
+				Spec: corev1.PodSpec{
+					Containers: []corev1.Container{
+						{
+							Name: "test-container",
+							Env: []corev1.EnvVar{
+								{Name: "KUBERNETES_SERVICE_HOST", Value: "some-host"},
+								{Name: "KUBERNETES_SERVICE_PORT", Value: "1234"},
+								{Name: "OTHER_ENV_VAR", Value: "some-value"},
+							},
+						},
+					},
+				},
+			},
+			expectedObj: &corev1.Pod{
+				Spec: corev1.PodSpec{
+					Containers: []corev1.Container{
+						{
+							Name: "test-container",
+							Env: []corev1.EnvVar{
+								{Name: "KUBERNETES_SERVICE_HOST", Value: masterHost},
+								{Name: "KUBERNETES_SERVICE_PORT", Value: masterPort},
+								{Name: "OTHER_ENV_VAR", Value: "some-value"},
+							},
+						},
+					},
+				},
+			},
+		},
+		{
+			name: "service host and service port set to correct value, should update nothing",
+			requestObj: &corev1.Pod{
+				Spec: corev1.PodSpec{
+					Containers: []corev1.Container{
+						{
+							Name: "test-container",
+							Env: []corev1.EnvVar{
+								{Name: "KUBERNETES_SERVICE_HOST", Value: masterHost},
+								{Name: "KUBERNETES_SERVICE_PORT", Value: masterPort},
+								{Name: "OTHER_ENV_VAR", Value: "some-value"},
+							},
+						},
+					},
+				},
+			},
+			expectedObj: &corev1.Pod{
+				Spec: corev1.PodSpec{
+					Containers: []corev1.Container{
+						{
+							Name: "test-container",
+							Env: []corev1.EnvVar{
+								{Name: "KUBERNETES_SERVICE_HOST", Value: masterHost},
+								{Name: "KUBERNETES_SERVICE_PORT", Value: masterPort},
+								{Name: "OTHER_ENV_VAR", Value: "some-value"},
+							},
+						},
+					},
+				},
+			},
+		},
+		{
+			name: "service host and service port does not exist",
+			requestObj: &corev1.Pod{
+				Spec: corev1.PodSpec{
+					Containers: []corev1.Container{
+						{
+							Name: "test-container",
+							Env: []corev1.EnvVar{
+								{Name: "OTHER_ENV_VAR", Value: "some-value"},
+							},
+						},
+					},
+				},
+			},
+			expectedObj: &corev1.Pod{
+				Spec: corev1.PodSpec{
+					Containers: []corev1.Container{
+						{
+							Name: "test-container",
+							Env: []corev1.EnvVar{
+								{Name: "OTHER_ENV_VAR", Value: "some-value"},
+							},
+						},
+					},
+				},
+			},
+		},
+		{
+			name: "service host does not exist",
+			requestObj: &corev1.Pod{
+				Spec: corev1.PodSpec{
+					Containers: []corev1.Container{
+						{
+							Name: "test-container",
+							Env: []corev1.EnvVar{
+								{Name: "OTHER_ENV_VAR", Value: "some-value"},
+								{Name: "KUBERNETES_SERVICE_PORT", Value: masterPort},
+							},
+						},
+					},
+				},
+			},
+			expectedObj: &corev1.Pod{
+				Spec: corev1.PodSpec{
+					Containers: []corev1.Container{
+						{
+							Name: "test-container",
+							Env: []corev1.EnvVar{
+								{Name: "OTHER_ENV_VAR", Value: "some-value"},
+								{Name: "KUBERNETES_SERVICE_PORT", Value: masterPort},
+							},
+						},
+					},
+				},
+			},
+		},
+		{
+			name: "service port does not exist",
+			requestObj: &corev1.Pod{
+				Spec: corev1.PodSpec{
+					Containers: []corev1.Container{
+						{
+							Name: "test-container",
+							Env: []corev1.EnvVar{
+								{Name: "OTHER_ENV_VAR", Value: "some-value"},
+								{Name: "KUBERNETES_SERVICE_HOST", Value: masterHost},
+							},
+						},
+					},
+				},
+			},
+			expectedObj: &corev1.Pod{
+				Spec: corev1.PodSpec{
+					Containers: []corev1.Container{
+						{
+							Name: "test-container",
+							Env: []corev1.EnvVar{
+								{Name: "OTHER_ENV_VAR", Value: "some-value"},
+								{Name: "KUBERNETES_SERVICE_HOST", Value: masterHost},
+							},
+						},
+					},
+				},
+			},
+		},
+		{
+			name: "service host and service port updates correctly in multiple containers",
+			requestObj: &corev1.Pod{
+				Spec: corev1.PodSpec{
+					Containers: []corev1.Container{
+						{
+							Name: "test-container",
+							Env: []corev1.EnvVar{
+								{Name: "KUBERNETES_SERVICE_HOST", Value: "some-host"},
+								{Name: "KUBERNETES_SERVICE_PORT", Value: "1234"},
+								{Name: "OTHER_ENV_VAR", Value: "some-value"},
+							},
+						},
+						{
+							Name: "test-container1",
+							Env: []corev1.EnvVar{
+								{Name: "KUBERNETES_SERVICE_HOST", Value: "some-host"},
+								{Name: "KUBERNETES_SERVICE_PORT", Value: "1234"},
+								{Name: "OTHER_ENV_VAR", Value: "some-value"},
+							},
+						},
+					},
+				},
+			},
+			expectedObj: &corev1.Pod{
+				Spec: corev1.PodSpec{
+					Containers: []corev1.Container{
+						{
+							Name: "test-container",
+							Env: []corev1.EnvVar{
+								{Name: "KUBERNETES_SERVICE_HOST", Value: masterHost},
+								{Name: "KUBERNETES_SERVICE_PORT", Value: masterPort},
+								{Name: "OTHER_ENV_VAR", Value: "some-value"},
+							},
+						},
+						{
+							Name: "test-container1",
+							Env: []corev1.EnvVar{
+								{Name: "KUBERNETES_SERVICE_HOST", Value: masterHost},
+								{Name: "KUBERNETES_SERVICE_PORT", Value: masterPort},
+								{Name: "OTHER_ENV_VAR", Value: "some-value"},
+							},
+						},
+					},
+				},
+			},
+		},
+	}
+	stopCh := make(<-chan struct{})
+
+	for _, tc := range tests {
+		t.Run(tc.name, func(t *testing.T) {
+			pef, _ := NewServiceEnvUpdaterFilter()
+			pef.SetMasterServiceHost(masterHost)
+			pef.SetMasterServicePort(masterPort)
+			newObj := pef.Filter(tc.requestObj, stopCh)
+
+			if tc.expectedObj == nil {
+				if !util.IsNil(newObj) {
+					t.Errorf("RuntimeObjectFilter expect nil obj, but got %v", newObj)
+				}
+			} else if !reflect.DeepEqual(newObj, tc.expectedObj) {
+				t.Errorf("RuntimeObjectFilter got error, expected: \n%v\nbut got: \n%v\n", tc.expectedObj, newObj)
+			}
+		})
+	}
+}
+
+func TestFilterNonPodReq(t *testing.T) {
+	serviceReq := &corev1.Service{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "svc1",
+			Namespace: "default",
+		},
+		Spec: corev1.ServiceSpec{
+			ClusterIP: "10.96.105.187",
+			Type:      corev1.ServiceTypeClusterIP,
+		},
+	}
+	pef, _ := NewServiceEnvUpdaterFilter()
+	newObj := pef.Filter(serviceReq, make(<-chan struct{}))
+
+	if !reflect.DeepEqual(newObj, serviceReq) {
+		t.Errorf("RuntimeObjectFilter got error, expected: \n%v\nbut got: \n%v\n", serviceReq, newObj)
+	}
+}
diff --git a/pkg/yurtiotdock/clients/edgex-foundry/v3/deviceservice_client_test.go b/pkg/yurtiotdock/clients/edgex-foundry/v3/deviceservice_client_test.go
index 0a2a8b7a..5c185fa5 100644
--- a/pkg/yurtiotdock/clients/edgex-foundry/v3/deviceservice_client_test.go
+++ b/pkg/yurtiotdock/clients/edgex-foundry/v3/deviceservice_client_test.go
@@ -18,7 +18,6 @@ package v3
 import (
 	"context"
 	"encoding/json"
-	"fmt"
 	"testing"
 
 	"github.com/edgexfoundry/go-mod-core-contracts/v3/dtos"
@@ -138,7 +137,6 @@ func Test_ConvertServiceSystemEvents(t *testing.T) {
 
 	service, err := serviceClient.Convert(context.TODO(), dsse, clients.GetOptions{Namespace: "default"})
 	assert.Nil(t, err)
-	fmt.Println(service)
 	assert.Equal(t, "device-virtual", service.Name)
 	assert.Equal(t, "http://edgex-device-virtual:59900", service.Spec.BaseAddress)
 }