Instana Integration Package Managementの使い方とサンプル

今回は、Instanaで新たに登場した **Integration Package Management** の機能をご紹介し、サンプルパッケージのインポート手順を一緒に見ていきます。

## 🔍 概要：Integration Package Managementとは？

**Integration Package Management（統合パッケージ管理）**は、Instana環境に新しい技術やカスタム要素（ダッシュボードなど）を簡単に取り込むための**パッケージベースの管理機能**です。  
現在、**Public Preview**として提供されており、CLIツールを用いて操作します。

この機能は、IBM Instanaチームが公式に管理する **[Instana Observability as Code Repository](https://github.com/instana/observability-as-code)** を中心に構成されています。  
このリポジトリには、以下のような仕組みが含まれています：

- Instana公式の **Integration Package群**
- パッケージの **ライフサイクルを管理するCLIツール**

**Integration Package** は、以下のような要素をまとめた **技術ごとの拡張モジュール** です：
- カスタムダッシュボード  
- メトリクス可視化定義  
- その他の統合要素  

これにより、**まだInstanaがネイティブ対応していない技術の可観測性を拡張**することが可能になります。  
たとえば、特定のLLM（大規模言語モデル）やOpenTelemetryベースのメトリクスを取り込むパッケージをインポートすれば、**即座にInstana環境でその技術の観測が可能**になります。

作成したパッケージは、**中央レジストリに公開**することもでき、他のユーザーと共有したり再利用することが可能です。
- チーム間での**再利用性・一貫性の向上**

### 🛠 Instana CLI for Integration Package Management

この仕組みを支えるのが、`stanctl-integration` というCLIツールです。  
CLIを使うことで、以下のような操作が可能になります：

- 中央レジストリから **パッケージをダウンロード(download)**
- 任意のInstana環境に **インポート(import)**
- Instana環境からの **要素のエクスポート（export）**
- 自分用の **新しいカスタムパッケージを初期化（init）**
- 作成したパッケージを **中央レジストリに公開（publish）**

```bash
./stanctl-integration --help

Usage: index.js <command> <options>

Commands:
  download     パッケージをダウンロード
  import       パッケージを環境にインポート
  export       環境からエクスポート
  init         新しいパッケージの初期化
  publish      パッケージの公開
```

このCLIにより、Instanaの可観測性構成を **コードとして管理・再利用：Observability as Code（OaC)** できる世界が広がります。

📝 公式ドキュメント  
- https://www.ibm.com/docs/en/instana-observability/current?topic=opentelemetry-integration-package-management-public-preview  
- https://www.ibm.com/docs/en/instana-observability/current?topic=preview-unleashing-your-instana-observe-new-technologies

---

## ⚙️ インストール（CLIダウンロード）

