🎪

開発における CI/CD 関連の情報を Backstage ポータルに集約する

2024/08/03に公開

概要

Backstage は開発ポータルを構築するための OSS フレームワークです。github star は ~ 27 K で CNCF の incubating project となっています。

https://backstage.io/docs/overview/what-is-backstage/

クラウドネイティブなアプリケーションの開発をより効率的に進めることを考えると、OSS ベースの自宅クラウドの構成 で見たように CI/CD のツールやコード管理のために様々なプロダクトを使いこなす必要があり、複数のドキュメントや web UI などを横断する手間が増えてきます。Backstage を使うと開発に必要不可欠なこれらの Git repo のコード、 CI/CD のインフラ、ドキュメントなどの情報を Backstage のポータルに集約することができ、プロジェクト全体を包括的に見通せるようになります。

Backstage の構築や基本的な使い方に関する記事は検索すると色々出てくるので、ここでは上記の目的に絞って CI/CD 関連の情報が集約できるか検証してみます。

Backstage の構築

Backstage は frontend と backend で構成されていますが、新しいものとそれ以前から使用されているものの 2 種類があります (古い方はドキュメントで old system や legacy system と表記されている)。

詳細は以下の記事あたりを参照。

https://techblog.ap-com.co.jp/entry/2023/12/23/012101

新しい方が plugin を導入する際の手順が簡単のため基本的に新しい方を使えばいいのですが、今回使用する plugin のいくつかがまだ新しい方に対応していないようなので、今回は古い方の frontend, backend を使用します。

backstage の構築は https://backstage.io/docs/getting-started/ に沿って進めます。Prerequisites に記載のツール一式をインストールしたのち、古い方の frontend, backend を使用するためすこし古い create-app:0.5.1 を使って backstage を含むパッケージをインストールします。(記事を書いた当時の最新バージョンは 0.5.17)

$ npx @backstage/create-app@0.5.10

? Enter a name for the app [required] backstage

Creating the app...

 Checking if the directory is available:
  checking      backstage ✔

 Creating a temporary app directory:

 Preparing files:
  copying       .dockerignore ✔
  templating    .eslintrc.js.hbs ✔
  ...

セットアップが完了すると backstage ディレクトリ以下に backstage を動作するために必要なパッケージや設定ファイル等が作成されます。以降ではこの backstage ディレクトリをプロジェクトのルートディレクトリと呼びます。

Backstage の構成はセットアップ後に作成される app-config.yaml に記載します。この記事では backstage のサーバーに backstage.ops.com というドメイン名でアクセスできるようにするため、app-config.yaml 内で localhost となっている部分を書き換えておきます。

app-config.yaml
app:
  baseUrl: http://backstage.ops.com:3000
backend:
  baseUrl: http://backstage.ops.com:7007
  cors:
    origin: http://backstage.ops.com:3000

plugins を導入する

具体的なアプリケーションがないとどのような情報を集約すればよいかイメージが掴みづらいので、ここでは以前記事に書いた ローカル環境に簡易 CI/CD 環境を構築して試す tekton 編 のアプリケーションを例に考えてみます。この記事では k8s クラスタ上で稼働するアプリケーションを CI/CD で開発するために以下のようなツールを使用しました。

  • アプリケーションのコードは Gitlab で管理
  • コードからコンテナイメージをビルドするのに tekton を使用
  • ビルドしたイメージは harbor で管理
  • argocd を使って対象のクラスタにデプロイ

このフローにおいてアプリケーション開発に関連のあるツールは gitlab, tekton, harbor, argocd, k8s クラスタとなっているので、これらを backstage ポータル上に集約できればプロジェクト全体を包括的に管理するのが便利になります。幸いそれぞれのツールを backstage と連携するための plugin がすでに存在しているので、これらを導入してどのような情報が取得できるのか見ていきます。

Kubernetes plugin

kubernetes との連携は backstage のコア機能の 1 つとなっており、特定の k8s クラスタと通信して label に基づく k8s リソースを backstage のポータルから閲覧できるようにします。plugin の有効化や設定は基本的に ドキュメント に記載されていますが、設定すべき項目は下記のサイトも適切にまとめられているので両方を参考にしながら進めます。

https://roadie.io/backstage/plugins/kubernetes/

以降では plugin のインストールは yarn で行い、プロジェクトのルートディレクトリで実行します。
まず k8s の frontend plugin をインストール。

yarn --cwd packages/app add @backstage/plugin-kubernetes

packages/app/src/components/catalog/EntityPage.tsx に以下の部分を追加。

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

// You can add the tab to any number of pages, the service page is shown as an
// example here
const serviceEntityPage = (
  <EntityLayout>
    {/* other tabs... */}
    <EntityLayout.Route path="/kubernetes" title="Kubernetes">
      <EntityKubernetesContent refreshIntervalMs={30000} />
    </EntityLayout.Route>
  </EntityLayout>
);

次に backend plugin をインストール。

yarn --cwd packages/backend add @backstage/plugin-kubernetes-backend

packages/backend/src/plugins/kubernetes.ts を新規作成。

