백엔드 플러그인을 새 백엔드 시스템으로 마이그레이션하기
기존 백엔드 플러그인을 새 백엔드 시스템으로 마이그레이션하는 것은 상당히 간단해요.
출처: 문서
본문
기존 백엔드 플러그인을 새 백엔드 시스템으로 마이그레이션하는 것은 상당히 간단해요. 이 과정은 백엔드의 index.ts 파일에서 배선되는 Router를 반환하는 대부분의 플러그인에서 유사해요. 우리가 해야 할 핵심은 플러그인이 필요로 하는 의존성이 사용 가능하도록 하고, 라우터를 HTTP 라우터 서비스에 등록하는 것이에요.
Kubernetes 백엔드 플러그인을 마이그레이션하는 예시를 살펴보아요. 기존(구) 시스템에서 kubernetes 백엔드는 다음과 같이 구성돼요:
// @backstage/plugin-kubernetes-backend/src/service/router.tsimport { KubernetesBuilder } from './KubernetesBuilder';export interface RouterOptions { logger: Logger; config: Config; catalogApi: CatalogApi; clusterSupplier?: KubernetesClustersSupplier; discovery: PluginEndpointDiscovery;}export async function createRouter( options: RouterOptions,): Promise<express.Router> { const { router } = await KubernetesBuilder.createBuilder(options) .setClusterSupplier(options.clusterSupplier) .build(); return router;}
새 백엔드 시스템에서 KubernetesBuilder가 만든 router를 재사용할 수 있어요. 위 RouterOptions에 지정된 의존성이 사용 가능한지만 확인하면 돼요. 그것들은 모두 coreServices의 일부이므로 마이그레이션이 쉬워요.
import { coreServices, createBackendPlugin,} from '@backstage/backend-plugin-api';import { catalogServiceRef } from '@backstage/plugin-catalog-node';import { Router } from 'express';import { KubernetesBuilder } from './KubernetesBuilder';export const kubernetesPlugin = createBackendPlugin({ pluginId: 'kubernetes', register(env) { env.registerInit({ deps: { logger: coreServices.logger, config: coreServices.rootConfig, catalogApi: catalogServiceRef, discovery: coreServices.discovery, // The http router service is used to register the router created by the KubernetesBuilder. http: coreServices.httpRouter, }, async init({ config, logger, catalogApi, discovery, http }) { const { router } = await KubernetesBuilder.createBuilder({ config, logger, catalogApi, discovery, }).build(); // We register the router with the http service. http.use(router); }, }); },});
마지막으로 src/index.ts에서 플러그인 인스턴스를 패키지의 기본 내보내기로 다시 내보내는지 확인하세요:
export { kubernetesPlugin as default } from './plugin.ts';
완료! 이 플러그인의 사용자는 이제 플러그인 패키지를 가져와 백엔드에 등록할 수 있어요:
// packages/backend/src/index.tsbackend.add(import('@backstage/plugin-kubernetes-backend'));
날카로운 독자들이 알아차렸을 한 가지가 빠져 있어요: clusterSupplier 옵션이 원래 플러그인에서 빠져 있어요. 그것을 추가하고 대안을 논의해 보아요.
한 가지 대안은 정적 구성을 사용해 클러스터 공급자를 구축할 수 있게 하는 것이에요. 예를 들어 선택할 수 있는 내장 구현 모음이 있거나, ClusterSupplier가 어떻게 작동해야 하는지의 로직이 전부 구성으로 결정되거나, 둘의 조합일 수 있어요. 사용자 지정에는 항상 가능할 때마다 정적 구성을 사용하는 것이 선호돼요. 이 경우 예를 들어 클러스터 공급자를 다음과 같이 구성할 수 있다고 상상할 수 있어요:
/* omitted imports but they remain the same as above */const kubernetesPlugin = createBackendPlugin({ pluginId: 'kubernetes', register(env) { env.registerInit({ deps: { /* omitted dependencies but they remain the same as above */ }, async init({ config, logger, catalogApi, discovery, http }) { // Note that in a real implementation this would be done by the `KubernetesBuilder` instead, // but here we've extracted it into a separate call to highlight the example. const configuredClusterSupplier = readClusterSupplierFromConfig(config); const { router } = await KubernetesBuilder.createBuilder({ config, logger, catalogApi, discovery, }) .setClusterSupplier(configuredClusterSupplier) .build(); http.use(router); }, }); },});
그러나 정적 구성으로는 할 수 없는 많은 종류의 사용자 지정이 있어요. 이 경우 통합자가 ClusterSupplier 인터페이스의 임의 구현을 만들 수 있게 하고 싶은데, 이는 결국 코드를 통한 구현을 요구해요. 이때 새 백엔드 시스템의 확장 지점이 유용하게 쓰여요.
새 확장 지점 API는 모듈이 백엔드 플러그인 자체에 기능을 추가할 수 있게 해요. 이 경우 추가 ClusterSupplier를 추가할 수 있어요. 확장 지점을 사용해 커스텀 공급자 설치 지원을 어떻게 추가할 수 있는지 살펴보아요. 이렇게 하면 통합자가 커스텀 ClusterSupplier 구현을 가진 자체 내부 모듈을 만들 수 있어요.
먼저 확장 지점을 정의할 수 있는 @backstage/plugin-kubernetes-node 패키지를 만들겠어요. 별도 패키지는 플러그인 패키지 자체에 직접 의존하지 않기 위해 사용돼요. 새 패키지를 만든 뒤 확장 지점을 다음과 같이 정의해요:
import { createExtensionPoint } from '@backstage/backend-plugin-api';export interface KubernetesClusterSupplierExtensionPoint { setClusterSupplier(supplier: KubernetesClustersSupplier): void;}/** * An extension point that allows other plugins to set the cluster supplier. */export const kubernetesClustersSupplierExtensionPoint = createExtensionPoint<KubernetesClusterSupplierExtensionPoint>({ id: 'kubernetes.cluster-supplier', });
확장 지점을 설계하는 방법에 대한 자세한 내용은 확장 지점 문서를 참고하세요.
다음으로 Kubernetes 백엔드 플러그인 자체에 이 확장 지점 지원을 추가해야 해요:
/* omitted other imports but they remain the same as above */import { kubernetesClustersSupplierExtensionPoint } from '@backstage/plugin-kubernetes-node';export const kubernetesPlugin = createBackendPlugin({ pluginId: 'kubernetes', register(env) { let clusterSupplier: KubernetesClustersSupplier | undefined = undefined; // We register the extension point with the backend, which allows modules to // register their own ClusterSupplier. env.registerExtensionPoint(kubernetesClustersSupplierExtensionPoint, { setClusterSupplier(supplier) { if (clusterSupplier) { throw new Error('ClusterSupplier may only be set once'); } clusterSupplier = supplier; }, }); env.registerInit({ deps: { /* omitted dependencies but they remain the same as above */ }, async init({ config, logger, catalogApi, discovery, http }) { const { router } = await KubernetesBuilder.createBuilder({ config, logger, catalogApi, discovery, }) .setClusterSupplier(clusterSupplier) .build(); http.use(router); }, }); },});
그리고 그게 전부예요! 이제 kubernetes 백엔드 플러그인에 클러스터를 추가하는 모듈을 만들 수 있어요. 다음은 GoogleContainerEngineSupplier를 kubernetes 백엔드에 추가하는 모듈의 예시예요:
import { kubernetesClustersSupplierExtensionPoint } from '@backstage/plugin-kubernetes-node';// This is a custom implementation of the ClusterSupplier interface.import { GoogleContainerEngineSupplier } from './GoogleContainerEngineSupplier';export default createBackendModule({ pluginId: 'kubernetes', moduleId: 'gke-supplier', register(env) { env.registerInit({ deps: { supplier: kubernetesClustersSupplierExtensionPoint, }, async init({ supplier }) { supplier.setClusterSupplier(new GoogleContainerEngineSupplier()); }, }); },});
위 모듈은 통합자가 kubernetes 백엔드 플러그인과 함께 설치할 수 있어요:
backend.add(import('@backstage/plugin-kubernetes-backend'));backend.add(import('@internal/gke-cluster-supplier'));
개발 서버
로컬 개발 서버에서 마이그레이션한 플러그인을 실행하려면 아래 단계를 따르세요:
-
먼저, 존재한다면
src/run.ts와src/service/standaloneServer.ts파일을 삭제하세요. (backstage-cli는 이전에 이 파일들을 사용해 레거시 백엔드 플러그인을 로컬에서 실행했지만, 더 이상 필요하지 않아요.) -
다음으로
dev/index.ts파일에 새 개발 백엔드를 만드세요. 개발 서버는 주로 플러그인을 로컬에서 실행하는 데 사용되는 백엔드 앱의 라이트 버전이므로, 간단한kubernetes백엔드 로컬 개발 서버는 다음과 같을 거예요:
in dev/index.js
// This package should be installed as a `dev` dependencyimport { createBackend } from '@backstage/backend-defaults';const backend = createBackend();// Path to the file where the plugin is export as defaultbackend.add(import('../src'));backend.start();
위에서 만든 개발 서버는 기본 의존성 팩토리로 자동 구성되지만, 플러그인이 의존하는 rootConfig 서비스 같은 일부 서비스를 mock해야 한다면 mockServices 팩토리 중 하나를 사용할 수 있어요:
in dev/index.js
//...// This package should be installed as `devDependencies`import { mockServices } from '@backstage/backend-test-utils';const backend = createBackend();// ...backend.add( mockServices.rootConfig.factory({ data: { // your config mocked values goes here }, }),);// ...
하나 이상의 서비스에 대한 자체 커스텀 mock 팩토리를 만들고 싶다면 커스텀 서비스 구현 문서와 핵심 서비스 구성 페이지를 확인하세요.
- 이제 마지막으로 플러그인 루트 폴더에서
yarn start를 실행해 플러그인을 로컬에서 시작할 수 있어요.
구 백엔드 시스템 지원 제거
위 가이드를 따라 새 백엔드 플러그인을 내보냈다면 구 백엔드 플러그인을 폐기하고 제거하는 단계는 다음과 같아요:
기본 내보내기 외의 공용 내보내기 폐기
먼저 createRouter와 routerOptions가 폐기(deprecated)로 표시되어 사용자가 새 시스템으로 마이그레이션할 시간과 신호를 얻도록 하세요(한 릴리스에서 폐기하고 다음 릴리스에서 폐기 표시를 제거하는 것을 권장해요).
이는 레거시 내보내기에 @deprecated 주석을 추가해 수행돼요. 플러그인이 내부적으로 createRouter를 계속 사용할 수는 있지만 공용 API의 일부로 내보내서는 안 된다는 점에 주목할 만해요. 마이그레이션한 플러그인에서 create router와 상대 import를 재사용한다면, createRouter 내보내기가 삭제된 뒤 폐기된 import를 제거하도록 내부 코드를 리팩터링해야 해요. 마이그레이션한 플러그인에서 @backstage/backend-common과 @backstage/backend-tasks의 사용을 피하는 것이 권장되는데, 레거시 시스템 지원 종료와 함께 삭제될 것이기 때문이에요. 대부분의 폐기된 import에는 새 백엔드 시스템으로 마이그레이션한 뒤 사용을 중단하는 방법에 대한 지침이 있어요.
@backstage/plugin-kubernetes-backend/src/service/router.ts
import { KubernetesBuilder } from './KubernetesBuilder';/*** @public* @deprecated Please migrate to the new backend system.*/export interface RouterOptions { logger: Logger; config: Config; catalogApi: CatalogApi; clusterSupplier?: KubernetesClustersSupplier; discovery: PluginEndpointDiscovery;}/*** @public* @deprecated Please migrate to the new backend system.*/export async function createRouter( options: RouterOptions,): Promise<express.Router> { const { router } = await KubernetesBuilder.createBuilder(options) .setClusterSupplier(options.clusterSupplier) .build(); return router;}
플러그인에 api-report.md 파일이 있다면 이후 yarn build:api-reports를 실행하세요.
API 리포트를 검사해 새 백엔드 플러그인 외의 다른 내보내기를 찾는 것을 권장해요. 새 백엔드 시스템의 플러그인은 옵션을 전달하는 대신 확장 지점으로 확장되므로 대부분 폐기되어야 할 거예요. 백엔드 플러그인과 함께 사용되는 모든 종류의 빌더나 헬퍼 메서드는 해당 플러그인 전용 라이브러리 패키지(예: plugin-kubernetes-backend-node 패키지, 자세한 내용은 패키지 역할 문서 참조)로 이동해야 해요.
폐기 제거 후 index.ts에 남아 있어야 할 것은 기본 내보내기뿐이에요:
@backstage/plugin-kubernetes-backend/src/index.ts
export { kubernetesPlugin as default } from './plugin';
/alpha 서브패스가 있으면 폐기
이전에 alpha 내보내기로 새 백엔드 시스템을 지원한 경우 alpha 내보내기를 폐기하고 index.ts에서 다시 내보내세요.
@backstage/-backend/src/alpha.ts
/*** @alpha* @deprecated Please import from the root path instead.*/export default createPlugin({ //...});