1.CLIはGitHubの[リリースページ](https://github.com/instana/observability-as-code/releases)から取得できます。

2.ダウンロードした後で、適切な名前を変更し、実行権限を与えます。
```bash
mv ./stanctl-integration-macos ./stanctl-integration
chmod +x ./stanctl-integration

# バージョン確認
./stanctl-integration --version
# 1.2.0
```

## 📦 パッケージのダウンロード

### 例：Go用パッケージの取得

```bash
./stanctl-integration download --package @instana-integration/go
```

保存先を指定したい場合：

```bash
./stanctl-integration download --package @instana-integration/go --location ./my-packages

./stanctl-integration download --package @disha-148/llm --location ./custom-packages
```

## 🚀 パッケージのインポート（Import）

今回はサンプルとして、`@disha-148/llm` パッケージにある英語表題などを日本語化したカスタマイズパッケージをインポートしてみます。
例（"title": "***llm-Jacky-demo***"）：

```json
{
  "id": "HtJoaBxfTwa8NFUlSzXXXX",
  "title": "llm-Jacky-demo",
  "accessRules": [
    {
      "accessType": "READ_WRITE",
      "relationType": "GLOBAL",
      "relatedId": ""
    }
  ],
  "widgets": [
    {
      "id": "qJ5q7vtv5i7u-SR5",
      "title": "要求数",
      "width": 12,
      "height": 13,
      "x": 0,
      "y": 48,
      "type": "chart",
      "config": {
        "shareMaxAxisDomain": false,
        "y1": {
          "formatter": "number.compact",
          "renderer": "line",
          "metrics": [
            {
              "lastValue": false,
              "color": "",
              "compareToTimeShifted": false,
              "aggregation": "SUM",
              "label": "",
              "source": "INFRASTRUCTURE_METRICS",
              "type": "oTelLLM",
              "metricPath": [
                "Others",
                "OpenTelemetry SDK LLM"
              ],
              "grouping": [
                {
                  "groupBys": [
                    {
                      "groupbyTag": "metric.tag.model_id",
                      "groupbyTagEntity": "NOT_APPLICABLE",
                      "groupbyTagSecondLevelKey": ""
                    }
                  ],
                  "maxResults": 5,
                  "includeOthers": false,
                  "direction": "DESC"
                }
              ],
              "formatter": "number.compact",
              "metric": "metrics.sums.llm.request.count",
              "timeShift": 0,
              "tagFilterExpression": {
                "logicalOperator": "AND",
                "elements": [],
                "type": "EXPRESSION"
              },
              "allowedCrossSeriesAggregations": [],
              "metricLabel": "OpenTelemetry sums llm.request.count",
              "crossSeriesAggregation": "SUM"
            }
          ],
          "formatterSelected": false
        },
        "y2": {
          "formatter": "number.detailed",
          "renderer": "line",
          "metrics": []
        },
        "type": "TIME_SERIES"
      }
    },
    {
      "id": "FXcZLZA9zdvMTs6u",
      "title": "コスト",
      "width": 12,
      "height": 13,
      "x": 0,
      "y": 35,
      "type": "chart",
      "config": {
        "shareMaxAxisDomain": false,
        "y1": {
          "formatter": "number.compact",
          "renderer": "line",
          "metrics": [
            {
              "lastValue": false,
              "color": "",
              "compareToTimeShifted": false,
              "aggregation": "MEAN",
              "label": "Cost",
              "source": "INFRASTRUCTURE_METRICS",
              "type": "oTelLLM",
              "metricPath": [
                "Others",
                "OpenTelemetry SDK LLM"
              ],
              "grouping": [
                {
                  "groupBys": [
                    {
                      "groupbyTag": "metric.tag.model_id",
                      "groupbyTagEntity": "NOT_APPLICABLE",
                      "groupbyTagSecondLevelKey": ""
                    }
                  ],
                  "maxResults": 5,
                  "includeOthers": false,
                  "direction": "DESC"
                }
              ],
              "formatter": "number.compact",
              "metric": "metrics.gauges.llm.usage.cost",
              "timeShift": 0,
              "tagFilterExpression": {
                "logicalOperator": "AND",
                "elements": [],
                "type": "EXPRESSION"
              },
              "allowedCrossSeriesAggregations": [],
              "metricLabel": "OpenTelemetry gauge llm.usage.cost",
              "crossSeriesAggregation": "MEAN"
            }
          ],
          "formatterSelected": false
        },
        "y2": {
          "formatter": "number.detailed",
          "renderer": "line",
          "metrics": []
        },
        "type": "TIME_SERIES"
      }
    },
    {
      "id": "OBWfaPn_PALtl1AR",
      "title": "トークン",
      "width": 12,
      "height": 13,
      "x": 0,
      "y": 22,
      "type": "chart",
      "config": {
        "shareMaxAxisDomain": false,
        "y1": {
          "formatter": "number.compact",
          "renderer": "line",
          "metrics": [
            {
              "lastValue": false,
              "color": "",
              "compareToTimeShifted": false,
              "aggregation": "SUM",
              "label": "",
              "source": "INFRASTRUCTURE_METRICS",
              "type": "oTelLLM",
              "metricPath": [
                "Others",
                "OpenTelemetry SDK LLM"
              ],
              "grouping": [
                {
                  "groupBys": [
                    {
                      "groupbyTag": "metric.tag.model_id",
                      "groupbyTagEntity": "NOT_APPLICABLE",
                      "groupbyTagSecondLevelKey": ""
                    }
                  ],
                  "maxResults": 5,
                  "includeOthers": false,
                  "direction": "DESC"
                }
              ],
              "formatter": "number.compact",
              "metric": "metrics.gauges.llm.usage.total_tokens",
              "timeShift": 0,
              "tagFilterExpression": {
                "logicalOperator": "AND",
                "elements": [],
                "type": "EXPRESSION"
              },
              "allowedCrossSeriesAggregations": [],
              "metricLabel": "OpenTelemetry gauge llm.usage.total_tokens",
              "crossSeriesAggregation": "SUM"
            }
          ],
          "formatterSelected": false
        },
        "y2": {
          "formatter": "number.detailed",
          "renderer": "line",
          "metrics": []
        },
        "type": "TIME_SERIES"
      }
    },
    {
      "id": "CBNq9r7sAHAJlkIK",
      "title": "モデルごとの合計要求数",
      "width": 4,
      "height": 16,
      "x": 8,
      "y": 5,
      "type": "topList",
      "config": {
        "formatter": "number.compact",
        "metricConfiguration": {
          "formatter": "number.compact",
          "lastValue": false,
          "metric": "metrics.sums.llm.request.count",
          "timeShift": 0,
          "tagFilterExpression": {
            "logicalOperator": "AND",
            "elements": [],
            "type": "EXPRESSION"
          },
          "allowedCrossSeriesAggregations": [],
          "aggregation": "SUM",
          "source": "INFRASTRUCTURE_METRICS",
          "crossSeriesAggregation": "SUM",
          "type": "oTelLLM",
          "metricPath": [
            "Others"
          ],
          "grouping": [
            {
              "maxResults": 5,
              "by": {
                "groupbyTag": "metric.tag.model_id",
                "groupbyTagEntity": "NOT_APPLICABLE",
                "groupbyTagSecondLevelKey": ""
              },
              "includeOthers": false,
              "direction": "DESC"
            }
          ]
        },
        "formatterSelected": false
      }
    },
    {
      "id": "xyjZ3eZaH_G13egG",
      "title": "モデル別の合計トークン数",
      "width": 4,
      "height": 16,
      "x": 0,
      "y": 6,
      "type": "topList",
      "config": {
        "formatter": "number.compact",
        "metricConfiguration": {
          "formatter": "number.compact",
          "lastValue": false,
          "metric": "metrics.gauges.llm.usage.total_tokens",
          "timeShift": 0,
          "tagFilterExpression": {
            "logicalOperator": "AND",
            "elements": [],
            "type": "EXPRESSION"
          },
          "allowedCrossSeriesAggregations": [],
          "aggregation": "SUM",
          "source": "INFRASTRUCTURE_METRICS",
          "crossSeriesAggregation": "SUM",
          "type": "oTelLLM",
          "metricPath": [
            "Others"
          ],
          "grouping": [
            {
              "maxResults": 20,
              "by": {
                "groupbyTag": "metric.tag.model_id",
                "groupbyTagEntity": "NOT_APPLICABLE",
                "groupbyTagSecondLevelKey": ""
              },
              "includeOthers": false,
              "direction": "DESC"
            }
          ]
        },
        "formatterSelected": false
      }
    },
    {
      "id": "8g79cbK3mhWKKeVZ",
      "title": "合計要求数",
      "width": 4,
      "height": 5,
      "x": 8,
      "y": 0,
      "type": "bigNumber",
      "config": {
        "formatter": "number.compact",
        "threshold": {
          "thresholdEnabled": false,
          "operator": ">="
        },
        "comparisonDecreaseColor": "greenish",
        "metricConfiguration": {
          "formatter": "number.compact",
          "lastValue": false,
          "metric": "metrics.sums.llm.request.count",
          "timeShift": 0,
          "tagFilterExpression": {
            "logicalOperator": "AND",
            "elements": [],
            "type": "EXPRESSION"
          },
          "allowedCrossSeriesAggregations": [],
          "aggregation": "SUM",
          "source": "INFRASTRUCTURE_METRICS",
          "crossSeriesAggregation": "SUM",
          "type": "oTelLLM",
          "metricPath": [
            "Others"
          ]
        },
        "comparisonIncreaseColor": "redish",
        "formatterSelected": false
      }
    },
    {
      "id": "NpXbaKW52_M2CqXE",
      "title": "合計トークン数",
      "width": 4,
      "height": 5,
      "x": 0,
      "y": 0,
      "type": "bigNumber",
      "config": {
        "formatter": "number.compact",
        "threshold": {
          "thresholdEnabled": false,
          "operator": ">="
        },
        "comparisonDecreaseColor": "greenish",
        "metricConfiguration": {
          "formatter": "number.compact",
          "lastValue": false,
          "metric": "metrics.gauges.llm.usage.total_tokens",
          "timeShift": 0,
          "tagFilterExpression": {
            "logicalOperator": "AND",
            "elements": [],
            "type": "EXPRESSION"
          },
          "allowedCrossSeriesAggregations": [],
          "aggregation": "SUM",
          "source": "INFRASTRUCTURE_METRICS",
          "crossSeriesAggregation": "SUM",
          "type": "oTelLLM",
          "metricPath": [
            "Others"
          ]
        },
        "comparisonIncreaseColor": "redish",
        "formatterSelected": false
      }
    },
    {
      "id": "L3VuOuc4-Rmy1B9Y",
      "title": "モデル別の合計コスト",
      "width": 4,
      "height": 16,
      "x": 4,
      "y": 5,
      "type": "topList",
      "config": {
        "formatter": "number.compact",
        "metricConfiguration": {
          "formatter": "number.compact",
          "lastValue": false,
          "metric": "metrics.gauges.llm.usage.cost",
          "timeShift": 0,
          "tagFilterExpression": {
            "logicalOperator": "AND",
            "elements": [],
            "type": "EXPRESSION"
          },
          "allowedCrossSeriesAggregations": [],
          "aggregation": "SUM",
          "source": "INFRASTRUCTURE_METRICS",
          "crossSeriesAggregation": "SUM",
          "type": "oTelLLM",
          "metricPath": [
            "Others"
          ],
          "grouping": [
            {
              "maxResults": 5,
              "by": {
                "groupbyTag": "metric.tag.model_id",
                "groupbyTagEntity": "NOT_APPLICABLE",
                "groupbyTagSecondLevelKey": ""
              },
              "includeOthers": false,
              "direction": "DESC"
            }
          ]
        },
        "formatterSelected": false
      }
    },
    {
      "id": "PUjCURABk8HTyjg4",
      "title": "モデル待ち時間",
      "width": 12,
      "height": 13,
      "x": 0,
      "y": 61,
      "type": "chart",
      "config": {
        "shareMaxAxisDomain": false,
        "y1": {
          "formatter": "number.compact",
          "renderer": "line",
          "metrics": [
            {
              "lastValue": false,
              "color": "",
              "compareToTimeShifted": false,
              "aggregation": "MEAN",
              "label": "",
              "source": "INFRASTRUCTURE_METRICS",
              "type": "oTelLLM",
              "metricPath": [
                "Others",
                "OpenTelemetry SDK LLM"
              ],
              "grouping": [
                {
                  "groupBys": [
                    {
                      "groupbyTag": "metric.tag.model_id",
                      "groupbyTagEntity": "NOT_APPLICABLE",
                      "groupbyTagSecondLevelKey": ""
                    }
                  ],
                  "maxResults": 5,
                  "includeOthers": false,
                  "direction": "DESC"
                }
              ],
              "formatter": "number.compact",
              "metric": "metrics.gauges.llm.response.duration",
              "timeShift": 0,
              "tagFilterExpression": {
                "logicalOperator": "AND",
                "elements": [],
                "type": "EXPRESSION"
              },
              "allowedCrossSeriesAggregations": [],
              "metricLabel": "OpenTelemetry gauge llm.response.duration",
              "crossSeriesAggregation": "MEAN"
            }
          ],
          "formatterSelected": false
        },
        "y2": {
          "formatter": "number.detailed",
          "renderer": "line",
          "metrics": []
        },
        "type": "TIME_SERIES"
      }
    },
    {
      "id": "rIMn7M2jVI7Eeiu0",
      "title": "合計コスト",
      "width": 4,
      "height": 5,
      "x": 4,
      "y": 0,
      "type": "bigNumber",
      "config": {
        "formatter": "number.compact",
        "threshold": {
          "thresholdEnabled": false,
          "operator": ">="
        },
        "comparisonDecreaseColor": "greenish",
        "metricConfiguration": {
          "formatter": "number.compact",
          "lastValue": false,
          "metric": "metrics.gauges.llm.usage.cost",
          "timeShift": 0,
          "tagFilterExpression": {
            "logicalOperator": "AND",
            "elements": [],
            "type": "EXPRESSION"
          },
          "allowedCrossSeriesAggregations": [],
          "aggregation": "SUM",
          "source": "INFRASTRUCTURE_METRICS",
          "crossSeriesAggregation": "SUM",
          "type": "oTelLLM",
          "metricPath": [
            "Others"
          ]
        },
        "comparisonIncreaseColor": "redish",
        "formatterSelected": false
      }
    }
  ],
  "ownerId": null,
  "writable": true
}
```

上記の一部日本語化したPackageをInstana Backendにインポートします。
```bash
export INSTANA_SERVER=unitxxxx-instanatenant.instana.io
export API_TOKEN=XXXXRsup402yKyjcww

./stanctl-integration import --package @disha-148/llm --location ./custom-packages \
  --server $INSTANA_SERVER \
  --token $API_TOKEN
```

出力例：
```
info: Start to import the integration package from ...
info: Importing dashboards/llm.json ...
info: Applying the dashboard to https://.../api/custom-dashboard ...
info: Successfully applied ...: 200
info: Total file(s) processed: 1
```

Instana UIにログインし、「llm-Jacky-demo」のカスタムダッシュボードを検索し、表示します。これは日本語ラベル付きのカスタムLLMダッシュボードです：
- 📊 合計トークン数
- 💰 コスト
- ⚡ モデル別の使用量 など

![Screenshot 2025-03-12 at 12.19.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3629252/aa607a75-dfab-4a1b-b15a-2b4861936381.png)
![Screenshot 2025-03-12 at 12.20.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3629252/db66fdc8-4d9f-44c5-a0a8-bdc6f4d2b8ca.png)
![Screenshot 2025-03-12 at 12.20.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3629252/1df2121c-d21e-41f1-9b5b-829936b751c6.png)


## 📤 ダッシュボードのExportも可能！
今のInstana Backend環境にある全部カスタムダッシュボードをパッケージ化して**ローカル保存**することもできます。

```bash
export INSTANA_SERVER=unitxxxx-instanatenant.instana.io
export API_TOKEN=XXXXRsup402yKyjcww

./stanctl-integration export --package jacky/llm --location ./custom-packages \
  --server $INSTANA_SERVER \
  --token $API_TOKEN
```

ログ出力（一部）：
```
info: Start to get the dashboard list 
...
info: Successfully got the dashboard(id=xxx): 200
info: Total dashboard(s) processed: 61
```


## 📎 参考リンク

- [Instana Integration Package Docs](https://www.ibm.com/docs/en/instana-observability/current?topic=opentelemetry-integration-package-management-public-preview)
- [Instana CLI GitHub](https://github.com/instana/observability-as-code?tab=readme-ov-file#instana-cli-for-integration-package-management)