packages/backend/src/plugins/kubernetes.ts
import { KubernetesBuilder } from '@backstage/plugin-kubernetes-backend';
import { Router } from 'express';
import { PluginEnvironment } from '../types';
import { CatalogClient } from '@backstage/catalog-client';

export default async function createPlugin(
  env: PluginEnvironment,
): Promise<Router> {
  const catalogApi = new CatalogClient({ discoveryApi: env.discovery });
  const { router } = await KubernetesBuilder.createBuilder({
    logger: env.logger,
    config: env.config,
    catalogApi,
    discovery: env.discovery,
    permissions: env.permissions,
  }).build();
  return router;
}

packages/backend/src/index.ts に追加。

packages/backend/src/index.ts
// ..
import kubernetes from './plugins/kubernetes';

async function main() {
  // ...
  const kubernetesEnv = useHotMemoize(module, () => createEnv('kubernetes'));
  // ...
  apiRouter.use('/kubernetes', await kubernetes(kubernetesEnv));

これで plugin の導入が完了したので、次に k8s クラスタ側の設定を進めます。
backstage を動かすサーバーとは別に k8s クラスタを用意し、backstage - k8s クラスタ間接続に使用する serviceaccount, role, rolebinding を作成。

serviceaccount.yml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: backstage-sa
  namespace: backstage-sample

---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: backstage-sample-clusterrole
rules:
- apiGroups: [""]
  resources: ["pods", "services"]
  verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
  resources: ["deployments", "replicasets"]
  verbs: ["get", "list", "watch"]

---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: backstage-sample-clusterrolebinding
subjects:
- kind: ServiceAccount
  name: backstage-sa
  namespace: backstage-sample
roleRef:
  kind: ClusterRole
  name: backstage-sample-clusterrole
  apiGroup: rbac.authorization.k8s.io

この serviceaccount に token を設定。

apiVersion: v1
kind: Secret
metadata:
  name: sa-secret
  namespace: backstage-sample
  annotations:
    kubernetes.io/service-account.name: backstage-sa
type: kubernetes.io/service-account-token

以下のコマンドで作成した secret から token を取得できるのでメモ。

kubectl -n backstage-sample get secrets sa-secret -o yaml | yq -r ".data.token" | base64 -d

次に backstage の app-config.yamlkubernetes を追加し、クラスタへの接続設定を記載します。

  • url: k8s control plane の接続先 url を指定する。k8s クラスタの control plane 上で kubectl cluster-info を実行することで確認可能。大抵は k8s api-server のエンドポイントに一致。
  • serviceAccountToken: 上記で確認した SA の token を指定。値を直接書いても良いが下記のように環境変数から読み込むことも可能。

その他の項目は configuration を参照。

app-config.yaml
kubernetes:
  serviceLocatorMethod:
    type: 'multiTenant'
  clusterLocatorMethods:
    - type: 'config'
      clusters:
        - url: https://192.168.3.131:6443
          name: k8s
          authProvider: 'serviceAccount'
          skipTLSVerify: true
          skipMetricsLookup: true
          serviceAccountToken: ${k8S_TOKEN}

これで準備ができたので、試しに適当なリソースを作成して backstage から確認できるか見てみます。

backstage の entity を k8s リソースと関連付けるにはいくつかの方法がありますが、ここでは annotations の label selector を指定する方法にします。Backstage 側のリソースである Component entity を作成し、annotations に backstage.io/kubernetes-label-selector: 'backstage-project=k8s-example' を設定します。

examples/k8s.yml
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
  name: k8s-example
  annotations:
    backstage.io/kubernetes-label-selector: 'backstage-project=k8s-example'
spec:
  type: service
  lifecycle: experimental
  owner: guests
  system: examples

上記を読み込むために app-config.yaml の catalog に以下を追加。

app-config.yaml
catalog:
  locations:
    - type: file
      target: ../../examples/k8s.yml

この場合、k8s 側のリソースで backstage-project: k8s-example の label を設定したリソースは上記の entity と関連付けられるようになります。動作確認のため、適当な deployments と svc を作成。

nginx.yml
kind: Deployment
metadata:
  name: nginx
  namespace: backstage-sample
  labels:
    app: nginx
    backstage-project: k8s-example
spec:
  selector:
    matchLabels:
      app: nginx
      backstage-project: k8s-example
  replicas: 2
  template:
    metadata:
      labels:
        app: nginx
        backstage-project: k8s-example
    spec:
      containers:
      - name: nginx
        image: nginx:1.14.2
        ports:
        - containerPort: 80

---
apiVersion: v1
kind: Service
metadata:
  name: nginx-svc
  namespace: backstage-sample
  labels:
    app: nginx
    backstage-project: k8s-example
spec:
  selector:
    app: nginx
    backstage-project: backstage-example
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80

プロジェクトのルートディレクトリで yarn dev を実行し、backstage frontend と backend を起動。ブラウザから backstage.ops.com:3000 にアクセスし、作成した Component entity k8s-example を確認すると kubernetes タブが追加され、クラスタに展開した pod と service が確認できます。

ページ上部には権限不足により取得できないリソースに関するエラーメッセージが表示されます。今回は使用しないので問題ないですが、これらのリソースも取得したい場合は k8s 側の Role に権限を追加する必要があります。

There was a problem retrieving some Kubernetes resources for the entity: k8s-example. This could mean that the Error Reporting card is not completely accurate.

Errors:
Cluster: k8s

Error fetching Kubernetes resource: '/api/v1/configmaps', error: UNKNOWN_ERROR, status code: 403
Error fetching Kubernetes resource: '/api/v1/limitranges', error: UNKNOWN_ERROR, status code: 403
Error fetching Kubernetes resource: '/api/v1/resourcequotas', error: UNKNOWN_ERROR, status code: 403
Error fetching Kubernetes resource: '/apis/autoscaling/v2/horizontalpodautoscalers', error: UNKNOWN_ERROR, status code: 403
Error fetching Kubernetes resource: '/apis/batch/v1/jobs', error: UNKNOWN_ERROR, status code: 403
Error fetching Kubernetes resource: '/apis/batch/v1/cronjobs', error: UNKNOWN_ERROR, status code: 403
Error fetching Kubernetes resource: '/apis/networking.k8s.io/v1/ingresses', error: UNKNOWN_ERROR, status code: 403
Error fetching Kubernetes resource: '/apis/apps/v1/statefulsets', error: UNKNOWN_ERROR, status code: 403
Error fetching Kubernetes resource: '/apis/apps/v1/daemonsets', error: UNKNOWN_ERROR, status code: 403

backstage entity と特定の deployment や svc などを関連付けられるので、例えば frontend のアプリケーションを k8s 上で動かしている場合に pod が正常に稼働しているか等を確認できるようになります。特に同一クラスタ内に多数の pod が存在しているようなケースではプロジェクトに関連する pod をいちいち探すような手間が省けるといったメリットが考えられます。

Argocd plugin

https://roadie.io/backstage/plugins/argo-cd/?utm_source=backstage.io&utm_medium=marketplace&utm_campaign=argo-cd

ArgoCD との連携は上記の plugin が使用できます。このプラグインでは連携先の Argocd から指定した application の状態を取得し、health, sync, 更新履歴などを backstage ポータルから確認できるようになります。

frontend のインストール

yarn --cwd packages/app add @roadiehq/backstage-plugin-argo-cd

packages/app/src/components/catalog/EntityPage.tsx に追加。

packages/app/src/components/catalog/EntityPage.tsx
import {
  EntityArgoCDOverviewCard,
  isArgocdAvailable
} from '@roadiehq/backstage-plugin-argo-cd';

const overviewContent = (
  <Grid container spacing={3} alignItems="stretch">
  ...
    <EntitySwitch>
      <EntitySwitch.Case if={e => Boolean(isArgocdAvailable(e))}>
        <Grid item sm={4}>
          <EntityArgoCDOverviewCard />
        </Grid>
      </EntitySwitch.Case>
    </EntitySwitch>
  ...
  </Grid>
);

次に backend のインストール

yarn --cwd packages/backend add @roadiehq/backstage-plugin-argo-cd-backend

packages/backend/src/plugins/argocd.ts を作成。

packages/backend/src/plugins/argocd.ts
import { createRouter } from '@roadiehq/backstage-plugin-argo-cd-backend';
import { PluginEnvironment } from '../types';

export default async function createPlugin({
  logger,
  config,
}: PluginEnvironment) {
  return await createRouter({ logger, config });
}

packages/backend/src/index.ts を更新。

packages/backend/src/index.ts
import argocd from './plugins/argocd';
...

const argocdEnv = useHotMemoize(module, () => createEnv('argocd'));
...
apiRouter.use('/argocd', await argocd(argocdEnv));

対象の argocd の接続情報は app-config,yaml に指定します。ここでは接続先の domain は argocd.ops.com とします。

app-config.yml
proxy:
  endpoints:
    '/argocd/api':
      target: https://argocd.ops.com:32566/api/v1/
      changeOrigin: true
      secure: false
      headers:
        Cookie:
          $env: ARGOCD_TOKEN

argocd:
  username: <username>
  password: <password>
  appLocatorMethods:
    - type: 'config'
      instances:
        - name: argoInstance1
          url: https://argocd.ops.com:32566
          token: ${ARGOCD_TOKEN}

次に argocd 側で検証に使用する application 等を作成しておきます。

application.yml
---
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: backstage-sample-app
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://gitlab.ops.com/kube/backstage-example.git
    targetRevision: HEAD
    path: argocd
  destination:
    server: https://kubernetes.default.svc
    namespace: backstage-sample

接続に使用する backstage ユーザーと role の設定。

argocd-cm
apiVersion: v1
data:
  accounts.backstage: apiKey,login
  accounts.backstage.enabled: "true"
kind: ConfigMap
metadata:
  labels:
    app.kubernetes.io/name: argocd-cm
    app.kubernetes.io/part-of: argocd
  name: argocd-cm
  namespace: argocd
rbac.yml
apiVersion: v1
kind: ConfigMap
metadata:
  name: argocd-rbac-cm
  namespace: argocd
  labels:
    app.kubernetes.io/name: argocd-rbac-cm
    app.kubernetes.io/part-of: argocd
data:
  policy.csv: |
    p, role:test-role, applications, create, default/*, allow
    p, role:test-role, applications, get, default/*, allow
    p, role:test-role, applications, update, default/*, allow
    p, role:test-role, applications, delete, default/*, allow
    p, role:test-role, applications, sync, default/*, allow
    p, role:test-role, applications, override, default/*, allow
    g, backstage, role:test-role
  policy.default: role:readonly

デプロイするマニフェストは self-managed gitlab に置いておくので証明書、認証方法なども設定。

repo.yml
apiVersion: v1
kind: ConfigMap
metadata:
  name: argocd-tls-certs-cm
  namespace: argocd
  labels:
    app.kubernetes.io/name: argocd-cm
    app.kubernetes.io/part-of: argocd
data:
  gitlab.ops.com: |
    -----BEGIN CERTIFICATE-----
    ...
    -----END CERTIFICATE-----

---
apiVersion: v1
kind: Secret
metadata:
  name: gitlab-repo
  namespace: argocd
  labels:
    argocd.argoproj.io/secret-type: repository
stringData:
  type: git
  url: https://gitlab.ops.com/kube/backstage-example.git
  password: <password>
  username: <username>

検証用に gitlab の backstage-example.git リポジトリにデプロイ用のマニフェストを配置します。

.
├── argocd
    └── deployment.yml
deployment.yml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-from-gitlab
  namespace: backstage-sample
  labels:
    app: nginx2
    backstage-project: sample-project
    backstage.io/kubernetes-id: k8s-example
spec:
  selector:
    matchLabels:
      app: nginx2
      backstage-project: sample-project
      backstage.io/kubernetes-id: k8s-example
  replicas: 2
  template:
    metadata:
      labels:
        app: nginx2
        backstage-project: sample-project
        backstage.io/kubernetes-id: k8s-example
    spec:
      containers:
      - name: nginx
        image: nginx:1.14.2
        ports:
        - containerPort: 80

上記のリソースを一通り作成したら、argocd 側で backstage ユーザーにパスワードを設定して token を作成。application の sync も実行しておきます。

argocd login argocd.ops.com:32566 --username admin --password <password> --insecure
argocd account update-password --account backstage --new-password backstage
argocd logout
argocd login argocd.ops.com:32566 --username backstage --password <password> --insecure
argocd account generate-token
argocd app sync backstage-sample-app

先程作成した component entity に 以下の label を追加します。

ks8.yml
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
  name: k8s-example
  annotations:
    backstage.io/kubernetes-label-selector: 'backstage-project=k8s-example'
+    argocd/app-name: backstage-sample-app
spec:
  type: service
  lifecycle: experimental
  owner: guests
  system: examples

argocd plugin も k8s plugin と同様にいくつかの方法で関連付けられる argocd リソースを指定できるようになっており、例えば上記のように argocd/app-name を指定すると指定したリソース名に対応する argocd の Application リソースが関連付けられるようになります。その他の指定方法については https://www.npmjs.com/package/@roadiehq/backstage-plugin-argo-cd を参照。

最後に、backstage → argocd の接続は先程作成した backstage ユーザーの token を使用するため、backstage サーバー側で export ARGOCD_TOKEN=... で環境変数に設定しておきます。
yarn dev で backstage を起動して entity を開くと、overview page に指定した argocd Application の情報が表示されるようになります。

クリックすると詳細が application の spec に設定されている repo 等の情報が確認できます。

Link を押すと argocd ダッシュボードの対応する Application のページに飛びます。

こちらも k8s plugin と同様に entity に関連づいた argocd application の詳細や同期ステータスが一目でわかるようになります。また、実際のデプロイの状況やリソースに関しては argocd 側で管理できるので、デプロイが正常に動いていることの確認は backstage ポータルで行い、異常やエラーが発生した場合は argocd 側のプロジェクトに飛んで詳細に確認するといった使い方ができます。

Tekton plugin

https://janus-idp.io/plugins/tekton/

tekton plugin を検索するといくつか見つかりますが上記のものを使用します。この plugin を使うと k8s クラスタ上の tekton PipelineRun リソースの成功・失敗や実行時のログを backstage から確認できるようになります。

frontend のインストール

yarn --cwd packages/app add @janus-idp/backstage-plugin-tekton

EntityPage.tsx に以下を追加。

packages/app/src/components/catalog/EntityPage.tsx
import {
  isTektonCIAvailable,
  TektonCI,
} from '@janus-idp/backstage-plugin-tekton';

const cicdContent = (
  <EntitySwitch>
    {/* ... */}
    <EntitySwitch.Case if={isTektonCIAvailable}>
      <TektonCI />
    </EntitySwitch.Case>
  </EntitySwitch>
);

