🅱️

Backstage でたわむれる on Google Cloud

2024/09/26に公開

Google Cloud Japan の RyuSA です 👍

最近 Backstage という IDP(Internal Developer Portal) が Platform Engineering の文脈で取り上げられることが増えてきています。Backstage はソフトウェアカタログやテンプレートなどをまとめ、開発者向けのポータルサイトを作り上げることができます。Backstage にはデフォルトで備え付けられている様々な機能がありますが、実際に運用するにはむしろその他のプラグインを多く利用することになります。

本記事では、Google Cloud を活用して Backstage を良い感じにデプロイしてみよう!ということを目標としています。紹介する内容は、Platform Engineering Kaigi 2024の会場でもデモとして公開していました。

https://x.com/ryusa_eng/status/1810576097207009444

Backstage プロジェクトの初期化

詳細は公式ドキュメントを参照してください。

今回は Node.js v20.x を利用して初期化します。Backstage は Prometheus や Istio などのように設定で振る舞いを変更するようなプロダクトではなく、自分で Backstage プロジェクトを初期化、実装、ビルドしデプロイする必要があります。まず npx でプロジェクトを初期化し、ローカルで動作することを確認します。

$ npx @backstage/create-app@latest 
? Enter a name for the app [required] backstage

Creating the app...

🥇  Successfully created backstage

$ cd backstage
$ yarn dev

ローカル開発時、デフォルトで3000(フロントエンド)と7007(バックエンド)のポートを利用します。ブラウザから3000番ポートへアクセスするとトップページが見れます。

Backstage を Google Cloud へデプロイする

