BackstageのKubernetes Pluginを使ってカタログのKubernetesリソースの状態を確認する

## Backstageとは
[Backstage](https://backstage.io/)とは元はSpotifyが開発したOSSで、オフィシャルドキュメントを引用すると、
>Backstage は、開発者ポータルを構築するためのオープンプラットフォームです。一元化されたソフトウェア・カタログを搭載したBackstageは、マイクロサービスやインフラの秩序を回復し、製品チームが自律性を損なうことなく、高品質のコードを迅速に出荷することを可能にします。

とあり、開発者向けのポータルを提供してくれることが分かる。
主要な機能は以下となる。

- [ソフトウェアカタログ](https://backstage.io/docs/features/software-catalog/)
- [ソフトウェアテンプレート](https://backstage.io/docs/features/software-templates/)
- [技術文書](https://backstage.io/docs/features/techdocs/)

今回はBackstageを起動し、ソフトウェアカタログの機能からKubernetesのリソースの状態を取得してみる。


## 検証環境
今回は以下の環境で試している。
- Backstageの動作環境：Ubuntu 20.04.6 LTS
- ソースコード置き場：GitHub
- Kubernetes：EKS

## 事前準備
### GitHubとの連携
公式ドキュメントは[こちら](https://backstage.io/docs/auth/github/provider/)。
OAuthを使ってGitHubと認証してソースコードを取得する。
最初にGitHubのClient IDとClient Secretを取得する。
アカウントの`Settings`->`Developer settings`->`OAuth Apps`から`New OAuth App`を選択し、
以下のような感じでOAuth applicationを作成する。

![1683849123194.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/9acf7da7-d36e-3193-0ae4-53f051d776e1.png)

IPはBackstageを起動するホストのIPとなる。IPやPortの部分は各自の環境に置き換えること。`localhost`で実行する場合は`localhost`にして問題ない。
以下コピペ用。
```
Homepage URL: http://localhost:3000
Authorization callback URL: http://localhost:7007/api/auth/github/handler/frame
```
作成後、Client Secretを作成し、Client IDと一緒に環境変数に設定しておく。
```sh
export AUTH_GITHUB_CLIENT_ID=xxxxx
export AUTH_GITHUB_CLIENT_SECRET=xxxxx
```

### Databaseの用意
DBなしでも利用できるが、起動し直すたびにデータが消えるので、DBは用意しておいた方がいい。
[こちら](https://qiita.com/zaburo/items/7ab51a7a4d9e1b2d1ec4)の記事のdocker-compose.ymlを利用させていただき、以下のような感じで起動しておく。
```sh
docker compose up -d
```
無事起動したら、以下の環境変数を設定しておく。
```sh
export POSTGRES_HOST=<ホストのIPアドレス>
export POSTGRES_PORT=5432
export POSTGRES_USER=postgres
export POSTGRES_PASSWORD=postgres
```
なお、ここの`POSTGRES_HOST`のIPはGitHubのapplicationで指定したIPと一致させておいた方がいい。

## Backstageのインストール
前提パッケージを入れる。Node.jsはv16かv18じゃないと上手く動かないっぽいので注意。
```sh
sudo apt-get update -y
sudo apt install npm -y
curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash - &&\
sudo apt-get install -y nodejs
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
sudo apt update && sudo apt install yarn
```

インストールする。
```sh
npx @backstage/create-app@latest
```
なお、前提パッケージが不足していると以下のようなエラーでコケる。
```
npx: installed 70 in 6.005s

TypeError: this.breakLines(...).flat is not a function
```
前提パッケージが足りていれば、以下のような感じでappの名前を聞かれる。
```sh
$ npx @backstage/create-app
? Enter a name for the app [required] 
```
ここでは[Getting Started](https://backstage.io/docs/getting-started/)にあわせて`my-backstage-app`を入力した。
この後少し待つと（自環境では5分くらい掛かった）、以下のようなメッセージが出てインストールが完了する。
```
🥇  Successfully created my-backstage-app
```
指定したappの名前でディレクトリが作成されているので、移動する。
```sh
cd my-backstage-app/
```
デフォルトではfrontendをlocalhost:3000で公開する。これをIPによるアクセスに変更したいため設定ファイルを変更する。設定ファイルはapp-config.yamlという名前でディレクトリ内に配置されている。以下の3箇所を変更する。
```yaml
app:
  baseUrl: http://10.151.195.88:3000
backend:
  baseUrl: http://10.151.195.88:7007
  cors:
    origin: http://10.151.195.88:3000
```
変更後、Backstageを起動する。
```sh
yarn dev
```

ブラウザでbaseUrlに指定したURLにアクセスすると、以下のような画面が表示される。
![1682505796166.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/53f0be78-01c1-90e3-bce5-6e48fe241fe3.png)

一度止めて、
- GitHubの認証情報の設定
- Postgresの設定
を`app-config.yaml`に追加する。

GitHubの認証情報の設定内容は以下。
```yaml
auth:
  environment: development
  providers:
    github:
      development:
        clientId: ${AUTH_GITHUB_CLIENT_ID}
        clientSecret: ${AUTH_GITHUB_CLIENT_SECRET}
```
Postgresの設定内容は以下。
```diff
backend:
  database:
-   client: better-sqlite3
-   connection: ':memory:'
+   client: pg
+   connection:
+     host: ${POSTGRES_HOST}
+     port: ${POSTGRES_PORT}
+     user: ${POSTGRES_USER}
+     password: ${POSTGRES_PASSWORD}
```
追記後、再度`yarn dev`で起動し直す。

## 動作確認
適当なGitHubのリポジトリを作成し、それをカタログに追加してみる。
Backstage起動後、右上の`CREATE`をクリックし、`REGISTER EXISTING COMPONENT`をクリックする。
![1683870857938.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/00c3fb62-3b83-7c8d-d193-f9a6f3eaefab.png)

`Select URL`では本来はカタログを定義したyamlのパスを指定するが、初回はそのyamlがないので、リポジトリのURLを指定し`ANALYZE`をクリックする。
`Entity Owner`は`guests`が選べるのでこちらを選択し、`CREATE PR`をクリックすると、Pull Requestが発行されるのでMergeすると、catalog-info.yamlがpushされる。
![1683871525849.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/1bbe8f6f-901e-d0a5-82a8-c11568da9634.png)

カタログ画面に追加したカタログが表示されるようになる。
![1683873050484.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/47201533-c852-a42d-35b2-67b1118efaef.png)

## Kubernetes Pluginの動作確認
[BackstageのPlugin](https://backstage.io/plugins/)は非常に種類が豊富である。
ここではKubernetes Pluginを試してみる。

### Kubernetes Pluginのインストール

オフィシャルサイトは[こちら](https://backstage.io/docs/features/kubernetes/installation)。
Pluginをインストールする。
```sh
yarn add --cwd packages/app @backstage/plugin-kubernetes
```
エンティティのページを修正する。
`packages/app/src/components/catalog/EntityPage.tsx`
をエディタで開き、以下を追加する。
```js
import { EntityKubernetesContent } from '@backstage/plugin-kubernetes';
```
また、既存の`serviceEntityPage`に以下のように追加する。
```diff
const serviceEntityPage = (
  <EntityLayout>
    {/* other tabs... */}
+   <EntityLayout.Route path="/kubernetes" title="Kubernetes">
+     <EntityKubernetesContent refreshIntervalMs={30000} />
+   </EntityLayout.Route>
  </EntityLayout>
);
```
これにより、該当するカタログがKubernetesリソースを持っている場合、以下の様にタブが表示されるようになる。

![1683866152466.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/14a7ccd8-e35c-99f1-ed76-f3bfbc41652d.png)

なお、パラメータの`refreshIntervalMs`の意味はコンテンツの自動更新の間隔で設定しない場合は10秒になる模様。

次にバックエンド側にもPluginを追加する。
Pluginをインストールする。
```sh
yarn add --cwd packages/backend @backstage/plugin-kubernetes-backend
```
KubernetesのバックエンドのPluginを呼び出すファイルを作成する。
```js
cat << EOF > ./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,
    permissions: env.permissions,
  }).build();
  return router;
}
EOF
```
バックエンドのindex.tsにKubernetesプラグインを利用するよう修正する。
`packages/backend/src/index.ts`をエディタで開いて以下を追加する。
```js
import kubernetes from './plugins/kubernetes';
```
次に同じファイルの`main`の中で以下を追加する。
```diff
async function main() {
  // ...
+ const kubernetesEnv = useHotMemoize(module, () => createEnv('kubernetes'));
  // ...
+ apiRouter.use('/kubernetes', await kubernetes(kubernetesEnv));
```

### クラスタ情報の設定（app-config.yaml)

後ほどKubernetes Pluginを使った検証を行うために、EKSとの連携設定を入れる。
公式ドキュメントで設定方法が見つけられず、stackoverflowの[こちら](https://stackoverflow.com/questions/74629572/how-to-connect-backstage-to-kubernetes-cluster-to-discover-pods)を参考にした。
EKSにコンテキストをあわせ、`kubectl cluster-info`でクラスタのエンドポイントのURLを取得する。
```sh
$ kubectl cluster-info
Kubernetes control plane is running at https://xxxxxxxxxx.gr7.us-east-1.eks.amazonaws.com
```

取得したURLを使って`app-config.yaml`に設定を追記する。以下の`url`、`name`を自環境のものに修正する。また、環境変数`AWS_ACCESS_KEY_ID`と`AWS_SECRET_ACCESS_KEY`を使っていない場合はexportしておく。
```yaml
kubernetes:
  serviceLocatorMethod:
    type: 'multiTenant'
  clusterLocatorMethods:
    - type: 'config'
      clusters:
        - url: https://xxxxxxxxxx.gr7.us-east-1.eks.amazonaws.com
          name: my-cluster-eks
          authProvider: 'aws'
          skipTLSVerify: true
          skipMetricsLookup: true

auth:
  environment: 'development'
  providers:
    aws:
      development:
        accessKeyId: ${AWS_ACCESS_KEY_ID}
        secretAccessKey: ${AWS_SECRET_ACCESS_KEY}
```
設定後、backstageを再起動する。

### 動作確認
先程適当なGitHubリポジトリ内に作成した`catalog-info.yaml`を以下のように修正する。
```diff
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
  name: hello-tanzu-backstage
  annotations:
    github.com/project-slug: imurata/hello-tanzu-backstage
+   'backstage.io/kubernetes-label-selector': 'app=hello-tanzu-backstage'
spec:
- type: other
+ type: service
  lifecycle: unknown
  owner: guests
```
上記の設定により、labelに`app=hello-tanzu-backstage`を持つリソースはカタログ上で表示されるようになる。
また、`type`を変更しないとカタログ上にKubernetesのタブが出てこないので、こちらも合わせて変更する。
なお、`kind: Component`の説明は[こちら](https://backstage.io/docs/features/software-catalog/descriptor-format#kind-component)を参照するとよい。
修正してpushしてしばらく待つと、カタログ上のtypeがserviceに書き換わる。
![1683875994525.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/3efcaf44-4b27-a7eb-e07a-ea141d34e54d.png)

deploymentリソースをコマンドで作成した場合、自動的に`app=<deployment名>`でラベルが付与されるため、コマンドでリソースを作成してみる。
```sh
kubectl create deploy hello-tanzu-backstage --image nginx --replicas 3 -n demo
```
しばらく待つと、カタログの`KUBERNETES`タブの中にラベルが一致するリソースの状態が出力される。
![1683876517086.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/7f6053fd-688d-ec35-e18a-74f4da00c124.png)

## まとめ
BackstageのデプロイからKubernetes Pluginの導入まで体験し、クラスタ内のカタログに紐づくリソースが確認できるところまで確認できた。
導入の敷居が高いので、将来的に導入が楽になることを期待したい。