k8s add hpa and ingresses UI (#2640)
* k8s add hpa and ingresses UI * add todo
This commit is contained in:
@@ -7,7 +7,9 @@ An app to roll dice (it doesn't actually do that).
|
||||
## Prerequisites
|
||||
|
||||
- kubectl installed
|
||||
- Minikube installed
|
||||
- Minikube installed, with the following addons
|
||||
- metrics-server
|
||||
- ingress
|
||||
- jq installed
|
||||
- Backstage locally built and ready to run
|
||||
|
||||
|
||||
@@ -53,6 +53,41 @@ spec:
|
||||
ports:
|
||||
- containerPort: 82
|
||||
|
||||
---
|
||||
apiVersion: autoscaling/v1
|
||||
kind: HorizontalPodAutoscaler
|
||||
metadata:
|
||||
name: dice-roller
|
||||
labels:
|
||||
'backstage.io/kubernetes-id': dice-roller
|
||||
spec:
|
||||
scaleTargetRef:
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
name: dice-roller
|
||||
minReplicas: 10
|
||||
maxReplicas: 15
|
||||
targetCPUUtilizationPercentage: 50
|
||||
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1beta1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: dice-roller
|
||||
annotations:
|
||||
nginx.ingress.kubernetes.io/rewrite-target: /$1
|
||||
labels:
|
||||
'backstage.io/kubernetes-id': dice-roller
|
||||
spec:
|
||||
rules:
|
||||
- host: nginx
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
backend:
|
||||
serviceName: dice-roller
|
||||
servicePort: 80
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
|
||||
@@ -15,7 +15,13 @@
|
||||
*/
|
||||
|
||||
import { ClusterDetails } from '..';
|
||||
import { AppsV1Api, CoreV1Api, KubeConfig } from '@kubernetes/client-node';
|
||||
import {
|
||||
AppsV1Api,
|
||||
AutoscalingV1Api,
|
||||
CoreV1Api,
|
||||
KubeConfig,
|
||||
NetworkingV1beta1Api,
|
||||
} from '@kubernetes/client-node';
|
||||
|
||||
export class KubernetesClientProvider {
|
||||
private readonly coreClientMap: {
|
||||
@@ -26,9 +32,19 @@ export class KubernetesClientProvider {
|
||||
[key: string]: AppsV1Api;
|
||||
};
|
||||
|
||||
private readonly autoscalingClientMap: {
|
||||
[key: string]: AutoscalingV1Api;
|
||||
};
|
||||
|
||||
private readonly networkingBeta1ClientMap: {
|
||||
[key: string]: NetworkingV1beta1Api;
|
||||
};
|
||||
|
||||
constructor() {
|
||||
this.coreClientMap = {};
|
||||
this.appsClientMap = {};
|
||||
this.autoscalingClientMap = {};
|
||||
this.networkingBeta1ClientMap = {};
|
||||
}
|
||||
|
||||
// visible for testing
|
||||
@@ -93,4 +109,36 @@ export class KubernetesClientProvider {
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
getAutoscalingClientByClusterDetails(clusterDetails: ClusterDetails) {
|
||||
const clientMapKey = clusterDetails.name;
|
||||
|
||||
if (this.autoscalingClientMap.hasOwnProperty(clientMapKey)) {
|
||||
return this.autoscalingClientMap[clientMapKey];
|
||||
}
|
||||
|
||||
const kc = this.getKubeConfig(clusterDetails);
|
||||
|
||||
const client = kc.makeApiClient(AutoscalingV1Api);
|
||||
|
||||
this.autoscalingClientMap[clientMapKey] = client;
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
getNetworkingBeta1Client(clusterDetails: ClusterDetails) {
|
||||
const clientMapKey = clusterDetails.name;
|
||||
|
||||
if (this.networkingBeta1ClientMap.hasOwnProperty(clientMapKey)) {
|
||||
return this.networkingBeta1ClientMap[clientMapKey];
|
||||
}
|
||||
|
||||
const kc = this.getKubeConfig(clusterDetails);
|
||||
|
||||
const client = kc.makeApiClient(NetworkingV1beta1Api);
|
||||
|
||||
this.networkingBeta1ClientMap[clientMapKey] = client;
|
||||
|
||||
return client;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,8 @@ describe('KubernetesClientProvider', () => {
|
||||
const kubernetesClientProvider: any = {
|
||||
getCoreClientByClusterDetails: jest.fn(() => clientMock),
|
||||
getAppsClientByClusterDetails: jest.fn(() => clientMock),
|
||||
getAutoscalingClientByClusterDetails: jest.fn(() => clientMock),
|
||||
getNetworkingBeta1Client: jest.fn(() => clientMock),
|
||||
};
|
||||
|
||||
const sut = new KubernetesClientBasedFetcher({
|
||||
|
||||
@@ -16,12 +16,15 @@
|
||||
|
||||
import {
|
||||
AppsV1Api,
|
||||
AutoscalingV1Api,
|
||||
CoreV1Api,
|
||||
ExtensionsV1beta1Ingress,
|
||||
NetworkingV1beta1Api,
|
||||
V1ConfigMap,
|
||||
V1Deployment,
|
||||
V1HorizontalPodAutoscaler,
|
||||
V1Pod,
|
||||
V1ReplicaSet,
|
||||
V1Secret,
|
||||
} from '@kubernetes/client-node';
|
||||
import { KubernetesClientProvider } from './KubernetesClientProvider';
|
||||
import { V1Service } from '@kubernetes/client-node/dist/gen/model/v1Service';
|
||||
@@ -36,6 +39,8 @@ import {
|
||||
export interface Clients {
|
||||
core: CoreV1Api;
|
||||
apps: AppsV1Api;
|
||||
autoscaling: AutoscalingV1Api;
|
||||
networkingBeta1: NetworkingV1beta1Api;
|
||||
}
|
||||
|
||||
export interface KubernetesClientBasedFetcherOptions {
|
||||
@@ -67,6 +72,7 @@ export class KubernetesClientBasedFetcher implements KubernetesFetcher {
|
||||
);
|
||||
}
|
||||
|
||||
// TODO could probably do with a tidy up
|
||||
private fetchByObjectType(
|
||||
serviceId: string,
|
||||
clusterDetails: ClusterDetails,
|
||||
@@ -93,13 +99,18 @@ export class KubernetesClientBasedFetcher implements KubernetesFetcher {
|
||||
serviceId,
|
||||
clusterDetails,
|
||||
).then(r => ({ type: type, resources: r }));
|
||||
case 'secrets':
|
||||
return this.fetchSecretsByServiceId(
|
||||
case 'services':
|
||||
return this.fetchServicesByServiceId(
|
||||
serviceId,
|
||||
clusterDetails,
|
||||
).then(r => ({ type: type, resources: r }));
|
||||
case 'services':
|
||||
return this.fetchServicesByServiceId(
|
||||
case 'horizontalpodautoscalers':
|
||||
return this.fetchHorizontalPodAutoscalersByServiceId(
|
||||
serviceId,
|
||||
clusterDetails,
|
||||
).then(r => ({ type: type, resources: r }));
|
||||
case 'ingresses':
|
||||
return this.fetchIngressesByServiceId(
|
||||
serviceId,
|
||||
clusterDetails,
|
||||
).then(r => ({ type: type, resources: r }));
|
||||
@@ -119,9 +130,15 @@ export class KubernetesClientBasedFetcher implements KubernetesFetcher {
|
||||
const apps = this.kubernetesClientProvider.getAppsClientByClusterDetails(
|
||||
clusterDetails,
|
||||
);
|
||||
const autoscaling = this.kubernetesClientProvider.getAutoscalingClientByClusterDetails(
|
||||
clusterDetails,
|
||||
);
|
||||
const networkingBeta1 = this.kubernetesClientProvider.getNetworkingBeta1Client(
|
||||
clusterDetails,
|
||||
);
|
||||
|
||||
this.logger.debug(`calling cluster=${clusterDetails.name}`);
|
||||
return fn({ core, apps }).then(result => {
|
||||
return fn({ core, apps, autoscaling, networkingBeta1 }).then(result => {
|
||||
return result.body.items;
|
||||
});
|
||||
}
|
||||
@@ -168,20 +185,6 @@ export class KubernetesClientBasedFetcher implements KubernetesFetcher {
|
||||
);
|
||||
}
|
||||
|
||||
private fetchSecretsByServiceId(
|
||||
serviceId: string,
|
||||
clusterDetails: ClusterDetails,
|
||||
): Promise<Array<V1Secret>> {
|
||||
return this.singleClusterFetch<V1Secret>(clusterDetails, ({ core }) =>
|
||||
core.listSecretForAllNamespaces(
|
||||
false,
|
||||
'',
|
||||
'',
|
||||
`backstage.io/kubernetes-id=${serviceId}`,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
private fetchDeploymentsByServiceId(
|
||||
serviceId: string,
|
||||
clusterDetails: ClusterDetails,
|
||||
@@ -209,4 +212,36 @@ export class KubernetesClientBasedFetcher implements KubernetesFetcher {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
private fetchHorizontalPodAutoscalersByServiceId(
|
||||
serviceId: string,
|
||||
clusterDetails: ClusterDetails,
|
||||
): Promise<Array<V1HorizontalPodAutoscaler>> {
|
||||
return this.singleClusterFetch<V1HorizontalPodAutoscaler>(
|
||||
clusterDetails,
|
||||
({ autoscaling }) =>
|
||||
autoscaling.listHorizontalPodAutoscalerForAllNamespaces(
|
||||
false,
|
||||
'',
|
||||
'',
|
||||
`backstage.io/kubernetes-id=${serviceId}`,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
private fetchIngressesByServiceId(
|
||||
serviceId: string,
|
||||
clusterDetails: ClusterDetails,
|
||||
): Promise<Array<ExtensionsV1beta1Ingress>> {
|
||||
return this.singleClusterFetch<ExtensionsV1beta1Ingress>(
|
||||
clusterDetails,
|
||||
({ networkingBeta1 }) =>
|
||||
networkingBeta1.listIngressForAllNamespaces(
|
||||
false,
|
||||
'',
|
||||
'',
|
||||
`backstage.io/kubernetes-id=${serviceId}`,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,8 @@ const DEFAULT_OBJECTS = new Set<KubernetesObjectTypes>([
|
||||
'configmaps',
|
||||
'deployments',
|
||||
'replicasets',
|
||||
'horizontalpodautoscalers',
|
||||
'ingresses',
|
||||
]);
|
||||
|
||||
export const handleGetKubernetesObjectsByServiceId: GetKubernetesObjectsByServiceIdHandler = async (
|
||||
|
||||
@@ -15,11 +15,12 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
ExtensionsV1beta1Ingress,
|
||||
V1ConfigMap,
|
||||
V1Deployment,
|
||||
V1HorizontalPodAutoscaler,
|
||||
V1Pod,
|
||||
V1ReplicaSet,
|
||||
V1Secret,
|
||||
V1Service,
|
||||
} from '@kubernetes/client-node';
|
||||
|
||||
@@ -43,9 +44,10 @@ export type FetchResponse =
|
||||
| PodFetchResponse
|
||||
| ServiceFetchResponse
|
||||
| ConfigMapFetchResponse
|
||||
| SecretFetchResponse
|
||||
| DeploymentFetchResponse
|
||||
| ReplicaSetsFetchResponse;
|
||||
| ReplicaSetsFetchResponse
|
||||
| HorizontalPodAutoscalersFetchResponse
|
||||
| IngressesFetchResponse;
|
||||
|
||||
// TODO fairly sure there's a easier way to do this
|
||||
|
||||
@@ -53,9 +55,10 @@ export type KubernetesObjectTypes =
|
||||
| 'pods'
|
||||
| 'services'
|
||||
| 'configmaps'
|
||||
| 'secrets'
|
||||
| 'deployments'
|
||||
| 'replicasets';
|
||||
| 'replicasets'
|
||||
| 'horizontalpodautoscalers'
|
||||
| 'ingresses';
|
||||
|
||||
export interface PodFetchResponse {
|
||||
type: 'pods';
|
||||
@@ -72,11 +75,6 @@ export interface ConfigMapFetchResponse {
|
||||
resources: Array<V1ConfigMap>;
|
||||
}
|
||||
|
||||
export interface SecretFetchResponse {
|
||||
type: 'secrets';
|
||||
resources: Array<V1Secret>;
|
||||
}
|
||||
|
||||
export interface DeploymentFetchResponse {
|
||||
type: 'deployments';
|
||||
resources: Array<V1Deployment>;
|
||||
@@ -87,6 +85,16 @@ export interface ReplicaSetsFetchResponse {
|
||||
resources: Array<V1ReplicaSet>;
|
||||
}
|
||||
|
||||
export interface HorizontalPodAutoscalersFetchResponse {
|
||||
type: 'horizontalpodautoscalers';
|
||||
resources: Array<V1HorizontalPodAutoscaler>;
|
||||
}
|
||||
|
||||
export interface IngressesFetchResponse {
|
||||
type: 'ingresses';
|
||||
resources: Array<ExtensionsV1beta1Ingress>;
|
||||
}
|
||||
|
||||
// Fetches information from a kubernetes cluster using the cluster details object
|
||||
// to target a specific cluster
|
||||
export interface KubernetesFetcher {
|
||||
|
||||
Reference in New Issue
Block a user