app-config.yml の kubernetes に以下の CR を追加します。

app-config.yml
kubernetes:
  customResources:
    - group: 'tekton.dev'
      apiVersion: 'v1'
      plural: 'pipelineruns'
    - group: 'tekton.dev'
      apiVersion: 'v1'
      plural: 'taskruns'

ドキュメントを参照に tekton リソースにアクセスできるように k8s の role に権限を追加します。ここではk8s plugin 導入時に作成した backstage-sample-clusterrole ロールに権限を追加します。

serviceaccount.yml
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: backstage-sample-clusterrole
rules:
- apiGroups: [""]
  resources: ["pods", "pods/log", "services"]
  verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
  resources: ["deployments", "replicasets"]
  verbs: ["get", "list", "watch"]
- apiGroups: ["tekton.dev"]
  resources:
    - pipelineruns
    - taskruns
  verbs:
    - get
    - list

次に tekton 側で pipeline リソースを追加します。ここでは ローカル環境に簡易 CI/CD 環境を構築して試す tekton 編 で使用したものを流用します。詳細は記事を参照。

pipeline.yml
apiVersion: tekton.dev/v1
kind: Pipeline
metadata:
  name: backstage-ci-cd
  namespace: backstage-sample
  labels:
    backstage.io/kubernetes-id: k8s-example
