Skip to content

Commit b8ecc7f

Browse files
committed
feat: paginate list() calls to the Kubernetes API
Ensures we control how hard we hit the Kubernetes API. Instead of listing everything in that namespace, do it in batches of 100. As part of the pagination, remove unneeded metadata like "managedFields" from the listed workloads.
1 parent 7f2d204 commit b8ecc7f

File tree

14 files changed

+620
-43
lines changed

14 files changed

+620
-43
lines changed

src/supervisor/kuberenetes-api-wrappers.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ export async function retryKubernetesApiRequestIndefinitely<ResponseType>(
7171
}
7272

7373
export function calculateSleepSeconds(
74-
httpResponse: http.IncomingMessage,
74+
httpResponse?: http.IncomingMessage,
7575
): number {
7676
let sleepSeconds = DEFAULT_SLEEP_SEC;
7777
if (

src/supervisor/watchers/handlers/cron-job.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,28 @@
1-
import { V1beta1CronJob } from '@kubernetes/client-node';
1+
import { V1beta1CronJob, V1beta1CronJobList } from '@kubernetes/client-node';
22
import { deleteWorkload } from './workload';
33
import { WorkloadKind } from '../../types';
44
import { FALSY_WORKLOAD_NAME_MARKER } from './types';
5+
import { IncomingMessage } from 'http';
6+
import { k8sApi } from '../../cluster';
7+
import { paginatedList } from './pagination';
8+
9+
export async function paginatedCronJobList(namespace: string): Promise<{
10+
response: IncomingMessage;
11+
body: V1beta1CronJobList;
12+
}> {
13+
const v1CronJobList = new V1beta1CronJobList();
14+
v1CronJobList.apiVersion = 'batch/v1beta1';
15+
v1CronJobList.kind = 'CronJobList';
16+
v1CronJobList.items = new Array<V1beta1CronJob>();
17+
18+
return await paginatedList(
19+
namespace,
20+
v1CronJobList,
21+
k8sApi.batchUnstableClient.listNamespacedCronJob.bind(
22+
k8sApi.batchUnstableClient,
23+
),
24+
);
25+
}
526

627
export async function cronJobWatchHandler(
728
cronJob: V1beta1CronJob,

src/supervisor/watchers/handlers/daemon-set.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,26 @@
1-
import { V1DaemonSet } from '@kubernetes/client-node';
1+
import { V1DaemonSet, V1DaemonSetList } from '@kubernetes/client-node';
22
import { deleteWorkload } from './workload';
33
import { WorkloadKind } from '../../types';
44
import { FALSY_WORKLOAD_NAME_MARKER } from './types';
5+
import { IncomingMessage } from 'http';
6+
import { k8sApi } from '../../cluster';
7+
import { paginatedList } from './pagination';
8+
9+
export async function paginatedDaemonSetList(namespace: string): Promise<{
10+
response: IncomingMessage;
11+
body: V1DaemonSetList;
12+
}> {
13+
const v1DaemonSetList = new V1DaemonSetList();
14+
v1DaemonSetList.apiVersion = 'apps/v1';
15+
v1DaemonSetList.kind = 'DaemonSetList';
16+
v1DaemonSetList.items = new Array<V1DaemonSet>();
17+
18+
return await paginatedList(
19+
namespace,
20+
v1DaemonSetList,
21+
k8sApi.appsClient.listNamespacedDaemonSet.bind(k8sApi.appsClient),
22+
);
23+
}
524

625
export async function daemonSetWatchHandler(
726
daemonSet: V1DaemonSet,

src/supervisor/watchers/handlers/deployment-config.ts

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,50 @@
1+
import { IncomingMessage } from 'http';
12
import { deleteWorkload } from './workload';
23
import { WorkloadKind } from '../../types';
3-
import { FALSY_WORKLOAD_NAME_MARKER, V1DeploymentConfig } from './types';
4+
import {
5+
FALSY_WORKLOAD_NAME_MARKER,
6+
V1DeploymentConfig,
7+
V1DeploymentConfigList,
8+
} from './types';
9+
import { paginatedList } from './pagination';
10+
import { k8sApi } from '../../cluster';
11+
12+
export async function paginatedDeploymentConfigList(
13+
namespace: string,
14+
): Promise<{
15+
response: IncomingMessage;
16+
body: V1DeploymentConfigList;
17+
}> {
18+
const v1DeploymentConfigList = new V1DeploymentConfigList();
19+
v1DeploymentConfigList.apiVersion = 'apps.openshift.io/v1';
20+
v1DeploymentConfigList.kind = 'DeploymentConfigList';
21+
v1DeploymentConfigList.items = new Array<V1DeploymentConfig>();
22+
23+
return await paginatedList(
24+
namespace,
25+
v1DeploymentConfigList,
26+
async (
27+
namespace: string,
28+
pretty?: string,
29+
_allowWatchBookmarks?: boolean,
30+
_continue?: string,
31+
fieldSelector?: string,
32+
labelSelector?: string,
33+
limit?: number,
34+
) =>
35+
k8sApi.customObjectsClient.listNamespacedCustomObject(
36+
'apps.openshift.io',
37+
'v1',
38+
namespace,
39+
'deploymentconfigs',
40+
pretty,
41+
_continue,
42+
fieldSelector,
43+
labelSelector,
44+
limit,
45+
) as any,
46+
);
47+
}
448

549
export async function deploymentConfigWatchHandler(
650
deploymentConfig: V1DeploymentConfig,

src/supervisor/watchers/handlers/deployment.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,26 @@
1-
import { V1Deployment } from '@kubernetes/client-node';
1+
import { V1Deployment, V1DeploymentList } from '@kubernetes/client-node';
22
import { deleteWorkload } from './workload';
33
import { WorkloadKind } from '../../types';
44
import { FALSY_WORKLOAD_NAME_MARKER } from './types';
5+
import { IncomingMessage } from 'http';
6+
import { k8sApi } from '../../cluster';
7+
import { paginatedList } from './pagination';
8+
9+
export async function paginatedDeploymentList(namespace: string): Promise<{
10+
response: IncomingMessage;
11+
body: V1DeploymentList;
12+
}> {
13+
const v1DeploymentList = new V1DeploymentList();
14+
v1DeploymentList.apiVersion = 'apps/v1';
15+
v1DeploymentList.kind = 'DeploymentList';
16+
v1DeploymentList.items = new Array<V1Deployment>();
17+
18+
return await paginatedList(
19+
namespace,
20+
v1DeploymentList,
21+
k8sApi.appsClient.listNamespacedDeployment.bind(k8sApi.appsClient),
22+
);
23+
}
524

625
export async function deploymentWatchHandler(
726
deployment: V1Deployment,

src/supervisor/watchers/handlers/index.ts

Lines changed: 27 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,24 @@ import {
99

1010
import { logger } from '../../../common/logger';
1111
import { WorkloadKind } from '../../types';
12-
import { podWatchHandler, podDeletedHandler } from './pod';
13-
import { cronJobWatchHandler } from './cron-job';
14-
import { daemonSetWatchHandler } from './daemon-set';
15-
import { deploymentWatchHandler } from './deployment';
16-
import { jobWatchHandler } from './job';
17-
import { replicaSetWatchHandler } from './replica-set';
18-
import { replicationControllerWatchHandler } from './replication-controller';
19-
import { statefulSetWatchHandler } from './stateful-set';
20-
import { deploymentConfigWatchHandler } from './deployment-config';
12+
import { podWatchHandler, podDeletedHandler, paginatedPodList } from './pod';
13+
import { cronJobWatchHandler, paginatedCronJobList } from './cron-job';
14+
import { daemonSetWatchHandler, paginatedDaemonSetList } from './daemon-set';
15+
import { deploymentWatchHandler, paginatedDeploymentList } from './deployment';
16+
import { jobWatchHandler, paginatedJobList } from './job';
17+
import { paginatedReplicaSetList, replicaSetWatchHandler } from './replica-set';
18+
import {
19+
paginatedReplicationControllerList,
20+
replicationControllerWatchHandler,
21+
} from './replication-controller';
22+
import {
23+
paginatedStatefulSetList,
24+
statefulSetWatchHandler,
25+
} from './stateful-set';
26+
import {
27+
deploymentConfigWatchHandler,
28+
paginatedDeploymentConfigList,
29+
} from './deployment-config';
2130
import { k8sApi, kubeConfig } from '../../cluster';
2231
import * as kubernetesApiWrappers from '../../kuberenetes-api-wrappers';
2332
import { IWorkloadWatchMetadata, FALSY_WORKLOAD_NAME_MARKER } from './types';
@@ -49,64 +58,57 @@ const workloadWatchMetadata: Readonly<IWorkloadWatchMetadata> = {
4958
[UPDATE]: podWatchHandler,
5059
[DELETE]: podDeletedHandler,
5160
},
52-
listFactory: (namespace) => () =>
53-
k8sApi.coreClient.listNamespacedPod(namespace),
61+
listFactory: (namespace) => () => paginatedPodList(namespace),
5462
},
5563
[WorkloadKind.ReplicationController]: {
5664
endpoint: '/api/v1/watch/namespaces/{namespace}/replicationcontrollers',
5765
handlers: {
5866
[DELETE]: replicationControllerWatchHandler,
5967
},
6068
listFactory: (namespace) => () =>
61-
k8sApi.coreClient.listNamespacedReplicationController(namespace),
69+
paginatedReplicationControllerList(namespace),
6270
},
6371
[WorkloadKind.CronJob]: {
6472
endpoint: '/apis/batch/v1beta1/watch/namespaces/{namespace}/cronjobs',
6573
handlers: {
6674
[DELETE]: cronJobWatchHandler,
6775
},
68-
listFactory: (namespace) => () =>
69-
k8sApi.batchUnstableClient.listNamespacedCronJob(namespace),
76+
listFactory: (namespace) => () => paginatedCronJobList(namespace),
7077
},
7178
[WorkloadKind.Job]: {
7279
endpoint: '/apis/batch/v1/watch/namespaces/{namespace}/jobs',
7380
handlers: {
7481
[DELETE]: jobWatchHandler,
7582
},
76-
listFactory: (namespace) => () =>
77-
k8sApi.batchClient.listNamespacedJob(namespace),
83+
listFactory: (namespace) => () => paginatedJobList(namespace),
7884
},
7985
[WorkloadKind.DaemonSet]: {
8086
endpoint: '/apis/apps/v1/watch/namespaces/{namespace}/daemonsets',
8187
handlers: {
8288
[DELETE]: daemonSetWatchHandler,
8389
},
84-
listFactory: (namespace) => () =>
85-
k8sApi.appsClient.listNamespacedDaemonSet(namespace),
90+
listFactory: (namespace) => () => paginatedDaemonSetList(namespace),
8691
},
8792
[WorkloadKind.Deployment]: {
8893
endpoint: '/apis/apps/v1/watch/namespaces/{namespace}/deployments',
8994
handlers: {
9095
[DELETE]: deploymentWatchHandler,
9196
},
92-
listFactory: (namespace) => () =>
93-
k8sApi.appsClient.listNamespacedDeployment(namespace),
97+
listFactory: (namespace) => () => paginatedDeploymentList(namespace),
9498
},
9599
[WorkloadKind.ReplicaSet]: {
96100
endpoint: '/apis/apps/v1/watch/namespaces/{namespace}/replicasets',
97101
handlers: {
98102
[DELETE]: replicaSetWatchHandler,
99103
},
100-
listFactory: (namespace) => () =>
101-
k8sApi.appsClient.listNamespacedReplicaSet(namespace),
104+
listFactory: (namespace) => () => paginatedReplicaSetList(namespace),
102105
},
103106
[WorkloadKind.StatefulSet]: {
104107
endpoint: '/apis/apps/v1/watch/namespaces/{namespace}/statefulsets',
105108
handlers: {
106109
[DELETE]: statefulSetWatchHandler,
107110
},
108-
listFactory: (namespace) => () =>
109-
k8sApi.appsClient.listNamespacedStatefulSet(namespace),
111+
listFactory: (namespace) => () => paginatedStatefulSetList(namespace),
110112
},
111113
[WorkloadKind.DeploymentConfig]: {
112114
/** https://docs.openshift.com/container-platform/4.7/rest_api/workloads_apis/deploymentconfig-apps-openshift-io-v1.html */
@@ -115,13 +117,7 @@ const workloadWatchMetadata: Readonly<IWorkloadWatchMetadata> = {
115117
handlers: {
116118
[DELETE]: deploymentConfigWatchHandler,
117119
},
118-
listFactory: (namespace) => () =>
119-
k8sApi.customObjectsClient.listNamespacedCustomObject(
120-
'apps.openshift.io',
121-
'v1',
122-
namespace,
123-
'deploymentconfigs',
124-
),
120+
listFactory: (namespace) => () => paginatedDeploymentConfigList(namespace),
125121
},
126122
};
127123

src/supervisor/watchers/handlers/job.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,26 @@
1-
import { V1Job } from '@kubernetes/client-node';
1+
import { V1Job, V1JobList } from '@kubernetes/client-node';
22
import { deleteWorkload } from './workload';
33
import { WorkloadKind } from '../../types';
44
import { FALSY_WORKLOAD_NAME_MARKER } from './types';
5+
import { IncomingMessage } from 'http';
6+
import { k8sApi } from '../../cluster';
7+
import { paginatedList } from './pagination';
8+
9+
export async function paginatedJobList(namespace: string): Promise<{
10+
response: IncomingMessage;
11+
body: V1JobList;
12+
}> {
13+
const v1JobList = new V1JobList();
14+
v1JobList.apiVersion = 'batch/v1';
15+
v1JobList.kind = 'JobList';
16+
v1JobList.items = new Array<V1Job>();
17+
18+
return await paginatedList(
19+
namespace,
20+
v1JobList,
21+
k8sApi.batchClient.listNamespacedJob.bind(k8sApi.batchClient),
22+
);
23+
}
524

625
export async function jobWatchHandler(job: V1Job): Promise<void> {
726
if (

0 commit comments

Comments
 (0)