(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:
@@ -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`
|
||||
|
||||
+18
-9
@@ -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':
|
||||
|
||||
+3
@@ -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`,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
+8
-3
@@ -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';
|
||||
|
||||
Reference in New Issue
Block a user