spec:
  description: >-
    This pipeline run the sequence of tasks as a pipeline.
    1. Clone the application source from a git repository.
    2. Build an image from Dockerfile and push it to a registry.
    3. Deploy application by argocd sync.
  params:
    - name: repositoryUrl
      type: string
      description: The git repository URL to clone the source.
    - name: branch
      type: string
      description: The branch of the repository.
    - name: context
      description: The path to the build context, used by Kaniko - within the workspace
      default: "./"
    - name: imageName
      description: Image name
    - name: imageTag
      description: Image tag
      default: "latest"
    - name: argocdApplicationName
      description: The name of Argocd application to be synced.
  workspaces:
    - name: shared-data
      description: >-
        This workspace contains the cloned repo files, so they can be read by the
        next task.
    - name: git-credentials
      description: Name of the secret to log in to gitlab.
    - name: harbor-credentials
      description: Name of the secret to log in to harbor.
    - name: argocd-credentials
      description: Name of the secret to log in to argocd.
  tasks:
    - name: git-clone
      taskRef:
        name: git-clone
      workspaces:
        - name: output
          workspace: shared-data
        - name: basic-auth
          workspace: git-credentials
      params:
        - name: url
          value: $(params.repositoryUrl)
        - name: revision
          value: $(params.branch)
        - name: sslVerify
          value: "false"
    - name: build
      taskRef:
        name: kaniko
      runAfter:
        - git-clone
      workspaces:
        - name: source
          workspace: shared-data
        - name: dockerconfig
          workspace: harbor-credentials
      params:
        - name: IMAGE
          value: $(params.imageName):$(params.imageTag)
        - name: CONTEXT
          value: $(params.context)
    - name: deploy
      taskRef:
        name: argocd-sync
      runAfter:
        - git-clone
        - build
      workspaces:
        - name: argocd-basic-auth
          workspace: argocd-credentials
      params:
        - name: applicationName
          value: $(params.argocdApplicationName)

