(feat): easier Kubernetes local development steps (#12373)

* (feat): easier kubernetes local deevlopment steps

Signed-off-by: Matthew Clarke <mclarke@spotify.com>

* add changeset

Signed-off-by: Matthew Clarke <mclarke@spotify.com>

* update api docs

Signed-off-by: Matthew Clarke <mclarke@spotify.com>

* fix typo

Signed-off-by: Matthew Clarke <mclarke@spotify.com>
This commit is contained in:
Matthew Clarke
2022-07-07 03:45:25 -04:00
committed by GitHub
parent 3a6f3536f3
commit f5c9730639
12 changed files with 84 additions and 134 deletions
+13
View File
@@ -0,0 +1,13 @@
---
'@backstage/plugin-kubernetes': minor
'@backstage/plugin-kubernetes-backend': minor
---
Add `localKubectlProxy` cluster locator method to make local development simpler to setup.
Consolidated no-op server side auth decorators.
The following Kubernetes auth decorators are now one class (`ServerSideKubernetesAuthProvider`):
- `AwsKubernetesAuthProvider`
- `AzureKubernetesAuthProvider`
- `ServiceAccountKubernetesAuthProvider`
@@ -57,10 +57,17 @@ This is an array used to determine where to retrieve cluster configuration from.
Valid cluster locator methods are:
- [`localKubectlProxy`](#localKubectlProxy)
- [`config`](#config)
- [`gke`](#gke)
- [custom `KubernetesClustersSupplier`](#custom-kubernetesclusterssupplier)
#### `localKubectlProxy`
This cluster locator method will assume a locally running [`kubectl proxy`](https://kubernetes.io/docs/tasks/extend-kubernetes/http-proxy-access-api/#using-kubectl-to-start-a-proxy-server) process using the default port (8001).
NOTE: This cluster locator method is for local development only and should not be used in production.
#### `config`
This cluster locator method will read cluster information from your app-config
@@ -2,48 +2,34 @@
This can be used to run the kubernetes plugin locally against a mock service.
# Viewing in local Minikube running Backstage locally
# Viewing in local Kind running Backstage locally
## Prerequisites
- kubectl installed
- Minikube installed, with the following addons
- metrics-server
- ingress
- jq installed
- [kubectl installed](https://kubernetes.io/docs/tasks/tools/#kubectl)
- [Kind installed](https://kind.sigs.k8s.io/docs/user/quick-start/)
- Backstage locally built and ready to run
## Steps
1. Start minikube
2. Get the Kubernetes master base URL `kubectl cluster-info`
3. Apply manifests `kubectl apply -f dice-roller-manifests.yaml`
4. Get service account token (see below)
5. Start Backstage UI and backend
6. Register existing component in Backstage
- https://github.com/mclarke47/dice-roller/blob/master/catalog-info.yaml
1. Start kind
2. Apply manifests `kubectl apply -f plugins/kubernetes-backend/examples/dice-roller/dice-roller-manifests.yaml`
3. Run `kubectl proxy`
4. In separate terminal windows start Backstage UI and backend
5. Register a test component ([example](https://github.com/mclarke47/dice-roller/blob/master/catalog-info.yaml))
6. Visit [kubernetes plugin page](http://localhost:3000/catalog/default/component/dice-roller/kubernetes)
Add or update `app-config.local.yaml` with the following:
### Example `app-config.local.yaml`
```yaml
kubernetes:
serviceLocatorMethod:
type: 'multiTenant'
clusterLocatorMethods:
- type: 'config'
clusters:
- url: <KUBERNETES MASTER BASE URL FROM STEP 2>
name: minikube
serviceAccountToken: <TOKEN FROM STEP 4>
authProvider: 'serviceAccount'
- type: 'localKubectlProxy'
catalog:
locations:
- type: url
target: https://github.com/mclarke47/dice-roller/blob/master/catalog-info.yaml
```
### Getting the service account token
Mac copy to clipboard:
```
kubectl get secret $(kubectl get sa dice-roller -o=json | jq -r '.secrets[0].name') -o=json | jq -r '.data["token"]' | base64 --decode | pbcopy
```
Paste into `app-config.local.yaml` `kubernetes.clusters[0].serviceAccountToken`
@@ -14,16 +14,25 @@
* limitations under the License.
*/
import { KubernetesAuthProvider } from './types';
import { KubernetesRequestBody } from '@backstage/plugin-kubernetes-common';
import { ClusterDetails, KubernetesClustersSupplier } from '../types/types';
export class ServiceAccountKubernetesAuthProvider
implements KubernetesAuthProvider
export class LocalKubectlProxyClusterLocator
implements KubernetesClustersSupplier
{
async decorateRequestBodyForAuth(
requestBody: KubernetesRequestBody,
): Promise<KubernetesRequestBody> {
// No-op, with service account for auth, cluster config/details should already have serviceAccountToken
return requestBody;
private readonly clusterDetails: ClusterDetails[];
public constructor() {
this.clusterDetails = [
{
name: 'local',
url: 'http:/localhost:8001',
authProvider: 'localKubectlProxy',
skipMetricsLookup: true,
},
];
}
async getClusters(): Promise<ClusterDetails[]> {
return this.clusterDetails;
}
}
@@ -19,6 +19,7 @@ import { Duration } from 'luxon';
import { ClusterDetails, KubernetesClustersSupplier } from '../types/types';
import { ConfigClusterLocator } from './ConfigClusterLocator';
import { GkeClusterLocator } from './GkeClusterLocator';
import { LocalKubectlProxyClusterLocator } from './LocalKubectlProxyLocator';
class CombinedClustersSupplier implements KubernetesClustersSupplier {
constructor(readonly clusterSuppliers: KubernetesClustersSupplier[]) {}
@@ -45,6 +46,8 @@ export const getCombinedClusterSupplier = (
.map(clusterLocatorMethod => {
const type = clusterLocatorMethod.getString('type');
switch (type) {
case 'localKubectlProxy':
return new LocalKubectlProxyClusterLocator();
case 'config':
return ConfigClusterLocator.fromConfig(clusterLocatorMethod);
case 'gke':
@@ -49,6 +49,9 @@ export class KubernetesAuthTranslatorGenerator {
case 'oidc': {
return new OidcKubernetesAuthTranslator();
}
case 'localKubectlProxy': {
return new NoopKubernetesAuthTranslator();
}
default: {
throw new Error(
`authProvider "${authProvider}" has no KubernetesAuthTranslator associated with it`,
+3 -27
View File
@@ -32,17 +32,6 @@ import { V1ReplicaSet } from '@kubernetes/client-node';
import { V1Service } from '@kubernetes/client-node';
import { V1StatefulSet } from '@kubernetes/client-node';
// Warning: (ae-forgotten-export) The symbol "KubernetesAuthProvider" needs to be exported by the entry point index.d.ts
// Warning: (ae-missing-release-tag) "AwsKubernetesAuthProvider" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
export class AwsKubernetesAuthProvider implements KubernetesAuthProvider {
// (undocumented)
decorateRequestBodyForAuth(
requestBody: KubernetesRequestBody,
): Promise<KubernetesRequestBody>;
}
// Warning: (ae-forgotten-export) The symbol "ClusterProps" needs to be exported by the entry point index.d.ts
// Warning: (ae-missing-release-tag) "Cluster" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
@@ -147,6 +136,7 @@ export function formatClusterLink(
options: FormatClusterLinkOptions,
): string | undefined;
// Warning: (ae-forgotten-export) The symbol "KubernetesAuthProvider" needs to be exported by the entry point index.d.ts
// Warning: (ae-missing-release-tag) "GoogleKubernetesAuthProvider" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
@@ -160,18 +150,6 @@ export class GoogleKubernetesAuthProvider implements KubernetesAuthProvider {
): Promise<KubernetesRequestBody>;
}
// Warning: (ae-missing-release-tag) "GoogleServiceAccountAuthProvider" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
export class GoogleServiceAccountAuthProvider
implements KubernetesAuthProvider
{
// (undocumented)
decorateRequestBodyForAuth(
requestBody: KubernetesRequestBody,
): Promise<KubernetesRequestBody>;
}
// Warning: (ae-missing-release-tag) "GroupedResponses" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
@@ -386,10 +364,8 @@ export const PodsTable: ({
// @public (undocumented)
export const Router: (props: { refreshIntervalMs?: number }) => JSX.Element;
// Warning: (ae-missing-release-tag) "ServiceAccountKubernetesAuthProvider" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
export class ServiceAccountKubernetesAuthProvider
// @public
export class ServerSideKubernetesAuthProvider
implements KubernetesAuthProvider
{
// (undocumented)
@@ -1,27 +0,0 @@
/*
* Copyright 2020 The Backstage 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.
*/
import { KubernetesAuthProvider } from './types';
import { KubernetesRequestBody } from '@backstage/plugin-kubernetes-common';
export class AwsKubernetesAuthProvider implements KubernetesAuthProvider {
async decorateRequestBodyForAuth(
requestBody: KubernetesRequestBody,
): Promise<KubernetesRequestBody> {
// No-op, with aws auth, server's AWS credentials are used for access
return requestBody;
}
}
@@ -1,27 +0,0 @@
/*
* Copyright 2020 The Backstage 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.
*/
import { KubernetesAuthProvider } from './types';
import { KubernetesRequestBody } from '@backstage/plugin-kubernetes-common';
export class AzureKubernetesAuthProvider implements KubernetesAuthProvider {
async decorateRequestBodyForAuth(
requestBody: KubernetesRequestBody,
): Promise<KubernetesRequestBody> {
// No-op, with azure auth, server's Azure credentials are used for access
return requestBody;
}
}
@@ -17,11 +17,8 @@
import { KubernetesRequestBody } from '@backstage/plugin-kubernetes-common';
import { KubernetesAuthProvider, KubernetesAuthProvidersApi } from './types';
import { GoogleKubernetesAuthProvider } from './GoogleKubernetesAuthProvider';
import { ServiceAccountKubernetesAuthProvider } from './ServiceAccountKubernetesAuthProvider';
import { AwsKubernetesAuthProvider } from './AwsKubernetesAuthProvider';
import { ServerSideKubernetesAuthProvider } from './ServerSideAuthProvider';
import { OAuthApi, OpenIdConnectApi } from '@backstage/core-plugin-api';
import { GoogleServiceAccountAuthProvider } from './GoogleServiceAccountAuthProvider';
import { AzureKubernetesAuthProvider } from './AzureKubernetesAuthProvider';
import { OidcKubernetesAuthProvider } from './OidcKubernetesAuthProvider';
export class KubernetesAuthProviders implements KubernetesAuthProvidersApi {
@@ -41,16 +38,23 @@ export class KubernetesAuthProviders implements KubernetesAuthProvidersApi {
);
this.kubernetesAuthProviderMap.set(
'serviceAccount',
new ServiceAccountKubernetesAuthProvider(),
new ServerSideKubernetesAuthProvider(),
);
this.kubernetesAuthProviderMap.set(
'googleServiceAccount',
new GoogleServiceAccountAuthProvider(),
new ServerSideKubernetesAuthProvider(),
);
this.kubernetesAuthProviderMap.set(
'aws',
new ServerSideKubernetesAuthProvider(),
);
this.kubernetesAuthProviderMap.set('aws', new AwsKubernetesAuthProvider());
this.kubernetesAuthProviderMap.set(
'azure',
new AzureKubernetesAuthProvider(),
new ServerSideKubernetesAuthProvider(),
);
this.kubernetesAuthProviderMap.set(
'localKubectlProxy',
new ServerSideKubernetesAuthProvider(),
);
if (options.oidcProviders) {
@@ -1,5 +1,5 @@
/*
* Copyright 2020 The Backstage Authors
* Copyright 2022 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,13 +17,18 @@
import { KubernetesAuthProvider } from './types';
import { KubernetesRequestBody } from '@backstage/plugin-kubernetes-common';
export class GoogleServiceAccountAuthProvider
/**
* No-op KubernetesAuthProvider, authorization will be handled in the kubernetes-backend plugin
*
* @public
*/
export class ServerSideKubernetesAuthProvider
implements KubernetesAuthProvider
{
async decorateRequestBodyForAuth(
requestBody: KubernetesRequestBody,
): Promise<KubernetesRequestBody> {
// No-op, with google service account auth, server's AWS credentials are used for access
// No-op, auth will be taken care of on the server-side
return requestBody;
}
}
@@ -17,7 +17,5 @@
export { kubernetesAuthProvidersApiRef } from './types';
export type { KubernetesAuthProvidersApi } from './types';
export { KubernetesAuthProviders } from './KubernetesAuthProviders';
export { AwsKubernetesAuthProvider } from './AwsKubernetesAuthProvider';
export { GoogleKubernetesAuthProvider } from './GoogleKubernetesAuthProvider';
export { GoogleServiceAccountAuthProvider } from './GoogleServiceAccountAuthProvider';
export { ServiceAccountKubernetesAuthProvider } from './ServiceAccountKubernetesAuthProvider';
export { ServerSideKubernetesAuthProvider } from './ServerSideAuthProvider';