diff --git a/app/assets/javascripts/environments/components/kubernetes_overview.vue b/app/assets/javascripts/environments/components/kubernetes_overview.vue
index cfb18cc4f824e9d6a4e5903df2e0820928933f3e..736eaa7062d629fa2c5c79a28f6b0d86beaea66c 100644
--- a/app/assets/javascripts/environments/components/kubernetes_overview.vue
+++ b/app/assets/javascripts/environments/components/kubernetes_overview.vue
@@ -1,14 +1,20 @@
 <script>
-import { GlCollapse, GlButton } from '@gitlab/ui';
+import { GlCollapse, GlButton, GlAlert } from '@gitlab/ui';
 import { __, s__ } from '~/locale';
+import csrf from '~/lib/utils/csrf';
+import { getIdFromGraphQLId, isGid } from '~/graphql_shared/utils';
 import KubernetesAgentInfo from './kubernetes_agent_info.vue';
+import KubernetesPods from './kubernetes_pods.vue';
 
 export default {
   components: {
     GlCollapse,
     GlButton,
+    GlAlert,
     KubernetesAgentInfo,
+    KubernetesPods,
   },
+  inject: ['kasTunnelUrl'],
   props: {
     agentName: {
       required: true,
@@ -22,10 +28,16 @@ export default {
       required: true,
       type: String,
     },
+    namespace: {
+      required: false,
+      type: String,
+      default: '',
+    },
   },
   data() {
     return {
       isVisible: false,
+      error: '',
     };
   },
   computed: {
@@ -35,11 +47,26 @@ export default {
     label() {
       return this.isVisible ? this.$options.i18n.collapse : this.$options.i18n.expand;
     },
+    gitlabAgentId() {
+      const id = isGid(this.agentId) ? getIdFromGraphQLId(this.agentId) : this.agentId;
+      return id.toString();
+    },
+    k8sAccessConfiguration() {
+      return {
+        basePath: this.kasTunnelUrl,
+        baseOptions: {
+          headers: { 'GitLab-Agent-Id': this.gitlabAgentId, ...csrf.headers },
+        },
+      };
+    },
   },
   methods: {
     toggleCollapse() {
       this.isVisible = !this.isVisible;
     },
+    onClusterError(message) {
+      this.error = message;
+    },
   },
   i18n: {
     collapse: __('Collapse'),
@@ -66,7 +93,17 @@ export default {
           :agent-name="agentName"
           :agent-id="agentId"
           :agent-project-path="agentProjectPath"
+          class="gl-mb-5" />
+
+        <gl-alert v-if="error" variant="danger" :dismissible="false" class="gl-mb-5">
+          {{ error }}
+        </gl-alert>
+
+        <kubernetes-pods
+          :configuration="k8sAccessConfiguration"
+          :namespace="namespace"
           class="gl-mb-5"
+          @cluster-error="onClusterError"
       /></template>
     </gl-collapse>
   </div>
diff --git a/app/assets/javascripts/environments/components/kubernetes_pods.vue b/app/assets/javascripts/environments/components/kubernetes_pods.vue
new file mode 100644
index 0000000000000000000000000000000000000000..e43bc838708809850c2fe2cbb034869b018a1c93
--- /dev/null
+++ b/app/assets/javascripts/environments/components/kubernetes_pods.vue
@@ -0,0 +1,111 @@
+<script>
+import { GlLoadingIcon } from '@gitlab/ui';
+import { GlSingleStat } from '@gitlab/ui/dist/charts';
+import { s__ } from '~/locale';
+import k8sPodsQuery from '../graphql/queries/k8s_pods.query.graphql';
+
+export default {
+  components: {
+    GlLoadingIcon,
+    GlSingleStat,
+  },
+  apollo: {
+    k8sPods: {
+      query: k8sPodsQuery,
+      variables() {
+        return {
+          configuration: this.configuration,
+          namespace: this.namespace,
+        };
+      },
+      update(data) {
+        return data?.k8sPods || [];
+      },
+      error(error) {
+        this.error = error;
+        this.$emit('cluster-error', this.error);
+      },
+    },
+  },
+  props: {
+    configuration: {
+      required: true,
+      type: Object,
+    },
+    namespace: {
+      required: true,
+      type: String,
+    },
+  },
+  data() {
+    return {
+      error: '',
+    };
+  },
+
+  computed: {
+    podStats() {
+      if (!this.k8sPods) return null;
+
+      return [
+        {
+          // eslint-disable-next-line @gitlab/require-i18n-strings
+          value: this.getPodsByPhase('Running'),
+          title: this.$options.i18n.runningPods,
+        },
+        {
+          // eslint-disable-next-line @gitlab/require-i18n-strings
+          value: this.getPodsByPhase('Pending'),
+          title: this.$options.i18n.pendingPods,
+        },
+        {
+          // eslint-disable-next-line @gitlab/require-i18n-strings
+          value: this.getPodsByPhase('Succeeded'),
+          title: this.$options.i18n.succeededPods,
+        },
+        {
+          // eslint-disable-next-line @gitlab/require-i18n-strings
+          value: this.getPodsByPhase('Failed'),
+          title: this.$options.i18n.failedPods,
+        },
+      ];
+    },
+    loading() {
+      return this.$apollo.queries.k8sPods.loading;
+    },
+  },
+  methods: {
+    getPodsByPhase(phase) {
+      const filteredPods = this.k8sPods.filter((item) => item.status.phase === phase);
+      return filteredPods.length;
+    },
+  },
+  i18n: {
+    podsTitle: s__('Environment|Pods'),
+    runningPods: s__('Environment|Running'),
+    pendingPods: s__('Environment|Pending'),
+    succeededPods: s__('Environment|Succeeded'),
+    failedPods: s__('Environment|Failed'),
+  },
+};
+</script>
+<template>
+  <div>
+    <p class="gl-text-gray-500">{{ $options.i18n.podsTitle }}</p>
+
+    <gl-loading-icon v-if="loading" />
+
+    <div
+      v-else-if="podStats && !error"
+      class="gl-display-flex gl-flex-wrap-wrap gl-sm-flex-wrap-nowrap gl-mx-n3 gl-mt-n3"
+    >
+      <gl-single-stat
+        v-for="(stat, index) in podStats"
+        :key="index"
+        class="gl-w-full gl-flex-direction-column gl-align-items-center gl-justify-content-center gl-bg-white gl-border gl-border-gray-a-08 gl-mx-3 gl-p-3 gl-mt-3"
+        :value="stat.value"
+        :title="stat.title"
+      />
+    </div>
+  </div>
+</template>
diff --git a/app/assets/javascripts/environments/components/new_environment_item.vue b/app/assets/javascripts/environments/components/new_environment_item.vue
index f46bd0e37800db497f07046093d9d59b32386284..b5ef7d00cc35d5acd91f4a1052cb96bd7b3d6c10 100644
--- a/app/assets/javascripts/environments/components/new_environment_item.vue
+++ b/app/assets/javascripts/environments/components/new_environment_item.vue
@@ -173,7 +173,8 @@ export default {
       return this.glFeatures?.kasUserAccessProject;
     },
     hasRequiredAgentData() {
-      return this.agent.project && this.agent.id && this.agent.name;
+      const { project, id, name } = this.agent || {};
+      return project && id && name;
     },
     showKubernetesOverview() {
       return this.isKubernetesOverviewAvailable && this.hasRequiredAgentData;
@@ -367,6 +368,7 @@ export default {
           :agent-project-path="agent.project"
           :agent-name="agent.name"
           :agent-id="agent.id"
+          :namespace="agent.kubernetesNamespace"
         />
       </div>
       <div v-if="rolloutStatus" :class="$options.deployBoardClasses">
diff --git a/app/assets/javascripts/environments/graphql/client.js b/app/assets/javascripts/environments/graphql/client.js
index 26514b59995fb76e66bb0a42981c34fc4baee302..0482741979bb7ea56bf4301304f538012e0d42b7 100644
--- a/app/assets/javascripts/environments/graphql/client.js
+++ b/app/assets/javascripts/environments/graphql/client.js
@@ -5,6 +5,7 @@ import pageInfoQuery from './queries/page_info.query.graphql';
 import environmentToDeleteQuery from './queries/environment_to_delete.query.graphql';
 import environmentToRollbackQuery from './queries/environment_to_rollback.query.graphql';
 import environmentToStopQuery from './queries/environment_to_stop.query.graphql';
+import k8sPodsQuery from './queries/k8s_pods.query.graphql';
 import { resolvers } from './resolvers';
 import typeDefs from './typedefs.graphql';
 
@@ -82,6 +83,14 @@ export const apolloProvider = (endpoint) => {
       },
     },
   });
+  cache.writeQuery({
+    query: k8sPodsQuery,
+    data: {
+      status: {
+        phase: '',
+      },
+    },
+  });
   return new VueApollo({
     defaultClient,
   });
diff --git a/app/assets/javascripts/environments/graphql/queries/k8s_pods.query.graphql b/app/assets/javascripts/environments/graphql/queries/k8s_pods.query.graphql
new file mode 100644
index 0000000000000000000000000000000000000000..818bca24d51dc4f7f3f6c753d203233c53c16c2a
--- /dev/null
+++ b/app/assets/javascripts/environments/graphql/queries/k8s_pods.query.graphql
@@ -0,0 +1,7 @@
+query getK8sPods($configuration: Object, $namespace: String) {
+  k8sPods(configuration: $configuration, namespace: $namespace) @client {
+    status {
+      phase
+    }
+  }
+}
diff --git a/app/assets/javascripts/environments/graphql/resolvers.js b/app/assets/javascripts/environments/graphql/resolvers.js
index e21670870b8715458a1a71e86bb520495eded3f3..39e05825cf0930279dd0670cd5bc8fb30471ea71 100644
--- a/app/assets/javascripts/environments/graphql/resolvers.js
+++ b/app/assets/javascripts/environments/graphql/resolvers.js
@@ -1,3 +1,4 @@
+import { CoreV1Api, Configuration } from '@gitlab/cluster-client';
 import axios from '~/lib/utils/axios_utils';
 import { s__ } from '~/locale';
 import {
@@ -71,6 +72,19 @@ export const resolvers = (endpoint) => ({
     isLastDeployment(_, { environment }) {
       return environment?.lastDeployment?.isLast;
     },
+    k8sPods(_, { configuration, namespace }) {
+      const coreV1Api = new CoreV1Api(new Configuration(configuration));
+      const podsApi = namespace
+        ? coreV1Api.listCoreV1NamespacedPod(namespace)
+        : coreV1Api.listCoreV1PodForAllNamespaces();
+
+      return podsApi
+        .then((res) => res?.data?.items || [])
+        .catch((err) => {
+          const error = err?.response?.data?.message ? new Error(err.response.data.message) : err;
+          throw error;
+        });
+    },
   },
   Mutation: {
     stopEnvironment(_, { environment }, { client }) {
diff --git a/app/assets/javascripts/environments/graphql/typedefs.graphql b/app/assets/javascripts/environments/graphql/typedefs.graphql
index b4d1f7326f6b7df12ebdf105090c0c075aff382b..7c102fd04d89d0f19a2d485e91085ecb1df0a293 100644
--- a/app/assets/javascripts/environments/graphql/typedefs.graphql
+++ b/app/assets/javascripts/environments/graphql/typedefs.graphql
@@ -62,6 +62,19 @@ type LocalPageInfo {
   previousPage: Int!
 }
 
+type k8sPodStatus {
+  phase: String
+}
+
+type LocalK8sPods {
+  status: k8sPodStatus
+}
+
+input LocalConfiguration {
+  basePath: String
+  baseOptions: JSON
+}
+
 extend type Query {
   environmentApp(page: Int, scope: String): LocalEnvironmentApp
   folder(environment: NestedLocalEnvironmentInput): LocalEnvironmentFolder
@@ -71,6 +84,7 @@ extend type Query {
   environmentToStop: LocalEnvironment
   isEnvironmentStopping(environment: LocalEnvironmentInput): Boolean
   isLastDeployment(environment: LocalEnvironmentInput): Boolean
+  k8sPods(configuration: LocalConfiguration, namespace: String): [LocalK8sPods]
 }
 
 extend type Mutation {
diff --git a/app/assets/javascripts/environments/index.js b/app/assets/javascripts/environments/index.js
index d9a523fd806840a63ae5a1bf7452ae86105cb901..3f746bc538304339630bf59c1b46f1bf6c529a09 100644
--- a/app/assets/javascripts/environments/index.js
+++ b/app/assets/javascripts/environments/index.js
@@ -1,5 +1,6 @@
 import Vue from 'vue';
 import VueApollo from 'vue-apollo';
+import { removeLastSlashInUrlPath } from '~/lib/utils/url_utility';
 import { parseBoolean } from '../lib/utils/common_utils';
 import { apolloProvider } from './graphql/client';
 import EnvironmentsApp from './components/environments_app.vue';
@@ -16,6 +17,7 @@ export default (el) => {
       projectPath,
       defaultBranchName,
       projectId,
+      kasTunnelUrl,
     } = el.dataset;
 
     return new Vue({
@@ -28,6 +30,7 @@ export default (el) => {
         newEnvironmentPath,
         helpPagePath,
         projectId,
+        kasTunnelUrl: removeLastSlashInUrlPath(kasTunnelUrl),
         canCreateEnvironment: parseBoolean(canCreateEnvironment),
       },
       render(h) {
diff --git a/app/views/projects/environments/index.html.haml b/app/views/projects/environments/index.html.haml
index e4b8750b96cb3c88933ba198a6829e95d958deb9..7ddaf868a355fd81be19a1fff619043fc5471263 100644
--- a/app/views/projects/environments/index.html.haml
+++ b/app/views/projects/environments/index.html.haml
@@ -8,4 +8,5 @@
   "help-page-path" => help_page_path("ci/environments/index.md"),
   "project-path" => @project.full_path,
   "project-id" => @project.id,
-  "default-branch-name" => @project.default_branch_or_main } }
+  "default-branch-name" => @project.default_branch_or_main,
+  "kas-tunnel-url" => ::Gitlab::Kas.tunnel_url } }
diff --git a/jest.config.base.js b/jest.config.base.js
index e8ba15f0a095d2e099d56742323f0206d0e08c61..3cbf2fdd61b6a8c20b765134c22e1cc73c39b985 100644
--- a/jest.config.base.js
+++ b/jest.config.base.js
@@ -199,6 +199,7 @@ module.exports = (path, options = {}) => {
     'vue-test-utils-compat',
     '@gitlab/ui',
     '@gitlab/favicon-overlay',
+    '@gitlab/cluster-client',
     'bootstrap-vue',
     'three',
     'monaco-editor',
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 06809f912e6a53af6bc898bcedfa3ff74da07a5b..45d81ac4f337d77cd62bbe79a653ecff1ee68d94 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -16690,9 +16690,24 @@ msgstr ""
 msgid "Environment|Deployment tier"
 msgstr ""
 
+msgid "Environment|Failed"
+msgstr ""
+
 msgid "Environment|Kubernetes overview"
 msgstr ""
 
+msgid "Environment|Pending"
+msgstr ""
+
+msgid "Environment|Pods"
+msgstr ""
+
+msgid "Environment|Running"
+msgstr ""
+
+msgid "Environment|Succeeded"
+msgstr ""
+
 msgid "Epic"
 msgstr ""
 
diff --git a/package.json b/package.json
index 5f68270975a597a39e9c69a05419ef71a6d9506b..6418a12910e4c88532e7231562edf67f08bfd840 100644
--- a/package.json
+++ b/package.json
@@ -53,6 +53,7 @@
     "@cubejs-client/core": "^0.32.17",
     "@cubejs-client/vue": "^0.32.17",
     "@gitlab/at.js": "1.5.7",
+    "@gitlab/cluster-client": "^1.2.0",
     "@gitlab/favicon-overlay": "2.0.0",
     "@gitlab/fonts": "^1.2.0",
     "@gitlab/svgs": "3.38.0",
diff --git a/spec/frontend/environments/graphql/mock_data.js b/spec/frontend/environments/graphql/mock_data.js
index b54359900426c93db339218cd18280f59ce367eb..8d91ffe5ffc46a61546c254a859b9320f816a8a6 100644
--- a/spec/frontend/environments/graphql/mock_data.js
+++ b/spec/frontend/environments/graphql/mock_data.js
@@ -801,6 +801,14 @@ export const resolvedDeploymentDetails = {
 
 export const agent = {
   project: 'agent-project',
-  id: '1',
+  id: 'gid://gitlab/ClusterAgent/1',
   name: 'agent-name',
+  kubernetesNamespace: 'agent-namespace',
 };
+
+const runningPod = { status: { phase: 'Running' } };
+const pendingPod = { status: { phase: 'Pending' } };
+const succeededPod = { status: { phase: 'Succeeded' } };
+const failedPod = { status: { phase: 'Failed' } };
+
+export const k8sPodsMock = [runningPod, runningPod, pendingPod, succeededPod, failedPod, failedPod];
diff --git a/spec/frontend/environments/graphql/resolvers_spec.js b/spec/frontend/environments/graphql/resolvers_spec.js
index 2c223d3a1a7c88a6d0d4daaf6d9a9107c1da5fe2..c66844f5f24d368b542d0b6b2253fa2b294522e8 100644
--- a/spec/frontend/environments/graphql/resolvers_spec.js
+++ b/spec/frontend/environments/graphql/resolvers_spec.js
@@ -1,4 +1,5 @@
 import MockAdapter from 'axios-mock-adapter';
+import { CoreV1Api } from '@gitlab/cluster-client';
 import { s__ } from '~/locale';
 import axios from '~/lib/utils/axios_utils';
 import { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_OK } from '~/lib/utils/http_status';
@@ -17,6 +18,7 @@ import {
   resolvedEnvironment,
   folder,
   resolvedFolder,
+  k8sPodsMock,
 } from './mock_data';
 
 const ENDPOINT = `${TEST_HOST}/environments`;
@@ -143,6 +145,61 @@ describe('~/frontend/environments/graphql/resolvers', () => {
       expect(environmentFolder).toEqual(resolvedFolder);
     });
   });
+  describe('k8sPods', () => {
+    const namespace = 'default';
+    const configuration = {
+      basePath: 'kas-proxy/',
+      baseOptions: {
+        headers: { 'GitLab-Agent-Id': '1' },
+      },
+    };
+
+    const mockPodsListFn = jest.fn().mockImplementation(() => {
+      return Promise.resolve({
+        data: {
+          items: k8sPodsMock,
+        },
+      });
+    });
+
+    const mockNamespacedPodsListFn = jest.fn().mockImplementation(mockPodsListFn);
+    const mockAllPodsListFn = jest.fn().mockImplementation(mockPodsListFn);
+
+    beforeEach(() => {
+      jest
+        .spyOn(CoreV1Api.prototype, 'listCoreV1NamespacedPod')
+        .mockImplementation(mockNamespacedPodsListFn);
+      jest
+        .spyOn(CoreV1Api.prototype, 'listCoreV1PodForAllNamespaces')
+        .mockImplementation(mockAllPodsListFn);
+    });
+
+    it('should request namespaced pods from the cluster_client library if namespace is specified', async () => {
+      const pods = await mockResolvers.Query.k8sPods(null, { configuration, namespace });
+
+      expect(mockNamespacedPodsListFn).toHaveBeenCalledWith(namespace);
+      expect(mockAllPodsListFn).not.toHaveBeenCalled();
+
+      expect(pods).toEqual(k8sPodsMock);
+    });
+    it('should request all pods from the cluster_client library if namespace is not specified', async () => {
+      const pods = await mockResolvers.Query.k8sPods(null, { configuration, namespace: '' });
+
+      expect(mockAllPodsListFn).toHaveBeenCalled();
+      expect(mockNamespacedPodsListFn).not.toHaveBeenCalled();
+
+      expect(pods).toEqual(k8sPodsMock);
+    });
+    it('should throw an error if the API call fails', async () => {
+      jest
+        .spyOn(CoreV1Api.prototype, 'listCoreV1PodForAllNamespaces')
+        .mockRejectedValue(new Error('API error'));
+
+      await expect(mockResolvers.Query.k8sPods(null, { configuration })).rejects.toThrow(
+        'API error',
+      );
+    });
+  });
   describe('stopEnvironment', () => {
     it('should post to the stop environment path', async () => {
       mock.onPost(ENDPOINT).reply(HTTP_STATUS_OK);
diff --git a/spec/frontend/environments/kubernetes_overview_spec.js b/spec/frontend/environments/kubernetes_overview_spec.js
index 8673c657760106204ad42ed51b947a31ff171e8e..1912fd4a82bd051964e8ed761069ea17d101807a 100644
--- a/spec/frontend/environments/kubernetes_overview_spec.js
+++ b/spec/frontend/environments/kubernetes_overview_spec.js
@@ -1,19 +1,28 @@
 import { nextTick } from 'vue';
 import { shallowMount } from '@vue/test-utils';
-import { GlCollapse, GlButton } from '@gitlab/ui';
+import { GlCollapse, GlButton, GlAlert } from '@gitlab/ui';
 import KubernetesOverview from '~/environments/components/kubernetes_overview.vue';
 import KubernetesAgentInfo from '~/environments/components/kubernetes_agent_info.vue';
-
-const agent = {
-  project: 'agent-project',
-  id: '1',
-  name: 'agent-name',
-};
+import KubernetesPods from '~/environments/components/kubernetes_pods.vue';
+import { agent } from './graphql/mock_data';
+import { mockKasTunnelUrl } from './mock_data';
 
 const propsData = {
   agentId: agent.id,
   agentName: agent.name,
   agentProjectPath: agent.project,
+  namespace: agent.kubernetesNamespace,
+};
+
+const provide = {
+  kasTunnelUrl: mockKasTunnelUrl,
+};
+
+const configuration = {
+  basePath: provide.kasTunnelUrl.replace(/\/$/, ''),
+  baseOptions: {
+    headers: { 'GitLab-Agent-Id': '1' },
+  },
 };
 
 describe('~/environments/components/kubernetes_overview.vue', () => {
@@ -22,10 +31,13 @@ describe('~/environments/components/kubernetes_overview.vue', () => {
   const findCollapse = () => wrapper.findComponent(GlCollapse);
   const findCollapseButton = () => wrapper.findComponent(GlButton);
   const findAgentInfo = () => wrapper.findComponent(KubernetesAgentInfo);
+  const findKubernetesPods = () => wrapper.findComponent(KubernetesPods);
+  const findAlert = () => wrapper.findComponent(GlAlert);
 
   const createWrapper = () => {
     wrapper = shallowMount(KubernetesOverview, {
       propsData,
+      provide,
     });
   };
 
@@ -57,6 +69,7 @@ describe('~/environments/components/kubernetes_overview.vue', () => {
 
     it("doesn't render components when the collapse is not visible", () => {
       expect(findAgentInfo().exists()).toBe(false);
+      expect(findKubernetesPods().exists()).toBe(false);
     });
 
     it('opens on click', async () => {
@@ -70,15 +83,40 @@ describe('~/environments/components/kubernetes_overview.vue', () => {
   });
 
   describe('when section is expanded', () => {
-    it('renders kubernetes agent info', async () => {
+    beforeEach(() => {
       createWrapper();
-      await toggleCollapse();
+      toggleCollapse();
+    });
 
+    it('renders kubernetes agent info', () => {
       expect(findAgentInfo().props()).toEqual({
         agentName: agent.name,
         agentId: agent.id,
         agentProjectPath: agent.project,
       });
     });
+
+    it('renders kubernetes pods', () => {
+      expect(findKubernetesPods().props()).toEqual({
+        namespace: agent.kubernetesNamespace,
+        configuration,
+      });
+    });
+  });
+
+  describe('on cluster error', () => {
+    beforeEach(() => {
+      createWrapper();
+      toggleCollapse();
+    });
+
+    it('shows alert with the error message', async () => {
+      const error = 'Error message from pods';
+
+      findKubernetesPods().vm.$emit('cluster-error', error);
+      await nextTick();
+
+      expect(findAlert().text()).toBe(error);
+    });
   });
 });
diff --git a/spec/frontend/environments/kubernetes_pods_spec.js b/spec/frontend/environments/kubernetes_pods_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..137309d78535a4735b150d924c00490c90d1ec7d
--- /dev/null
+++ b/spec/frontend/environments/kubernetes_pods_spec.js
@@ -0,0 +1,114 @@
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import { shallowMount } from '@vue/test-utils';
+import { GlLoadingIcon } from '@gitlab/ui';
+import { GlSingleStat } from '@gitlab/ui/dist/charts';
+import waitForPromises from 'helpers/wait_for_promises';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import KubernetesPods from '~/environments/components/kubernetes_pods.vue';
+import { mockKasTunnelUrl } from './mock_data';
+import { k8sPodsMock } from './graphql/mock_data';
+
+Vue.use(VueApollo);
+
+describe('~/environments/components/kubernetes_pods.vue', () => {
+  let wrapper;
+
+  const namespace = 'my-kubernetes-namespace';
+  const configuration = {
+    basePath: mockKasTunnelUrl,
+    baseOptions: {
+      headers: { 'GitLab-Agent-Id': '1' },
+    },
+  };
+
+  const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
+  const findAllStats = () => wrapper.findAllComponents(GlSingleStat);
+  const findSingleStat = (at) => findAllStats().at(at);
+
+  const createApolloProvider = () => {
+    const mockResolvers = {
+      Query: {
+        k8sPods: jest.fn().mockReturnValue(k8sPodsMock),
+      },
+    };
+
+    return createMockApollo([], mockResolvers);
+  };
+
+  const createWrapper = (apolloProvider = createApolloProvider()) => {
+    wrapper = shallowMount(KubernetesPods, {
+      propsData: { namespace, configuration },
+      apolloProvider,
+    });
+  };
+
+  describe('mounted', () => {
+    it('shows the loading icon', () => {
+      createWrapper();
+
+      expect(findLoadingIcon().exists()).toBe(true);
+    });
+
+    it('hides the loading icon when the list of pods loaded', async () => {
+      createWrapper();
+      await waitForPromises();
+
+      expect(findLoadingIcon().exists()).toBe(false);
+    });
+  });
+
+  describe('when gets pods data', () => {
+    it('renders stats', async () => {
+      createWrapper();
+      await waitForPromises();
+
+      expect(findAllStats()).toHaveLength(4);
+    });
+
+    it.each`
+      count | title                                | index
+      ${2}  | ${KubernetesPods.i18n.runningPods}   | ${0}
+      ${1}  | ${KubernetesPods.i18n.pendingPods}   | ${1}
+      ${1}  | ${KubernetesPods.i18n.succeededPods} | ${2}
+      ${2}  | ${KubernetesPods.i18n.failedPods}    | ${3}
+    `(
+      'renders stat with title "$title" and count "$count" at index $index',
+      async ({ count, title, index }) => {
+        createWrapper();
+        await waitForPromises();
+
+        expect(findSingleStat(index).props()).toMatchObject({
+          value: count,
+          title,
+        });
+      },
+    );
+  });
+
+  describe('when gets an error from the cluster_client API', () => {
+    const error = new Error('Error from the cluster_client API');
+    const createErroredApolloProvider = () => {
+      const mockResolvers = {
+        Query: {
+          k8sPods: jest.fn().mockRejectedValueOnce(error),
+        },
+      };
+
+      return createMockApollo([], mockResolvers);
+    };
+
+    beforeEach(async () => {
+      createWrapper(createErroredApolloProvider());
+      await waitForPromises();
+    });
+
+    it("doesn't show pods stats", () => {
+      expect(findAllStats()).toHaveLength(0);
+    });
+
+    it('emits an error message', () => {
+      expect(wrapper.emitted('cluster-error')).toMatchObject([[error]]);
+    });
+  });
+});
diff --git a/spec/frontend/environments/mock_data.js b/spec/frontend/environments/mock_data.js
index a6d67c26304c27d46b1f90d73a8c367c9b1e4454..bd2c6b7c892912dd83fe876028420ac8882472f3 100644
--- a/spec/frontend/environments/mock_data.js
+++ b/spec/frontend/environments/mock_data.js
@@ -313,6 +313,8 @@ const createEnvironment = (data = {}) => ({
   ...data,
 });
 
+const mockKasTunnelUrl = 'https://kas.gitlab.com/k8s-proxy';
+
 export {
   environment,
   environmentsList,
@@ -321,4 +323,5 @@ export {
   tableData,
   deployBoardMockData,
   createEnvironment,
+  mockKasTunnelUrl,
 };
diff --git a/spec/frontend/environments/new_environment_item_spec.js b/spec/frontend/environments/new_environment_item_spec.js
index 89a9ca725ba1807443e155643b4af0b5cffc7aba..b4f5263a15147a0f6509ad9423493bca674cf8c0 100644
--- a/spec/frontend/environments/new_environment_item_spec.js
+++ b/spec/frontend/environments/new_environment_item_spec.js
@@ -12,6 +12,7 @@ import Deployment from '~/environments/components/deployment.vue';
 import DeployBoardWrapper from '~/environments/components/deploy_board_wrapper.vue';
 import KubernetesOverview from '~/environments/components/kubernetes_overview.vue';
 import { resolvedEnvironment, rolloutStatus, agent } from './graphql/mock_data';
+import { mockKasTunnelUrl } from './mock_data';
 
 Vue.use(VueApollo);
 
@@ -26,7 +27,13 @@ describe('~/environments/components/new_environment_item.vue', () => {
     mountExtended(EnvironmentItem, {
       apolloProvider,
       propsData: { environment: resolvedEnvironment, ...propsData },
-      provide: { helpPagePath: '/help', projectId: '1', projectPath: '/1', ...provideData },
+      provide: {
+        helpPagePath: '/help',
+        projectId: '1',
+        projectPath: '/1',
+        kasTunnelUrl: mockKasTunnelUrl,
+        ...provideData,
+      },
       stubs: { transition: stubTransition() },
     });
 
@@ -536,6 +543,7 @@ describe('~/environments/components/new_environment_item.vue', () => {
         agentProjectPath: agent.project,
         agentName: agent.name,
         agentId: agent.id,
+        namespace: agent.kubernetesNamespace,
       });
     });
 
diff --git a/yarn.lock b/yarn.lock
index a9a4dd4d600a5022c95aee308e95f789710ca1f1..a948e43630ea49c2b859b652e6b0458b9a15dbdb 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1085,10 +1085,19 @@
   resolved "https://registry.yarnpkg.com/@gitlab/at.js/-/at.js-1.5.7.tgz#1ee6f838cc4410a1d797770934df91d90df8179e"
   integrity sha512-c6ySRK/Ma7lxwpIVbSAF3P+xiTLrNTGTLRx4/pHK111AdFxwgUwrYF6aVZFXvmG65jHOJHoa0eQQ21RW6rm0Rg==
 
+"@gitlab/cluster-client@^1.2.0":
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/@gitlab/cluster-client/-/cluster-client-1.2.0.tgz#3b56da46748403354b5af73678b17db3851cbe53"
+  integrity sha512-2emHgfOF9CdibzwXJ2yZVf2d+ez8b67O47qa+pwlG+NnYatjfIcj9Pkzs4kBcO/9+j2lVH/EegPDyEkZZt8Irg==
+  dependencies:
+    axios "^0.24.0"
+    core-js "^3.29.1"
+
 "@gitlab/eslint-plugin@18.3.2":
   version "18.3.2"
   resolved "https://registry.yarnpkg.com/@gitlab/eslint-plugin/-/eslint-plugin-18.3.2.tgz#dc4d5b487e26a1473106c1a3e34ae3ea219d4dd1"
   integrity sha512-Lz0RnEW5isZ/jkeHcr2k6NqaHISwgKeWN/vkWUU5J4Ax7oYPR0CgA2KO/dEnOvIPmGfbnUKowsekBmmy5SUQHA==
+
   dependencies:
     "@babel/core" "^7.17.0"
     "@babel/eslint-parser" "^7.17.0"