tekton リソースと backstage entity を関連付けるには、entity 側の annotations に janus-idp.io/tekton: <BACKSTAGE_ENTITY_NAME> を追加します。

k8s.yml
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
  name: k8s-example
  annotations:
    backstage.io/kubernetes-label-selector: 'backstage-project=k8s-example'
    argocd/app-name: backstage-sample-app
+    janus-idp.io/tekton: k8s-example
spec:
  type: service
  lifecycle: experimental
  owner: guests
  system: examples

tekton 側では pipeline リソースの labels に backstage.io/kubernetes-id: <BACKSTAGE_ENTITY_NAME> を設定します。こちらは annotations ではなく labels に設定するので注意。他の指定方法は https://janus-idp.io/plugins/tekton/#for-administrators を参照。

pipelinerun.yml
apiVersion: tekton.dev/v1
kind: Pipeline
metadata:
  name: backstage-ci-cd
  namespace: backstage-sample
  labels:
    backstage.io/kubernetes-id: k8s-example
spec:
  ,,,

backstage を起動し、entity の CI/CD タブを確認すると tekton が追加されています。ここでは tekton パイプラインとパイプライン内の各タスクの実行結果が確認できます。

ステージをクリックすると各タスクのログが確認できます(pod log に対応)。字がやや小さいですがログをテキストファイルとしてダウンロードすることもできます。

これによって CI で行われる lint やテスト、コンテナイメージのビルド、コンテナレジストリへのアップロード、argocd のトリガーなど諸々の処理の結果を backstage ポータルから確認できます。

Harbor plugin

Harbor plugin では harbor に保存されているコンテナイメージの情報を backstage から確認できるようになります。harbor 用の plugin は backstage plugin list に container-registry が管理するもの が記載されていますが、現時点ではアーカイブとなっているようです (参考)。

アーカイブとなっているものの使用はできるのでこれを使ってもいいのですが、一部機能に不具合があるため代わりに上記のリポジトリから fork された @digitalist-open-cloud 所有の plugin を使用します。

https://www.npmjs.com/package/@digitalist-open-cloud/backstage-plugin-harbor
https://www.npmjs.com/package/@digitalist-open-cloud/backstage-plugin-harbor-backend

frontend のインストール

yarn --cwd packages/app add @digitalist-open-cloud/backstage-plugin-harbor

EntityPage.tsx に追加。

