「Backstage」でユーザーからのフィードバックを収集する仕組みを作ってみよう
第4回の今回は、「Backstage」にKeycloak認証とentity-feedbackプラグインを導入し、ユーザーからのフィードバックを収集する仕組みを構築する方法について解説します
6:30
はじめに
本連載も4回目となり、コンテナオーケストレーション、APIゲートウェイ、認証認可、ゴールデンパステンプレートとプラットフォームの機能を拡充してきました。新しい機能を追加していくことも重要ですが、これまで追加した機能を振り返り、改善にいかすことも大切です。
そこで今回は、これまで作った機能でユーザー(プロダクトチーム)がどのような体験をしているのか、ユーザーは満足しているのかを把握するため、ユーザーからのフィードバックを収集する仕組みを追加していきます。ユーザーからのフィードバックをプラットフォームに反映させるプロセスは「プラットフォームエンジニアリングの成熟度モデル」でも重要な要素の1つとして挙げられています。
今回、フィードバックの仕組みに利用するのは、Backstageの「entity-feedback」プラグインです。これにより、ユーザーは各機能に対するフィードバックをプラットフォームチームへ伝えられるようになります。
また、entity-feedbackプラグインでフィードバックを行えるのは認証済みユーザーのみです。そのため、まずはBackstageをKeycloakに連携させ、ユーザー認証を行えるようにしていきます。
なお、今回利用したソースコードはこちらに格納しています。
Backstageのセットアップ
第3回で構築したBackstageはバージョンの関係でKeycloakとの連携が少し複雑になってしまうため、最新版のBackstageを新たにセットアップして、そこにKeycloak連携とフィードバック機能の追加を行っていきます。 詳しい説明は第3回の解説を参照してください。
まずは、以下のコマンドを実行して、Backstageの初期セットアップを行います。
npx @backstage/create-app@0.8.2以下のようにBackstageアプリケーションの名前(ディレクトリ名)を聞かれるため、任意の名前を入力します。
今回は、backstage-appと入力しました。
Keycloak連携
BackstageをKeycloakに連携させるための手順を説明します。 今回、ユーザーのログインだけでなく、ユーザーの管理もKeycloakで行いたいので、以下の2つの設定を行います。
- Keycloakでユーザーがログインできるようにする設定
- KeycloakのユーザーとグループをBackstageに同期する設定
ここでは簡単な手順を示します。 詳しい情報が必要な方は、以下のドキュメントを参照してください。
- OIDC provider from scratch: 汎用的なOIDC(OpenID Connect)認証の設定方法
- Keycloak backend plugin for Backstage:
catalog-backend-module-keycloakプラグインを利用して、Keycloakのユーザーとグループを同期する設定(バックエンドのみ)
フロントエンドコード修正
フロントエンドにKeycloakでログインするための設定を追加していきます。 なお、Keycloakのユーザーとグループを同期する設定はフロントエンド側に機能を持たないため、フロントエンドで設定する項目はありません。
Keycloakでのログインページの設定を追加します。
# packages/app/src/App.tsx
import { createApp } from '@backstage/frontend-defaults';
import catalogPlugin from '@backstage/plugin-catalog/alpha';
import { navModule } from './modules/nav';
+import {
+ OpenIdConnectApi,
+ ProfileInfoApi,
+ BackstageIdentityApi,
+ SessionApi,
+} from '@backstage/core-plugin-api';
+import { OAuth2 } from '@backstage/core-app-api';
+import { SignInPageBlueprint } from '@backstage/plugin-app-react';
+import { SignInPage } from '@backstage/core-components';
+import {
+ createApiRef,
+ createFrontendModule,
+ configApiRef,
+ discoveryApiRef,
+ oauthRequestApiRef,
+ ApiBlueprint,
+} from '@backstage/frontend-plugin-api';
+
+const keycloakAuthApiRef = createApiRef<
+ OpenIdConnectApi & ProfileInfoApi & BackstageIdentityApi & SessionApi
+>().with({
+ id: 'auth.keycloak',
+});
+
+const keycloakAuthApi = ApiBlueprint.make({
+ name: 'keycloak',
+ params: defineParams =>
+ defineParams({
+ api: keycloakAuthApiRef,
+ deps: {
+ discoveryApi: discoveryApiRef,
+ oauthRequestApi: oauthRequestApiRef,
+ configApi: configApiRef,
+ },
+ factory: ({ discoveryApi, oauthRequestApi, configApi }) =>
+ OAuth2.create({
+ configApi,
+ discoveryApi,
+ oauthRequestApi,
+ environment: configApi.getOptionalString('auth.environment'),
+ provider: {
+ id: 'oidc',
+ title: 'Keycloak',
+ icon: () => null,
+ },
+ defaultScopes: ['openid', 'profile', 'email'],
+ popupOptions: {
+ size: {
+ width: 900,
+ height: 600,
+ },
+ },
+ }),
+ }),
+});
+
+const signInPage = SignInPageBlueprint.make({
+ params: {
+ loader: async () => props =>
+ (
+ <SignInPage
+ {...props}
+ provider={{
+ id: 'keycloak-auth-provider',
+ title: 'Keycloak',
+ message: 'Sign In using Keycloak',
+ apiRef: keycloakAuthApiRef,
+ }}
+ />
+ ),
+ },
+});
export default createApp({
- features: [catalogPlugin, navModule],
+ features: [
+ catalogPlugin,
+ navModule,
+ createFrontendModule({
+ pluginId: 'app',
+ extensions: [keycloakAuthApi, signInPage],
+ }),
+ ],
});バックエンドコード修正
Backstageのバックエンドに以下のプラグインを追加します。
@backstage/plugin-auth-backend-module-oidc-provider: OIDC認証の設定を行うプラグイン@backstage-community/plugin-catalog-backend-module-keycloak: KeycloakのユーザーとグループをBackstageに同期するためのプラグイン
yarn workspace backend add @backstage/plugin-auth-backend-module-oidc-provider @backstage-community/plugin-catalog-backend-module-keycloakBackstageのバックエンドコードに、上記2つのプラグインを初期化する設定を追加します。
# packages/backend/src/index.ts
+backend.add(import('@backstage/plugin-auth-backend-module-oidc-provider'));
+backend.add(
+ import('@backstage-community/plugin-catalog-backend-module-keycloak'),
+);
backend.start();設定ファイルの書き換え
Backstageの設定ファイル(app-config.production.yaml)にKeycloakとの連携設定を追加します。${AUTH_OIDC_METADATA_URL}のような変数は、KubernetesのSecretから環境変数として渡される想定です。
# 認証認可設定
auth:
+ environment: production
+ session:
+ secret: ${AUTH_SESSION_SECRET}
providers:
guest:
userEntityRef: user:default/guest
ownershipEntityRefs: [group:default/guests]
dangerouslyAllowOutsideDevelopment: true
+ oidc:
+ production:
+ metadataUrl: ${AUTH_OIDC_METADATA_URL}
+ clientId: ${AUTH_OIDC_CLIENT_ID}
+ clientSecret: ${AUTH_OIDC_CLIENT_SECRET}
+ prompt: auto
+ signIn:
+ resolvers:
+ - resolver: emailMatchingUserEntityProfileEmail
catalog:
+ providers:
+ # Keycloakのユーザーとグループを同期する設定
+ keycloakOrg:
+ default:
+ baseUrl: ${CATALOG_KEYCLOAK_BASE_URL}
+ loginRealm: ${CATALOG_KEYCLOAK_LOGIN_REALM}
+ realm: ${CATALOG_KEYCLOAK_REALM}
+ clientId: ${CATALOG_KEYCLOAK_CLIENT_ID}
+ clientSecret: ${CATALOG_KEYCLOAK_CLIENT_SECRET}
+ schedule:
+ frequency: { seconds: 30 } # テスト用に短く設定
+ timeout: { minutes: 3 }Keycloakの設定
ここでは、プロダクトチームの開発者を管理するためのRealm platformを作成します。 そして、Realm platformに以下のクライアントを作成します。
| クライアントID | 用途 |
| backstage-auth | 認証認可 |
| backstage-sync-user-group | ユーザーとグループの同期 |
Realm platform作成
KeycloakでRealmを作成します。
Realm nameにplatformを入力し、Createをクリックします。
backstage-authクライアント作成
以下の設定で認証認可用のクライアントを作成する手順を示します。URL(http://backstage.172.32.4.127.nip.io)部分は、ご自身の環境に合わせて変更してください。
- Client ID:
backstage-auth - Client authenticationを有効化
- Standard flowにチェック
- Valid redirect URIs:
http://backstage.172.32.4.127.nip.io/api/auth/oidc/handler/frame - Web origins:
http://backstage.172.32.4.127.nip.io
クライアントを作成します。
Client IDにbackstage-authを入力し、Nextをクリックします。
Client authenticationを有効にします。続いて、Standard flowにチェックが入っていることを確認し、Nextをクリックします。なお、Standard flowはデフォルトでチェックされています。
Valid redirect URIsにhttp://backstage.172.32.4.127.nip.io/api/auth/oidc/handler/frame、Web originsにhttp://backstage.172.32.4.127.nip.ioを設定してSaveをクリックし、クライアントを作成します。
クライアントを作成したら、クライアントシークレットをメモしておきます。
backstage-sync-user-groupクライアント作成
以下の設定でユーザーとグループの同期用のクライアントを作成する手順を示します。
- Client ID:
backstage-sync-user-group - Client authenticationを有効にする
- Service accounts rolesにチェックを入れる
- サービスアカウントロールに
query-groups、query-users、view-usersを追加する
クライアント作成画面で、Client IDにbackstage-sync-user-groupを入力し、Nextをクリックします。
Client authenticationを有効にし、Service accounts rolesにチェックを入れます。また、Standard flowは使用しないため、チェックを外しておきます。
Login settingsは特に入力せず、Saveをクリックしてクライアントを作成します。
次に、サービスアカウントロールを設定します。
query-groups、query-users、view-usersにチェックを入れて、Assignをクリックします。realm-managementで検索すると見つけやすいです。
クライアントシークレットをメモしておきます。
Backstage用のSecret作成
Backstageの設定ファイル(app-config.production.yaml)で参照されている環境変数をKubernetesのSecretに設定します。
GitHub Personal access tokens作成
Keycloak連携とは直接関係ないのですが、フィードバックプラグインで評価の対象とするソフトウェアテンプレートで利用するGitHubのPersonal access tokens(classic)を作成します。詳細はGitHubのドキュメントを参照してください。
必要な権限は以下の通りです。
- repo(全て)
- workflow
- read:org
- read:user
- user:email
発行したトークンは、BackstageのSecretに設定するためにメモしておきます。なお、今回は簡略化のためPersonal access tokensを使いましたが、本番環境ではGitHub Appsの使用を推奨します。
セッションシークレットの生成
Backstageのセッション管理で使用されるセッションクッキーの署名と、検証のためのランダムな文字列を生成します。
export AUTH_SESSION_SECRET=$(openssl rand -hex 32)シークレット作成
Backstage用のSecretを作成します。 環境変数はそれぞれの環境に合わせて設定してください。
export BACKSTAGE_URL="http://backstage.172.32.4.127.nip.io"
export KC_BASE_URL="http://keycloak.172.32.4.127.nip.io"
export KC_REALM="platform"
export KC_OIDC_URL="${KC_BASE_URL}/realms/${KC_REALM}/.well-known/openid-configuration"
export KC_AUTH_CLIENT_SECRET="<backstage-authクライアントのシークレット>"
export KC_SYNC_CLIENT_SECRET="<backstage-sync-user-groupクライアントのシークレット>"
export GITHUB_TOKEN="<GitHub連携用のトークン>"
kubectl apply -n backstage -f- <<EOF
apiVersion: v1
kind: Secret
metadata:
name: backstage-secret
type: Opaque
stringData:
# BackstageのURL
BACKSTAGE_URL: "${BACKSTAGE_URL}"
# PostgreSQL接続情報
POSTGRES_HOST: "postgres"
POSTGRES_PORT: "5432"
POSTGRES_USER: "postgres"
POSTGRES_PASSWORD: "postgres"
POSTGRES_DB: "backstage"
# GitHub連携
GITHUB_TOKEN: "${GITHUB_TOKEN}"
# Keycloak接続情報
AUTH_SESSION_SECRET: "${AUTH_SESSION_SECRET}"
AUTH_OIDC_METADATA_URL: "${KC_OIDC_URL}"
AUTH_OIDC_CLIENT_ID: "backstage-auth"
AUTH_OIDC_CLIENT_SECRET: "${KC_AUTH_CLIENT_SECRET}"
# Catalog Keycloak情報
CATALOG_KEYCLOAK_BASE_URL: "${KC_BASE_URL}"
CATALOG_KEYCLOAK_LOGIN_REALM: "${KC_REALM}"
CATALOG_KEYCLOAK_REALM: "${KC_REALM}"
CATALOG_KEYCLOAK_CLIENT_ID: "backstage-sync-user-group"
CATALOG_KEYCLOAK_CLIENT_SECRET: "${KC_SYNC_CLIENT_SECRET}"
EOF以下のように、BackstageのPodから環境変数が渡されます。 具体的なマニフェストはbackstage.yamlを参照してください。
spec:
containers:
- name: backstage
envFrom:
- secretRef:
name: backstage-secretKeycloakでユーザーとグループの作成
Keycloakの管理コンソールで、適当なユーザーとグループを作成します。
グループ作成
KeycloakのRealm platformでグループを作成します。
今回は、BackstageAdminというグループを作成しました。
ユーザー作成
ユーザーを作成します。
今回、Keycloakでメール送信の設定をしていないため、メールアドレスを検証済みとします。Email verifiedにチェックを入れておいてください。ユーザー名、メールアドレス、姓名を埋めたら、先ほど作ったグループにユーザーを所属させます。
ユーザーを作成したら、パスワードを設定します。
今回は自分で設定して自分でログインするため、パスワードを後から再設定させるTemporaryのチェックを外します。
動作確認
コンテナイメージをビルドして、コンテナレジストリにプッシュします。GH_USERNAME、REPOSITORY、TAGの部分は、ご自身の環境に合わせて変更してください。
export IMG=ghcr.io/GH_USERNAME/REPOSITORY:TAG
yarn build-image --tag $IMG
docker push $IMG次に、Kustomizeの設定ファイルkustomization.yamlを作成します。GH_USERNAME、REPOSITORY、TAGの部分は、先ほどビルドしたイメージの名前とタグに置き換えてください。また、backstage.172.32.4.127.nip.ioもご自身の環境に合わせて変更してください。
resources:
- github.com/Hitachi/oss-assets/article/thinkit-smallplatform/04-backstage-feedback/deploy
images:
- name: ghcr.io/makihh/backstage:latest
newName: ghcr.io/GH_USERNAME/REPOSITORY
newTag: TAG
patches:
- target:
kind: HTTPRoute
name: backstage-route
patch: |-
- op: replace
path: /spec/hostnames/0
value: backstage.172.32.4.127.nip.iokustomization.yamlがあるディレクトリで以下のコマンドを実行して、Backstageをデプロイします。
kubectl apply -k .http://backstage.172.32.4.127.nip.ioにアクセスし、Keycloakでログインします。
また、ユーザーおよびグループがKeycloakからBackstageに同期されていることも確認できます。
このグループに対して、Permission Frameworkを使ってアクセス制御をかけられますが、今回は割愛します。
フィードバック機能の実装
ユーザーからのフィードバックを集めて継続的な改善に活かすため、entity-feedbackプラグインを導入していきます。entity-feedbackプラグインはフロントエンドとバックエンドの両方にプラグインがあるため、両方に追加していきます。
フロントエンド
Entity Feedback PluginのREADMEに従い、フロントエンド側の設定を行います。
まずはフロントエンド用のプラグイン@backstage-community/plugin-entity-feedbackをBackstageに追加します。
yarn --cwd packages/app add @backstage-community/plugin-entity-feedback次に、app-config.yamlのapp.extensionsにプラグインの設定を追加します。
# app-config.yaml
app:
extensions:
+ # フィードバック画面の設定
+ - 'entity-card:entity-feedback/ratings-buttons':
+ config:
+ variant: 'starred' # 今回は5段階の星評価を使用。
+ title: '評価をお願いします'
+ requireResponse: true # 低評価で追加のフィードバックを入力する項目を表示
+ dialogTitle: '理由をお聞かせください?' # 追加フィードバックのダイアログタイトル
+ dialogResponses: # 追加フィードバックの選択肢
+ - id: 'documentation'
+ label: 'ドキュメントが不十分/わかりにくい/不正確/不足している/古い'
+ - id: 'usability'
+ label: 'ユーザビリティが悪い/使いにくい'
+ # フィードバックの集計画面の設定
+ - 'entity-card:entity-feedback/ratings-table':
+ config:
+ variant: 'starred'
+ title: '評価一覧'
+ allEntities: trueバックエンド
Entity Feedback Backendのドキュメントに従い、バックエンド側の設定を行います。
バックエンド用のプラグイン@backstage-community/plugin-entity-feedback-backendをBackstageに追加します。
yarn --cwd packages/backend add @backstage-community/plugin-entity-feedback-backendバックエンドコードにフィードバックプラグインを初期化する設定を追加します。
# packages/backend/src/index.ts
backend.add(import('@backstage/plugin-auth-backend-module-oidc-provider'));
backend.add(
import('@backstage-community/plugin-catalog-backend-module-keycloak'),
);
+backend.add(import('@backstage-community/plugin-entity-feedback-backend'));
backend.start();動作確認
コンテナイメージをビルドし直して、Podを再起動します。
yarn build-image --tag $IMG
docker push $IMG
kubectl rollout restart deployment backstage -n backstage各カタログページに5段階評価のUIが表示されるようになります。
評価1または2を選択すると、低評価の理由を詳しく聞くためのフィードバックフォームが表示されます。試しに内容を入力してみます。
5段階評価の集計結果は、グループ画面で確認できます。
詳細なフィードバックは、各カタログページのFeedbackタブで確認できます。
利用したソフトウェアのバージョン
| コンポーネント | バージョン |
| Node.js | v24.15.0 |
| Backstage | 1.50.0 |
| @backstage/plugin-auth-backend-module-oidc-provider | 0.4.16 |
| @backstage-community/plugin-catalog-backend-module-keycloak | 3.19.3 |
| @backstage-community/plugin-entity-feedback | 0.19.0 |
| @backstage-community/plugin-entity-feedback-backend | 0.21.0 |
| Keycloak | 26.3.2 |
まとめ
Backstageにフィードバック機能を追加することで、ユーザーからのフィードバックを収集し、プラットフォームの継続的な改善に活かすことができるようになりました。
「プラットフォームエンジニアリングの成熟度モデル」に照らし合わせると、フィードバックチャネルが標準化された状態と言えるため、「計測」のレベル2の一要素を満たすことができました。
この記事をシェアしてください