Backstage を動かすために、今回は Backstage のドキュメントに記載されている構成(https://backstage.io/docs/deployment/k8s)を参考に次のような構成を組みます。

Cloud SQL for PostgreSQL をバックエンドデータベースとして利用し、コンテナイメージを Artifact Registry へ保存、GKE 上で Backstage そのものを動作させます。

# Artifact Registry を作成
$ gcloud artifacts repositories create backstage-ar --repository-format=docker --location=asia-northeast1

# Cloud SQL インスタンスを作成
$ gcloud sql instances create backstage-db \
  --database-version=POSTGRES_13 \
  --tier=db-f1-micro \
  --network=default \
  --no-assign-ip
$ gcloud sql users set-password postgres --instance=backstage-db --password=$POSTGRES_PASSWORD

# GKE Autopilot を作成
$ gcloud container clusters create-auto "backstage-gke" --region "asia-northeast1" 

次にこのプロジェクトルートで以下のコマンドを実行してコンテナイメージをビルド、Artifact Registry へアップロードします。ただしビルドする前におまじないを app-config.production.yaml へ追加しておいてください。

app-config.production.yaml
auth:
  providers:
-    guest: null
+    # これはProduction環境ではguest認証が無効となるので、強制的にguest認証を有効にする方法です。
+    # TODO 本番環境リリース前に変更する
+    guest: 
+      dangerouslyAllowOutsideDevelopment: true
# プロジェクトのビルド
$ yarn build:all
# コンテナイメージのビルド
$ yarn build-image --tag asia-northeast1-docker.pkg.dev/$PROJECT_ID/backstage-ar/backstage:latest
# push
$ docker push asia-northeast1-docker.pkg.dev/$PROJECT_ID/backstage-ar/backstage:latest

最後に GKE へデータベースへの認証情報やコンテナイメージをデプロイして動作することを確認します。

# Cloud SQL への接続情報を Secret として作成
$ kubectl create secret generic backstage-secret \
  --from-literal=POSTGRES_HOST=$(gcloud sql instances describe backstage-db --format='value(ipAddresses.ipAddress)') \
  --from-literal=POSTGRES_USER=postgres \
  --from-literal=POSTGRES_PASSWORD=$POSTGRES_PASSWORD

# Service Account(Kubernetes) を作成
$ kubectl create serviceaccount backstage

# マニフェストを作成してデプロイする
$ cat <<EOF > manifest.yaml 
apiVersion: apps/v1
kind: Deployment
metadata:
  name: backstage
spec:
  replicas: 1
  selector:
    matchLabels:
      app: backstage
  template:
    metadata:
      labels:
        app: backstage
    spec:
      serviceAccount: backstage
      containers:
      - image: asia-northeast1-docker.pkg.dev/$PROJECT_ID/backstage-ar/backstage:latest
        name: backstage
        ports:
          - containerPort: 7007
            name: http
        envFrom:
        - secretRef:
            name: backstage-db
        resources:
          limits:
            memory: 8Gi
            cpu: 1500m
EOF
$ kubectl apply -f manifest.yaml
$ kubectl expose deployment backstage 7007:7007

# 動作検証のため、7007番ポートを開放してローカルから確認できるようにしておきます
$ kubectl port-forward service/backstage 7007:7007

ローカルのブラウザからアクセスできることを確認しましょう。Backstage のトップページが表示されれば成功です。

ソフトウェアカタログを作成する

Backstage はソフトウェアの一覧や依存関係を管理するソフトウェアカタログの機能を提供しています。そのソフトウェアカタログは Entity という概念で構成されています。この Entity はチームやソフトウェア、API など様々なものを記述し、それらに関係性を持たせたり Annotation をつけることで Backstage 上でいい感じに見たり検索したりできるようになります。

Entity の種類には組み込みで様々なものが定義されています。

  • Component
    • ソフトウェアのユニットやサービスを表現するもので、ひとつの Git リポジトリと大体一緒の概念として扱うことが多いです
  • Resource
    • Componentを維持するためのインフラストラクチャを表します、Cloud SQL インスタンスやGKE クラスタなど
  • Template
  • Group, User
    • Entity のオーナーシップの媒体を表す特別な Entity。外部の認証サービスと連携して動的に作成されることもあります。

たとえば、ひとつ前の節で GKE へデプロイした Backstage を表す Entity は次のような構成として表現できます。

apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
  name: MyBackstage
  description: Hello Backstage
spec:
  type: service
  lifecycle: experiments
  owner: guests
  dependsOn:
    - resource:default/backstage-db
    - resource:default/backstage-gke
    - resource:default/backstage-ar
---
apiVersion: backstage.io/v1alpha1
kind: Resource
metadata:
  name: backstage-db
  description: Cloud SQL for PostgreSQL
  links:
    - url: https://console.cloud.google.com/sql/instances/backstage-db/overview
      title: backstage-db | Google Cloud Console
spec:
  type: database
  owner: user:guest
---
apiVersion: backstage.io/v1alpha1
kind: Resource
metadata:
  name: backstage-gke
  description: GKE Autopilot
  links:
    - url: https://console.cloud.google.com/kubernetes/clusters/details/asia-northeast1/backstage-gke/details
      title: backstage-gke | Google Cloud Console
spec:
  type: kubernetes-cluster
  owner: guests
---
apiVersion: backstage.io/v1alpha1
kind: Resource
metadata:
  name: backstage-ar
  description: Artifact Registry
spec:
  type: container-registry
  owner: guests
---
apiVersion: backstage.io/v1alpha1
kind: User
metadata:
  name: guest
spec:
  memberOf: [guests]
---
apiVersion: backstage.io/v1alpha1
kind: Group
metadata:
  name: guests
spec:
  type: team
  children: []

この Entity を含む YAML ファイルを Backstage へ読み込ませることで、この Backstage が必要とする依存関係などを簡単に確認できるようになります。ローカルの環境で試してみます。

この YAML ファイルをローカルの catalog-info.yaml へ保存し、ローカル向けの設定ファイル app-config.local.yaml を作成してください。その後 Backstage を起動すると Backstage が catalog-info.yaml を読み込み、Component リストに MyBackstage が登録されるようになります。

app-config.local.yaml
catalog:
  rules:
    - allow: [Component, Resource, Location, User, Group]
  locations:
    - type: file
      target: ../../catalog-info.yaml

Techdocs を使ってドキュメントをセットアップする

Backstage はマークダウンで記述されたドキュメントを Entity と紐つける Techdocs を提供しています。どこかにホスティングされているドキュメントを取得し、Backstage 上でドキュメントを読む/検索できます。これにより、他のチームメンバーや横断チームのような自チーム以外のメンバーがコンポーネントの情報へリーチしやすくなります。

まずはローカルで動作することを確認してみます。docs/index.md を作成して適当なマークダウンを記載して保存してください。

docs/index.md
My Backstage
===

This is a sample techdocs.

次に catalog-info.yaml の MyBackstage に Techdocs 用の Annnotation を付与します。これにより MyBackstage コンポーネントの Techdocs 用の設定とドキュメントのありかを設定でき、Backstage がその情報を取得できるようになります。

catalog-info.yaml
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
  name: MyBackstage
  description: Hello Backstage haha
+  annotations:
+    backstage.io/techdocs-ref: dir:.
+    backstage.io/techdocs-entity: component:default/MyBackstage
spec:
...

最後に、内部のドキュメントビルダーが利用する mkdocs.yaml をプロジェクトルート上に作成して保存します。

mkdocs.yaml
site_name: My Backstage
nav:
  - Home: index.md
plugins:
  - techdocs-core

この状態で Backstage の MyBackstage のページへ移動すると、docs/index.md で作成したドキュメントがビルドされ HTML としてレンダリングされています。

Backstage の Techdocs は app-config.yaml の techdocs.builder の値を読み取り、値がデフォルトの local と指定されている場合ドキュメントページをアドホックにビルドし、マークダウンから HTML へ変換しています。この設定はアドホックにビルドされてしまいCPUリソースなどを多く消費してしまい、本番環境には推奨されていません。本番稼働させる際には techdocs.builder=external として指定しアドホックビルドを抑制し、ドキュメントは事前にビルド&ホストしておくことが推奨されています。

コンポーネントを Kubernetes リソースと連携する

Backstage には Kubernetes と接続して「このコンポーネントがどのクラスター/名前空間/リソースで動いているか」といった情報を一緒に表示できるようにするプラグイン @backstage/plugin-kubernetes が提供されています。これによりコンポーネントがどの Kubernetes クラスターのどの Deployment (あるいは Statefulset など)で表現されているのか、またはその接続名などを Backstage 上で確認できるようになります。 MyBackstage は GKE 上で動かしている Deployment と Service に紐ついています。それぞれの Kubernetes リソースにラベリングをし、Backstage の Entity と紐つけましょう。

参考: Installation | Backstage

まずは依存関係をアップデートします。プロジェクトルート上で次の yarn コマンドでプロジェクトの依存関係を更新します。

# フロントエンドプロジェクトの更新
$ yarn --cwd packages/app add @backstage/plugin-kubernetes
# バックエンドプロジェクトの更新
$ yarn --cwd packages/backend add @backstage/plugin-kubernetes-backend

まずはフロントエンドに Kubernetes リソースを確認できるようなタブを追加します。packages/app/src/components/catalog/EntityPage.tsx を開き、次のように変更して保存します。

packages/app/src/components/catalog/EntityPage.tsx
...
+import { EntityKubernetesContent } from '@backstage/plugin-kubernetes';
...

const websiteEntityPage = (
  <EntityLayout>
    <EntityLayout.Route path="/" title="Overview">
      {overviewContent}
    </EntityLayout.Route>

    <EntityLayout.Route path="/ci-cd" title="CI/CD">
      {cicdContent}
    </EntityLayout.Route>

+    <EntityLayout.Route path="/kubernetes" title="Kubernetes">
+      <EntityKubernetesContent refreshIntervalMs={30000} />
+    </EntityLayout.Route>
...

さらにバックエンドサーバーにも Kubernetes のプラグインをインストールします。packages/backend/src/index.ts を次のように修正してバックエンドシステムへプラグインを登録します。

packages/backend/src/index.ts
...
+// kubernetes plugin
+backend.add(import('@backstage/plugin-kubernetes-backend/alpha'));
backend.start();

次にローカル環境の Backstage を GKE に接続します。app-config.local.yaml を以下のように修正します。

app-config.local.yaml
...
+     kubernetes:
+       serviceLocatorMethod:
+         type: 'multiTenant'
+       clusterLocatorMethods:
+         - type: 'gke'
+           projectId: PROJECT_ID
+           region: REGION
+           skipTLSVerify: true
+           authProvider: 'googleServiceAccount'

これで Backstage は PROJECT_ID プロジェクト内の REGION リージョンの GKE と接続できるようになりました。Backstage の Kubernetes Integration の機能では backstage.io/kubernetes-id ラベルが付与されている Kubernetes リソースをピックアップします。そこで Deployment リソースにラベルを付与します。

manifest.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: backstage
+  labels:
+    backstage.io/kubernetes-id: mybackstage
spec:
  replicas: 1
...
  template:
    metadata:
      labels:
        app: backstage
+        backstage.io/kubernetes-id: mybackstage
    spec:
...

これにより GKE にデプロイされている状況を確認できるようになりました。ローカルで再度 Backstage を起動すると、Backstage は GKE へ接続して MyBackstage に紐つく Kubernetes リソースを調査してくれます。Backstage から MyBackstage コンポーネントのページを開くと、新たに Kubernetes タブが生えており、そこから GKE 内の情報を確認できるようになります。

ソフトウェアカタログを更新する

Backstage が追加の依存を持つようになったので catalog-info.yaml を更新しておきます。MyBackstage の依存関係を増やし、外部の catalog-info.yaml を Location 型で登録します。

catalog-info.yaml
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
  name: MyBackstage
  ...
spec:
...
  dependsOn:
    - resource:default/backstage-db
    - resource:default/backstage-gke
    - resource:default/backstage-ar
+    - component:default/backstage-plugin-kubernetes
...
+---
+apiVersion: backstage.io/v1alpha1
+kind: Location
+metadata:
+  name: libraries
+spec:
+  type: url
+  targets:
+    - https://github.com/backstage/backstage/blob/v1.30.4/plugins/kubernetes/catalog-info.yaml

ローカルで起動している Backstage に反映されていれば完了です。このように Backstage では利用している依存関係(インフラストラクチャー、ライブラリ、API など)を相互に接続して一覧にできます。

Backstage を本番環境として Google Cloud へデプロイする

この節では Backstage を本番環境で動かすためのセットアップを進めます。今までローカルで検証してきた内容を、今度は GKE 上にデプロイされた環境で動かすための設定をしていきます。Backstage は組み込みのビルドの機構としてプロジェクト初期化時にプロジェクトルートに作成される app-config.production.yaml を利用して本番向けのコンテナイメージを作成します。この YAML ファイルを変更しつつ、本番環境向けの設定をします。

Workload Identity のセットアップをする

Backstage の Pod から Google Cloud の各種 API を利用するために、Service Account(Kubernetes) から Google Cloud の認証を通すための設定をしておきます。まずはプロジェクト内に Service Account(Google Cloud) を作成しいくつかの IAM Role を付与、さらにその Service Account(Google Cloud) と Service Account(Kubernetes) を Workload Identity 経由で紐つけます。

参考: GKE ワークロードから Google Cloud APIs に対する認証を行う | Google Cloud

# Service Account(Google Cloud)を作成する
$ gcloud iam service-accounts create backstage 

# Service Account(Google Cloud)とプロジェクトのいくつかのロールとを紐つける
$ gcloud projects add-iam-policy-binding $PROJECT_ID \
    --member "serviceAccount:backstage@$PROJECT_ID.iam.gserviceaccount.com" \
    --role "roles/viewer"

$ gcloud projects add-iam-policy-binding $PROJECT_ID \
    --member "serviceAccount:backstage@$PROJECT_ID.iam.gserviceaccount.com" \
    --role "roles/container.viewer"

$ gcloud projects add-iam-policy-binding $PROJECT_ID \
    --member "serviceAccount:backstage@$PROJECT_ID.iam.gserviceaccount.com" \
    --role=roles/storage.objectViewer

# Service Account(Google Cloud)とService Account(Kubernetes)を紐つけ、Service Account(Kubernetes)からGoogle Cloudへアクセスできるようにする
$ gcloud iam service-accounts add-iam-policy-binding backstage@$PROJECT_ID.iam.gserviceaccount.com \
    --role roles/iam.workloadIdentityUser \
    --member "serviceAccount:$PROJECT_ID.svc.id.goog[default/backstage]"

# Service Account(Kubernetes)に紐つけ情報を連携する
$ kubectl annotate serviceaccount backstage \
    iam.gke.io/gcp-service-account=backstage@$PROJECT_ID.iam.gserviceaccount.com

これにより Backstage の Pod が利用する Service Account(Kubernetes) は Service Account(Google Cloud) と連携して Google Cloud 上のリソースへアクセスができるようになりました。

リモートに存在する catalog-info.yaml を利用する

ソフトウェアカタログを作成するの節で作成した catalog-info.yaml と app-config.local.yaml はローカルで完結しており、GKE 上で動いている Backstage には反映されていません。コンテナイメージに本番用にカタログ情報を埋め込んでも良いですがそれでは柔軟にカタログを更新できず、新規プロジェクト作成やライブラリ利用への障壁が高くなってしまいます。

Backstageにはリモート上にホストされている catalog-info.yaml を読み取って動的に構造化する機能が提供されています。Integrations | Backstage Docs

今回は Cloud Storage 上にホストされている catalog-info.yaml を利用して Backstage に Entity を読み込ませます。まずホストするためのバケットを作成し、ローカルで作成した catalog-info.yaml を作成したバケットにアップロードします。

# GCSバケットを作成する
$ gsutil mb YOUR_BUTCKET_NAME

# ローカルのcatalog-info.yamlをGCSバケットにコピーする
$ gsutil cp ./catalog-info.yaml gs://YOUR_BUTCKET_NAME/

次に GKE 上の Backstage がリモートのソフトウェアカタログを読めるように app-config.production.yaml を修正してコンテナイメージを再ビルドし再デプロイします。

app-config.production.yaml
+integrations:
+  googleGcs: {}

catalog:
+  processingInterval: 
+    minutes: 1    
+  rules:
+    - allow: [Component, System, API, Resource, Location, Group, User]
-  locations: []
+  locations:
+    - type: url
+      target: "https://storage.cloud.google.com/YOUR_BUTCKET_NAME/catalog-info.yaml"
# コンテナイメージをビルドしてArtifact Registryへプッシュ、GKEへデプロイする
$ yarn build:all
$ yarn build-image --tag asia-northeast1-docker.pkg.dev/$PROJECT_ID/backstage-ar/backstage:latest
$ docker push asia-northeast1-docker.pkg.dev/$PROJECT_ID/backstage-ar/backstage:latest
$ kubectl rollout restart deployment backstage 

ロールアウト完了後、GKE 上の Backstage はバケットからソフトウェアカタログを読み取り Entity が見えるようになっていることを確認します。

最後にローカルの catalog-info.yaml を更新して依存関係を増やし、バケットへアップロードしておきます。数分すると GKE 上の Backstage で情報が更新されています。

catalog-info.yaml
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
  name: MyBackstage
  ...
spec:
...
  dependsOn:
    - resource:default/backstage-db
    - resource:default/backstage-gke
    - resource:default/backstage-ar
    - component:default/backstage-plugin-kubernetes
+    - resource:default/backstage-gcs
...
+---
+apiVersion: backstage.io/v1alpha1
+kind: Resource
+metadata:
+  name: backstage-gcs
+  description: Cloud Storage for Backstge
+spec:
+  type: s3-bucket
+  owner: user:guest

Techdocs のセットアップ

ローカル環境にて Techdocs を動作させることができたので、次は GKE へデプロイされている環境に Techdocs を設定します。ローカルで開発する場合はローカル上のドキュメントをアドホックにビルドしていましたが、実際の本番環境ではパフォーマンスなどの観点から事前にビルド&ホストしておくことが推奨されています。Backstage には Cloud Storage をドキュメントリモートストレージとしてドキュメントをホストするプラグインが実装されています。今回はその方針を利用するアーキテクチャを採用します。

まずは Cloud Storage のバケットを作成します。(すでに作成済みのバケットでも構いません)

$ gsutil mb YOUR_BUTCKET_NAME

次に techdocs-cli を使いローカルのドキュメントを本番向けにビルドし、Cloud Storage のバケットへデプロイします。npx から利用可能です。

# ドキュメントをビルド
$ npx @techdocs/cli generate

# Cloud Storageのバケットへ公開
$ npx @techdocs/cli publish \
  --publisher-type googleGcs \
  --storage-name YOUR_BUTCKET_NAME \
  --entity default/Component/MyBackstage

これでビルド済みのドキュメントをビルド&デプロイできました。このドキュメントを GKE 上の Backstage から読めるように修正します。ローカルのプロジェクトの app-config.production.yaml を修正し、コンテナイメージを再ビルドしてデプロイします。

app-config.production.yaml
+ techdocs:
+   builder: 'external'
+   publisher:
+     type: 'googleGcs'
+     googleGcs:
+       bucketName: YOUR_BUTCKET_NAME
+       projectId: ${PROJECT_ID}
# コンテナイメージをビルドしてArtifact Registryへプッシュ、GKEへデプロイする
$ yarn build-image --tag asia-northeast1-docker.pkg.dev/$PROJECT_ID/backstage-ar/backstage:latest
$ docker push asia-northeast1-docker.pkg.dev/$PROJECT_ID/backstage-ar/backstage:latest
$ kubectl rollout restart deployment backstage 

ロールアウトが完了するとコンポーネントのページからドキュメントを読むことができるようになります。

Google Cloud の Observability と統合する

本番環境で Backstage を稼働させる場合、やはり可観測性は欲しいですよね。Backstage は OpenTelemetry API を利用してメトリクスやトレース情報を公開できるように実装されています。これを Google Cloud の Observerbility と統合してみましょう。

各種依存関係を先に追加しておきます。

$ yarn --cwd packages/backend add \
    @opentelemetry/sdk-node \
    @opentelemetry/auto-instrumentations-node \
    @opentelemetry/exporter-prometheus \
    @opentelemetry/sdk-trace-base \
    @google-cloud/opentelemetry-cloud-trace-exporter

参考: Setup OpenTelemetry | Backstage

Managed Prometheus

Backstage はデフォルトでいくつかのカスタムメトリクスを提供しています。Managed Prometheus は Google Cloud プロジェクト上の様々な Prometheus 形式のメトリクスを収集して Cloud Monitoring の監視に統合できるサービスです。今回は Backstage のメトリクスを Managed Prometheus に取り込みます。

まずはローカル環境でプロジェクトを変更していきます。packages/backend/o11y/instrumentation.js を次の通りに作成します。

packages/backend/o11y/instrumentation.js
const { NodeSDK } = require('@opentelemetry/sdk-node');
const {
  getNodeAutoInstrumentations,
} = require('@opentelemetry/auto-instrumentations-node');
const { PrometheusExporter } = require('@opentelemetry/exporter-prometheus');

const prometheus = new PrometheusExporter();
const sdk = new NodeSDK({
  metricReader: prometheus,
  instrumentations: [getNodeAutoInstrumentations()],
});

sdk.start();

process.on('SIGTERM', () => {
  sdk.shutdown()
    .then(() => console.log('OpenTelemetrySDK gracefully terminated'))
    .catch(err => console.log('Failed to terminate OpenTelemetrySDK gracefully', err));
});

この実装ではPrometheus形式のメトリクスを公開し、またSIGTERMを受信するとSDKをGracefulにシャットダウンするようになっています。詳細はOpenTelemetrySDKのドキュメントを参照ください。Getting Started - Nodejs| OpenTelemetry

さらに Backstage 起動時にこのスクリプトを同時に起動させる必要があるため、--require オプションでランタイムに読み込ませるようにします。packages/backend/package.json を次のように修正し、yarn workspace backend start コマンドを変更します。

packages/backend/package.json
  "scripts": {
-    "start": "backstage-cli package start",
+    "start": "backstage-cli package start --require ./o11y/instrumentation.js",
    "build": "backstage-cli package build",
    "lint": "backstage-cli package lint",
    "test": "backstage-cli package test",
    "clean": "backstage-cli package clean",
    "build-image": "docker build ../.. -f Dockerfile --tag backstage"
  },

この状態で Backstage を起動すると、:9464/metricsで Prometheus 形式のメトリクスが公開されます。

$ curl localhost:9464/metrics
# HELP http_server_duration Measures the duration of inbound HTTP requests.
# UNIT http_server_duration ms
# TYPE http_server_duration histogram
http_server_duration_count{http_scheme="http",http_method="GET",net_host_name="localhost",http_flavor="1.1",http_status_code="200",net_host_port="7007",http_route="/"} 3
http_server_duration_sum{http_scheme="http",http_method="GET",net_host_name="localhost",http_flavor="1.1",http_status_code="200",net_host_port="7007",http_route="/"} 1350.739066
...

ローカルで動作の確認ができるようになりました。次にこれをビルドしてコンテナイメージへ組み込み、GKE へデプロイします。まずはコンテナイメージにテレメトリ収集スクリプトを追加し、起動コマンドを修正します。

packages/backend/Dockerfile
RUN --mount=type=cache,target=/home/node/.cache/yarn,sharing=locked,uid=1000,gid=1000 \
    yarn install --frozen-lockfile --production --network-timeout 300000

+# Copy instrumentation.js for observability
+COPY --chown=node:node packages/backend/o11y/instrumentation.js ./
+
# Then copy the rest of the backend bundle, along with any other files we might want.
COPY --chown=node:node packages/backend/dist/bundle.tar.gz app-config*.yaml ./
RUN tar xzf bundle.tar.gz && rm bundle.tar.gz

-CMD ["node", "packages/backend", "--config", "app-config.yaml", "--config", "app-config.production.yaml"]
+CMD ["node", "--require", "./instrumentation.js", "packages/backend", "--config", "app-config.yaml", "--config", "app-config.production.yaml"]

コンテナイメージをビルド&プッシュ後に GKE へデプロイすることでメトリクス収集ができるようになります。さらに Managed Prometheus がメトリクスを収集するための設定をします。GKE Autopilot 内にデフォルトでデプロイされている Collector に Backstage のメトリクスポートへクロールしてもらえるように、メトリクスのポートを Deployment に登録し、そのポートをクロールするような PodMonitoring リソースを作成します。

manifest.yaml
...
    spec:
      serviceAccount: backstage
      containers:
      - envFrom:
        - secretRef:
            name: backstage-db
        image: asia-northeast1-docker.pkg.dev/PROJECT_ID/backstage-ar/backstage:latest
        name: backstage
        ports:
          - containerPort: 7000
            name: http
+          - containerPort: 9464
+            name: metrics
        resources:
          limits:
            cpu: "2"
            ephemeral-storage: 1Gi
            memory: 8Gi
          requests:
            cpu: "2"
            ephemeral-storage: 1Gi
            memory: 8Gi
...
podmonitoring.yaml
apiVersion: monitoring.googleapis.com/v1
kind: PodMonitoring
metadata:
  name: mybackstage
spec:
  selector:
    matchLabels:
      app: backstage
  endpoints:
  - port: metrics
    path: /metrics
    interval: 30s

それぞれを GKE へデプロイし、数分ほどすると Metrics Explorer からメトリクスを確認できるようになります。

Cloud Trace でトレース情報を収集する

Backstage はその特性上、本番運用を目標とする場合カスタムプラグインなどを実装することが多いです。その場合プラグイン内でのパフォーマンス調査などのためにトレーシングを入れたくなることがあるでしょう。packages/backend/o11y/instrumentation.js を少し修正し、Backstage 内のトレーシングを Cloud Trace へ連携できるようにします。

packages/backend/o11y/instrumentation.js
...
const { PrometheusExporter } = require('@opentelemetry/exporter-prometheus');
+const { BatchSpanProcessor } = require("@opentelemetry/sdk-trace-base");
+const { TraceExporter } = require("@google-cloud/opentelemetry-cloud-trace-exporter");

const prometheus = new PrometheusExporter();
+const trace = new TraceExporter(projectId=PROJECT_ID);
+const span = new BatchSpanProcessor(trace)

const sdk = new NodeSDK({
  metricReader: prometheus,
+  traceExporter: trace,
  instrumentations: [getNodeAutoInstrumentations()],
+  spanProcessors: [span],
});

console.log('OpenTelemetrySDK successfully started');
...

instrumentation.js を修正後、最後にコンテナイメージをビルドしてデプロイすると Backstage の様々なトレース情報を Cloud Trace から確認できます。たとえば Backstage にデフォルトで実装されているトレース TaskPipelineLoop は次のように表示されます。

Google Cloud の認証情報を連携する

Backstageには認証の機構が組み込まれており、様々な認証形式を連携することができます。Authentication in Backstage | Backstage 特に Google Cloud と統合して使える認証方式として「Googleアカウントでログイン」「IAPでログイン」の2つが提供されています。

いずれも OAuth2.0 アプリケーションとして認証情報を構成し、前者はポップアップからの認証、後者はロードバランサーからの JWT での認証を行います。それぞれの構成方法を解説すると文量が倍になってしまうので簡略いたしますが、いずれも Google Cloud プロジェクトや Google Workspace と統合することができるため、普段の開発時の認証とシームレスに統合できます。

おわり

Backstage はよく Platform Engineering の文脈における IDP として紹介されることも多々あります。残念ながら本記事では Backstage の多くの機能を紹介できておらず、特にソフトウェアテンプレートは強力な機能のひとつですが紹介できていません。ソフトウェアテンプレートはプロジェクトフォーマットの共通化はもちろん、認知負荷の高い問題への知見や知識を共有することができる便利な機能です。多くの記事で紹介されておりますので、ぜひ確認してみてください。Backstage Software Templates | Backstage
そして、もし Google Cloud 上で Backstage の開発/運用を考えることがあれば本記事を参考いただければと思います。

今回は紹介できませんでしたが、たとえば「Google Workspace と連携して動的にユーザーを管理する」「Cloud SQL への接続を Cloud SQL Auth Proxy に切り替える」「Cloud Storage 上のドキュメントを Vertex AI Search に組み込んでセマンティック検索を実装する」など、Backstage on Google Cloud でできることはまだまだありますので、ぜひ試してみてください。

Google Cloud Japan

Discussion