packages/app/src/components/catalog/EntityPage.tsx
import {
  HarborPage,
  HarborWidget,
  isHarborAvailable,
} from '@digitalist-open-cloud/backstage-plugin-harbor'

const serviceEntityPage = (
  <EntityPageLayout>
    // ...
    <EntityLayout.Route path="/harbor" title="Harbor" if={isHarborAvailable}>
      <HarborPage />
    </EntityLayout.Route>
  </EntityPageLayout>
)

以下の変更では backstage entity の overview に widget を追加します。お好みで記載。

packages/app/src/components/catalog/EntityPage.tsx
const overviewContent = (
  <Grid container spacing={6} alignItems="stretch">
    // ...
    <EntitySwitch>
      <EntitySwitch.Case if={isHarborAvailable}>
        <Grid item>
          <HarborWidget />
        </Grid>
      </EntitySwitch.Case>
    </EntitySwitch>
    ...
  </Grid>
)

backend をインストール

yarn --cwd packages/backend add @digitalist-open-cloud/backstage-plugin-harbor-backend

packages/backend/src/plugins/harbor.ts を新規作成。

packages/backend/src/plugins/harbor.ts
import { createRouter } from '@digitalist-open-cloud/backstage-plugin-harbor-backend';
import { Router } from 'express';
import { PluginEnvironment } from '../types';

export default async function createPlugin({
  logger,
  config,
}: PluginEnvironment): Promise<Router> {
  return await createRouter({ logger, config });
}

index.ts を更新。サイトでは harborusage となっていますが、実際に試すと harborusage ではエラーとなり harbor だとうまくいきました。

packages/backend/src/index.ts
import harbor from './plugins/harbor';
// ...
async function main() {
  // ...
  const harborEnv = useHotMemoize(module, () => createEnv('harbor'));
-  apiRouter.use('/harbor', await harborusage(harborEnv));
+  apiRouter.use('/harbor', await harbor(harborEnv));

harbor への接続情報は app-config.yaml に記載します。

app-config.yaml
harbor:
  # This is the traditional way of configuring the Harbor plugin.
  baseUrl: https://harbor.yourdomain.com
  username: ${HARBOR_USERNAME}
  password: ${HARBOR_PASSWORD}

backstage entity リソースの annotations に goharbor.io/repository-slug を追加し、関連付けたい harbor イメージを <project>/<image_name> の形式で追加します。ここで指定したイメージは上記の harbor ユーザーで参照できるように公開範囲や権限も設定する必要あり。

k8s.yml
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
  name: k8s-example
  annotations:
    backstage.io/kubernetes-label-selector: 'backstage-project=k8s-example'
    argocd/app-name: backstage-sample-app
    janus-idp.io/tekton: k8s-example
+   goharbor.io/repository-slug: k8s/backstage-front
spec:
  type: service
  lifecycle: experimental
  owner: guests
  system: examples

これで Backstage を輝度すると harbor タブが追加され、指定したイメージのタグ一覧やサイズ、脆弱性スキャンの結果が取得できます。ただ見た感じ harbor 側のイメージタグ数と一致していないので、ある程度新しいイメージタグのみ取得される等の制約があるのかもしれません。

Learn more を押すと harbor webUI の対象イメージのページに飛びます。

こちらも argocd と同様にざっと確認したい際は backstage ポータルから確認し、詳細な情報を知りたい場合はそのまま Learn more から harbor の UI に飛んで確認するといった使い分けができます。

参考: 古いプライグインについて

アーカイブとなっている container-registry の harbor plugin ( @bestsellerit/backstage-plugin-harbor など) の方も試した結果タブが追加されることは確認できましたが、harbor 側で脆弱性スキャンを有効化していないと以下のエラーがログに表示されてイメージが取得できませんでした。

[1] 2024-07-28T08:30:30.025Z backstage error Cannot read properties of undefined (reading 'href') type=errorHandler stack=TypeError: Cannot read properties of undefined (reading 'href')
[1]     at <anonymous> (/home/ubuntu/backstage/old/node_modules/@bestsellerit/backstage-plugin-harbor-backend/src/service/artifact.ts:18:73)
[1]     at Array.map (<anonymous>)
[1]     at getArtifacts (/home/ubuntu/backstage/old/node_modules/@bestsellerit/backstage-plugin-harbor-backend/src/service/artifact.ts:16:37)
[1]     at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
[1]     at <anonymous> (/home/ubuntu/backstage/old/node_modules/@bestsellerit/backstage-plugin-harbor-backend/src/service/router.ts:19:23)

イメージの取得処理は harbor api を実行してその取得結果を以下の HarborApiArtifact に格納していますが、脆弱性スキャンの結果がない場合 addition_links.vulnerabilities.href が返り値に含まれないため Cannot read properties of undefined (reading 'href が発生するようです。

https://github.com/container-registry/backstage-plugin-harbor-backend/blob/ad65cb61fd1deb542e125d371832592afa1d247a/src/service/artifact.ts#L138-L151

このエラーは harbor 側で trivy などの脆弱性スキャンを有効化することで対応できます。詳細は harbor Vulnerability Scanning を参照。
ただしこちらを対応しても harbor 側の脆弱性スキャンが正しく取得できない、harbor への link が正しく機能しないという問題があります。これは https://github.com/container-registry/backstage-plugin-harbor/issues/272 で報告されており、今回使った @digitalist-open-cloud/backstage-plugin-harbor の方では修正されています。

TechDocs plugin

techdocs は backstage のコア機能の 1 つで、markdown で書かれた文章を mkdocs を使って backstage 上でビルド、レンダリングして閲覧できる機能となっています。アプリケーション開発においても仕様やアーキテクチャ、運用方針、引き継ぎ手順など様々な場面で文書化が必要となりますが、techdocs plugin を利用することでそういった文書を backstage ポータルに集約することができます。

techdocs を有効化するための plugin のインストールや設定は https://backstage.io/docs/features/techdocs/getting-started に記載されていますが、backstage のバージョンによっては構築時にセットアップされるため設定が不要な場合もあります。今回使用したバージョンでは構築時に既にセットアップ済みとなっていました。

backstage entity と techdocs の文書を関連付けるには entity の annotations に backstage.io/techdocs-ref: を指定する必要があります。https://backstage.io/docs/features/techdocs/creating-and-publishing
techdocs が正しく動作することを検証するため、backstage のプロジェクトディレクトリに以下のようなファイルを作成します。

.
├── app-config.yml
├── docs
│   └── index.md
├── mkdocs.yml
└── k8s.yml

mkdocs.yml と docs/index.md は mkdocs でドキュメントを作成するために必要なファイルです。techdocs-core plugin を指定する以外は通常の mkdocs でドキュメントを作成する際と同様に記述できます。

mkdocs.yml
site_name: Backstage-frontend
nav:
  - HomeDir: index.md
plugins:
  - techdocs-core
docs/index.md
## markdown sample

サンプル

k8s.yml は今まで使ってきた component entity と同じ内容にします。annotations に backstage.io/techdocs-ref: dir:. で index.md が存在する docs のディレクトリパスを指定することで entity と techdocs を関連付けることができます。
techdocs の仕様上ファイルが存在するディレクトリより上のディレクトリを指定すると Relative path is not allowed to refer to a directory outside its parent Relative のエラーが発生するので、k8s.yml 一時的に docs と同じディレクトリに配置します。

k8s.yml
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
  name: k8s-example
  annotations:
    backstage.io/kubernetes-label-selector: 'backstage-project=k8s-example'
    argocd/app-name: backstage-sample-app
    janus-idp.io/tekton: k8s-example
    goharbor.io/repository-slug: k8s/backstage-front
+   backstage.io/techdocs-ref: dir:.
spec:
  type: service
  lifecycle: experimental
  owner: guests
  system: examples

k8s.yml の位置を変えたので app-config.yml も修正します。

app-config.yml
catalog:
  locations:
-    - type: file
-      target: ../../examples/k8s.yml
+    - type: file
+      target: ../../k8s.yml

これで yarn dev で backstage を起動し、component entity の overview を見ると View techodocs のリンクが選択できるようになります。

これをクリックすると index.md の内容を mkdocs でレンダリングしたページに飛びます(初回にページを開く際は markdown のビルドが行われるため少し時間がかかる)。また、docs タブからも同様の内容が確認できます。

生成されたドキュメントは内部的に docs に分類されているため、左側のナビゲーションの Docs からも確認できます。こちらは backstage 内の docs リソース一覧が表示されるようです。

ドキュメント自体は mkdocs を使って作成できるので、アプリケーションの説明、アーキテクチャ、運用方針やなど色々な情報を自由に記載できます。

その他

上記ではまとめきれなかったものやメモなど。

他の plugin

本編では CI に tekton を使用している際の連携例について記載しましたが、plugin list では他にも様々なサービスに対応する plugin があります。役立ちそうなものを以下にまとめておきます。

monitoring 等の plugin もあるのでアプリケーションの監視状況などの情報も Backstage に集約することができそうです。

Authenticate provider

backstage はデフォルトでは認証が設定されていないため URL を知っていればユーザー認証無しで誰でもアクセスできますが、authentication provider を指定することで外部サービスと連携して認証を実装することができます。Github や Google など主要な provider はデフォルトで組み込まれているので plugin 等をインストールしなくしても使用できます。

https://backstage.io/docs/auth/

OIDC の処理を実装することで上記のリストにない OIDC provider を認証に使うことも可能。

https://backstage.io/docs/auth/oidc

k8s での稼働

今までは backstage を起動する際は yarn dev を実行してローカルで稼働させていましたが、本格的な運用を考える場合はやはり kubernetes でのデプロイが推奨されているようです。

https://backstage.io/docs/deployment/

At Spotify, we deploy software generally by:

Building a Docker image
Storing the Docker image on a container registry
Referencing the image in a Kubernetes Deployment YAML
Applying that Deployment to a Kubernetes cluster
This method is covered in Building a Docker image and Deploying with Kubernetes.

kubernetes でのデプロイは以下のドキュメントに記載があります。

https://backstage.io/docs/deployment/k8s

これを読んでいくと、backstage イメージは以下の手順に沿ってビルドするように書かれています。

https://backstage.io/docs/deployment/docker

こちらでは以下の構成のビルド方法が記載されています。一見すると Host Build が推奨されてそう。

Type Description メリット
Host Build ホスト側で backstage 関連をビルドしておき、コンテナ内に成果物を配置する方法 ホスト側でキャッシュが聞くのでビルドが早い
Multi-stage Build docker のマルチステージビルドを利用してコンテナ内で backstage のビルドを行う方法 一部のケースで使う
Separate Frontend backend と frontend を分離する方法 一部のケースで使う

その他 backstage をデプロイするための helm chart もあります。

https://github.com/backstage/charts

ただ database や role 等は helm chart でセットアップできますが、肝心の backstage イメージ自体は上記の方法により自前でビルドする必要があります。

ポータルの theme を変える

backstage ポータルのデフォルトテーマは dark/light の 2 種類ですがカスタムテーマを作成することもできます。

https://backstage.io/docs/getting-started/app-custom-theme/

また、他の人が作成した plugin を利用することもできます。
私は dracula テーマが好きなので backstage 用の dracula theme があるか探しましたが、以下のものが dracula org 配下にありました。

https://github.com/dracula/backstage

現時点では公式のインストール方法については記載されていないようですが、backstage/plugins/dracula-theme に導入方法が記載されていたのでこちらを参考に導入します。

README では yarn --cwd packages/app add @fjudith/plugin-dracula-theme で plugin をインストールできるとありますが、package が npm のレジストリにアップロードされていないようなのでローカルからインストールします。
まずリポジトリを clone

git clone https://github.com/dracula/backstage.git

dracula theme は plugins/dracula-theme 配下にあるので、元の backstage プロジェクトディレクトリの plugins 以下にコピー。

cd backstage
cp -r plugins/dracula-theme [project_dir]/plugins

元の project のルートディレクトリから local パッケージを yarn add する。

cd [project_dir]
yarn --cwd packages/app add ./plugins/dracula-theme/

あとは Theme installation と Update logo color に沿ってファイルを追加、編集する。
readme ではディレクトリが packages/app/src/components/ となっていますが、手元の環境では packages/app/src/components/Root にありました。
また、以下の部分はそのままでは動かず以下のように修正する必要がありました。

packages/app/src/components/Root.tsx
-  path: props => ({
-    fill: props.ThemeId === 'dracula' ? '#F8F8F2' ; '#7df3e1'
-  }),
+  path: props => ({
+    fill: props.themeId === 'dracula' ? '#F8F8F2' : '#7df3e1'
+  }),

-  const themeId = appThemeApi.getActiveThemeId();
+  const appThemeApi = useApi(appThemeApiRef);
+  const themeId = appThemeApi.getActiveThemeId();

yarn dev で backstage を起動すると以下のエラーが発生。

[0] Module parse failed: Unexpected token (12:19)
[0] You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
[0] |
[0] | // Change stop color: https://johndecember.com/html/spec/colorsvg.html
[0] > export const shapes: Record<string, string> = {
[0] |   wave: `url("data:image/svg+xml,%3csvg xmlns='http://

上記エラーは import 内のコメントアウトされていた shapes を外し、もとの export const shapes となっていた箇所をコメントアウトすることで解消しました。

plugins/dracula-theme/src/themes/dracula.ts
import {
  createBaseThemeOptions,
  createUnifiedTheme,
  genPageTheme,
  palettes,
+  shapes,
- //  shapes,
} from '@backstage/theme';

import { alpha } from '@material-ui/core/styles';

- // Change stop color: https://johndecember.com/html/spec/colorsvg.html
- // export const shapes: Record<string, string> = {
- //  wave: `url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='1368'
...

これで backstage 全体に dracula theme が適用されるようになりました。

theme は左下の settings > General > Appearance から切り替えられます。

まとめ

各 plugin を有効化した際に backstage ポータルでどのように見えるかをまとめておきます。


home page


Component entity の Overview


CI/CD タブの tekton pipeline 実行結果


API タブでは entity に関連づく API entity が表示される


OpenAPI 形式で記載した API 定義はレンダリングされて表示される


Dependency タブでは Component の依存関係が表示される


Docs タブ


K8s タブ


Harbor タブのイメージ一覧


System entity では Relation で他 entity との依存関係が一目でわかるようになっている

今回使った resource は以下に置いてあります。
https://github.com/git-ogawa/zenn_resource/tree/main/articles/backstage

おわりに

Backstage の plugin を利用して CI/CD 周りの情報を Backstage で集約できるか検証しましたが、試してみると割といい感じにまとめることができました。特に Argocd や Harbor は対応する webUI 側へのリンクも表示されるので、プロジェクト全体を俯瞰的に見渡す場合は Backstage ポータルから確認し、より詳細な情報は各ツールの webUI に飛んで確認するといったように使い分けることができます。
また、Backstage では主に React で plugin を書いて機能や使い勝手を拡張できるのが特徴の 1 つとなっていますが、既存の plugin ではそこまで踏み込まずとも適用できるようになっており、 React や frontend にそれほど馴染みがなくても活用できるのがメリットとなっています。

Discussion