はじめに
「SAP AI Core技術連載」第5回。
SAP AI Coreの管理作業のうち、リソースグループ管理(6.3節)と3種類のシークレット管理(6.4-6.6節)を扱う。前回(#4)はGitリポジトリ・アプリケーション管理を扱った。次回(#6)は高度な機能(AI Content as a Service)を扱う。
リソースグループの概念(定義・分離の目的・リソースの範囲)は#2で、クォータ(リソースグループ最大50個・テナント全体シークレット最大5個)は#3で解説済み。本記事では、それらを実際に操作するAPI手順に絞る。
curl例で使う$AI_API_URL・$TOKENの環境変数設定は#3を参照。
シークレットの全体像
SAP AI Coreで扱うシークレットは、大きく3種類に分かれる。
- オブジェクトストアシークレット(6.4節):データセット・モデルの保存先となるクラウドストレージへの接続情報
- Dockerレジストリシークレット(6.5節):プライベートDockerイメージをpullするための認証情報
- 汎用シークレット(6.6節):上記のシステムシークレットが該当しない、任意の機密情報
前2つは「システムシークレット」と呼ばれ、テンプレート内でコンテナに直接引き渡すことはできない。コンテナに環境変数やボリュームとして渡せるのは汎用シークレットのみである。
Remember: BTP内のSAP AI Coreアクセス認証情報・証明書のローテーションは、リージョンポリシーに従いユーザー自身の責任で行う
6.3 リソースグループ管理
管理者は、サービスコンシューマーや利用シナリオに応じてリソースグループを作成・編集・削除する。
リソースグループIDの命名規則
- 最小3文字、最大253文字
- 先頭と末尾の文字は、小文字・大文字・数字のいずれか
- 2文字目から末尾の1つ前までは、小文字・大文字・数字・ピリオド(
.)・ハイフン(-)が使える - 上記以外の特殊文字は使用不可
6.3.1 リソースグループの作成
curl --location --request POST "$AI_API_URL/v2/admin/resourceGroups" \
--header "Authorization: Bearer $TOKEN" \
--header 'Content-Type: application/json' \
--data-raw '{ "resourceGroupId": "<ID of your resource group>"}'
リクエストが受理されると、202レスポンスが返る。
重要: 新しくリソースグループを作成した場合、そのリソースグループで学習を実行する前に、
defaultという名前のオブジェクトストアシークレットを登録する必要がある(6.4節参照)。これが無いと学習パイプラインは失敗する
6.3.2 リソースグループの編集
curl --location --request PATCH "$AI_API_URL/v2/admin/resourceGroups/{{resource_group_name}}" \
--header "Authorization: Bearer $TOKEN" \
--header 'Content-Type: application/json' \
--data-raw '{ "resourceGroupId": "<ID of your resource group>"}'
6.3.3 リソースグループの削除
{{apiurl}}/v2/admin/resourceGroups/{{resource_group_name}}エンドポイントにDELETEリクエストを送信する。
なお、デフォルトのリソースグループは削除できない(#2参照)。
6.4 オブジェクトストアシークレット管理
SAP AI Coreをクラウドオブジェクトストアに接続し、オブジェクトストアシークレットでアクセスを管理する。接続したストレージには、データセット・モデル、およびMetaflow Library for SAP AI Coreのキャッシュファイルが保存される。
Restriction: 学習出力アーティファクト(モデルなど)を保存するには、
defaultという名前のオブジェクトストアシークレットを作成する必要がある。このdefaultシークレットが存在しないと、学習パイプラインは失敗する。 入力学習アーティファクト用には、必要に応じて異なる名前で複数のオブジェクトストアシークレットを作成できる
対応するハイパースケーラーオブジェクトストア
- Amazon S3
- Azure Blob Storage
- Google Cloud Storage(GCS)
- OSS(Alibaba Cloud Object Storage Service)
- SAP HANA Cloud, Data Lake
6.4.1 オブジェクトストアシークレットの登録
/v2/admin/objectStoreSecretsエンドポイントに登録する。
Note: Azure Blob Storage以外のストレージタイプでは、
dataフィールドはすべて必須。Azureのみ必須項目が個別に指定されている
Amazon S3の場合
curl --location --request POST "$AI_API_URL/v2/admin/objectStoreSecrets" \
--header "Authorization: Bearer $TOKEN" \
--header 'Content-Type: application/json' \
--header 'AI-Resource-Group: <Resource group>' \
--data-raw '{
"name": "default",
"type": "S3",
"bucket": "<S3 bucket name>",
"endpoint": "<S3 end point>",
"pathPrefix": "<A path prefix that follows the bucket name>",
"region": "<S3 region>",
"data": {
"AWS_ACCESS_KEY_ID": "<AWS access key ID>",
"AWS_SECRET_ACCESS_KEY": "<AWS secret access key>"
}
}'
OSS(Alibaba Cloud)の場合
{
"name": "default",
"type": "oss",
"pathPrefix": "<path prefix to be appended with bucketname>",
"data": {
"BUCKET": "<bucket-name>",
"ENDPOINT": "oss-cn-shanghai.aliyuncs.com",
"REGION": "",
"ACCESS_KEY_ID": "xxxxx",
"SECRET_ACCESS_KEY": "xxxxx"
}
}
SAP HANA Cloud, Data Lakeの場合
{
"name": "default",
"type": "webhdfs",
"pathPrefix": "<path prefix to be appended>",
"data": {
"HDFS_NAMENODE": "https://<file-container-name>.files.hdl.canary-eu10.hanacloud.ondemand.com",
"TLS_CERT": "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----\n",
"TLS_KEY": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n",
"HEADERS": "{\"x-sap-filecontainer\": \"<file-container-name>\", \"Content-Type\": \"application/octet-stream\"}"
}
}
Restriction: SAP HANA Data Lakeのオブジェクトストアで、出力アーティファクトがディレクトリを指す場合、ワークフローテンプレートで
archive: none: {}を使ってアーティファクトのアーカイブを無効化することはできない
Azure Blob Storageの場合
{
"name": "default",
"type": "azure",
"pathPrefix": "<path prefix to be appended>",
"data": {
"CONTAINER_URI": "https://account_name.blob.core.windows.net/container_name",
"REGION": "<region name>",
"CLIENT_ID": "<azure client id>",
"CLIENT_SECRET": "<azure client secret>",
"STORAGE_ACCESS_KEY": "sas_token",
"TENANT_ID": "azure tenant id",
"SUBSCRIPTION_ID": "subscription id"
}
}
Azureで必須なのはCONTAINER_URIとSTORAGE_ACCESS_KEYの2つ。REGION・CLIENT_ID・CLIENT_SECRET・TENANT_ID・SUBSCRIPTION_IDは任意。
Google Cloud Storage(GCS)の場合
{
"name": "default",
"type": "gcs",
"pathPrefix": "<path prefix to be appended>",
"data": {
"BUCKET": "<gcs bucket name>",
"PRIVATE_KEY": "<base64 encoded service account key>"
}
}
GCSはBUCKETとPRIVATE_KEYの両方が必須。
Tip:
pathPrefixは、同じバケットを異なるプロジェクトで共有する場合に有用。プロジェクトフォルダ名をmy-ml-project1のように設定すれば、全データがそのフォルダに保存される
Note:
AI-Resource-Groupヘッダを指定しない場合、リソースグループには自動的にdefaultが割り当てられる
このヘッダ省略時の自動適用は、複数リソースグループを運用している環境では事故につながりうる。指定を忘れると、意図したリソースグループではなくdefaultにシークレットが登録され、しかもエラーにはならないため気づきにくい。シークレット操作時はAI-Resource-Groupヘッダを常に明示することを推奨する。
登録に成功すると、202レスポンスと成功メッセージが返る。
6.4.2 オブジェクトストアシークレットの編集
$AI_API_URL/v2/admin/objectStoreSecrets/{{objectStoreName}}エンドポイントにPATCHリクエストを送信する。リクエストボディの構造は登録時と同じ。
curl --location --request PATCH "$AI_API_URL/v2/admin/objectStoreSecrets/{{objectStoreName}}" \
--header "Authorization: Bearer $TOKEN" \
--header 'Content-Type: application/json' \
--header 'AI-Resource-Group: <Resource group>' \
--data-raw '{ ... }'
6.4.3 オブジェクトストアシークレットの削除
$AI_API_URL/v2/admin/objectStoreSecrets/{{objectStoreName}}エンドポイントにDELETEリクエストを送信する。
オブジェクトストアシークレットを削除すると、そのオブジェクトストアへのアクセスは停止する。
6.5 Dockerレジストリシークレット管理
Dockerはアプリケーションをリモートコンテナにパッケージ化して実行する。SAP AI CoreをDockerリポジトリに接続し、Dockerレジストリシークレットでアクセスを管理する。
Dockerレジストリシークレットにより、SAP AI CoreがプライベートDockerイメージをリポジトリからpullすることを認可する。ワークフロー内でシークレット名を指定して、Dockerイメージpullを認証する。
前提条件
- 初期セットアップが完了していること(#3参照)
- インターネットに公開されたDockerレジストリにアクセスできること。VPNや社内ネットワークの背後にあるDockerレジストリは使用できない
6.5.1 Dockerレジストリシークレットの登録
{{apiurl}}/v2/admin/dockerRegistrySecretsエンドポイントにPOSTリクエストを送信する。
curl --location --request POST "$AI_API_URL/v2/admin/dockerRegistrySecrets" \
--header "Authorization: Bearer $TOKEN" \
--header 'Content-Type: application/json' \
--data-raw '{
"name": "mydockerregistry",
"data": {
".dockerconfigjson": "{\"auths\": {\"my.docker.repositories.io\": {\"username\":\"$USERNAME\", \"password\": \"$PWD\"}}}"
}
}'
パラメータは以下の通り。
-
name:Dockerレジストリシークレットの名前。任意の識別子を指定する -
data:Dockerレジストリシークレットを表すJSON文字列
Note: Docker Hub(
hub.docker.com)のパブリックDockerレジストリを使う場合、auths変数の入力にはDocker URLをhttps://index.docker.ioの形式で指定する必要がある
成功すると{"message": "secret has been created"}が返る。
作成後は、テンプレート内でイメージpullシークレットとして参照する。
spec:
imagePullSecrets:
- name: <Name of your Docker registry secret>
6.5.2 Dockerレジストリシークレットの編集
$AI_API_URL/v2/admin/dockerRegistrySecrets/{{dockerRegistryName}}エンドポイントにPATCHリクエストを送信する。ボディの構造は登録時と同じ(name・data)。
6.5.3 Dockerレジストリシークレットの削除
$AI_API_URL/v2/admin/dockerRegistrySecrets/{{dockerRegistryName}}エンドポイントにDELETEリクエストを送信する。
削除するとDockerレジストリへのアクセスは失われる。
6.6 汎用シークレット管理
汎用シークレットは、システムシークレットが該当しない機密情報を格納する。SAP AI Coreがオーケストレーション層として機能する統合シナリオで有用である。
3つのスコープ
汎用シークレットは、リクエストヘッダの組み合わせでスコープを指定する。
-
メインテナントレベル:
AI-Tenant-Scope: true -
リソースグループレベル:
AI-Resource-Group: <resource-group-name> -
テナント全体レベル:
AI-Tenant-Scope: trueかつAI-Resource-Group: *
汎用シークレットは、executionやdeployment内のコンテナに、環境変数またはボリュームマウントとして接続できる。
長時間稼働するdeploymentでテナント全体シークレットを再起動なしにローテーションするには、deployment側がテナント全体シークレットをマウントし、メモリ上のコピーに頼らずマウントしたシークレットの変更を監視する必要がある。テナント全体シークレットが更新された際は、Get SecretエンドポイントのresourceGroupSecretReplicationStatusフィールドを観察し、必要なリソースグループ全体にシークレットが正しく複製されたことを確認する。
テナント全体シークレットはテナントあたり最大5個(#3参照)。
スコープ重複時の挙動
Tip: テナントレベルで作成した汎用シークレットは、全リソースグループに自動的に伝播する。ただし同じ名前の汎用シークレットがリソースグループレベルで作成されると、作成時点でテナントレベルのシークレットを置き換える。システムは定期的にリソースグループレベルのシークレットをテナントレベルのものに上書きするが、この処理には時間がかかる場合がある
リソースグループのユーザーが既存のテナント全体シークレットと同名のシークレットを作成すると、リソースグループレベルで一時的にテナント全体シークレットを上書きしてしまう。この挙動は、メータリングのような重要な処理で問題を引き起こす可能性がある。
意図しない上書きを防ぐには、リソースグループのユーザーが任意のシークレットを作成できないようにする。方法は2つある。
- JWTトークンを渡さないことで、リソースグループレベルのユーザーがシークレットエンドポイントにアクセスすることを制限する
- リソースグループレベルのユーザーには、別の認証メカニズムでリクエストさせて汎用シークレットの作成を許可する。メインテナントがそのリクエストを検証・変換してからランタイムアダプターへ伝播させることで、シークレット名の一貫性を保ち、重要なシークレットが意図せず変更されないようにする
この2案にはトレードオフがある。前者はシンプルだが、リソースグループユーザーによる正当なシークレット操作も一律に塞ぐ。後者は正当な操作を許容できるが、メインテナント側に検証・変換の実装が必要になる。運用体制とリソースグループユーザーの役割に応じて選択する。
6.6.1 汎用シークレットの作成
curl --location --request POST "$AI_API_URL/v2/admin/secrets" \
--header "Authorization: Bearer $TOKEN" \
--header 'Content-Type: application/json' \
--header 'AI-Resource-Group: default' \
--data-raw '{
"name": "my-generic-secret",
"data": {
"some-credential": "bXktc2Vuc2l0aXZlLWRhdGE="
}
}'
Note: 慣例として、後でUnix環境変数として利用しやすいよう、シークレット名はハイフンなしで記述してもよい
成功すると{"message": "secret has been created", "name": "my-generic-secret"}が返る。
6.6.2 汎用シークレットの取得
単一のシークレット取得と、全シークレットの一覧取得ができる。
単一取得
curl --location --request GET "$AI_API_URL/v2/admin/secrets/$SECRET_NAME" \
--header "Authorization: Bearer $TOKEN" \
--header 'AI-Resource-Group: default'
一覧取得
curl --location --request GET "$AI_API_URL/v2/admin/secrets" \
--header "Authorization: Bearer $TOKEN" \
--header 'AI-Resource-Group: default'
レスポンスには、シークレットの名前・ラベル・作成タイムスタンプが含まれる。機密情報そのものはレスポンスに一切現れない。
テナント全体シークレットの場合、レスポンスにはさらに、テナントに紐づく全リソースグループの一覧と、それらへの複製状況が含まれる。
{
"name": "secret-1",
"createdAt": "<timestamp>",
"resourceGroupSecretReplicationStatus": {
"rg-id-1": true,
"rg-id-2": false
},
"labels": {
"<key>": "<value>"
}
}
resourceGroupSecretReplicationStatusの値は、trueがそのnamespaceに正しく複製済み、falseが未複製またはまだ存在しないことを示す。
6.6.3 汎用シークレットの更新
PATCHエンドポイントで、dataとラベルを更新する。シークレット認証情報のローテーションにも使える。
curl --location --request PATCH "$AI_API_URL/v2/admin/secrets/$SECRET_NAME" \
--header "Authorization: Bearer $TOKEN" \
--header 'Content-Type: application/json' \
--header 'AI-Resource-Group: default' \
--data-raw '{
"data": {
"some-credential": "bXktc2Vuc2l0aXZlLWRhdGE="
},
"labels": [
{"key": "ext.ai.sap.com/<key1>", "value": "<value1>"},
{"key": "ext.ai.sap.com/<key2>", "value": "<value2>"}
]
}'
ラベルはシークレットデータと同時に、またはデータの代わりに更新できる。変更できるのはext.ai.sap.com/接頭辞を持つラベルのみ。
Restriction: 以下のラベルはPATCHで更新できない
ext.ai.sap.com/document-groundingext.ai.sap.com/documentRepositoryType
ラベルを削除するには、値を空文字列("")に設定する。
ラベルの更新はシークレットの再作成を必要とせず即座に適用される。更新内容はGETエンドポイントで確認できる。
6.6.4 汎用シークレットの削除
curl --location --request DELETE "$AI_API_URL/v2/admin/secrets/$SECRET_NAME" \
--header "Authorization: Bearer $TOKEN" \
--header 'AI-Resource-Group: default'
成功すると200が返る。
6.6.5 executionまたはdeploymentでの汎用シークレットの利用
リソースグループレベルの汎用シークレットは、executionやdeployment内のコンテナに接続できる。ボリュームとしてマウントするか、環境変数として渡すかを選べる。
Note: この方法でコンテナに接続できるのは汎用シークレットのみ。システムシークレット(オブジェクトストア・Dockerレジストリ)はテンプレートで消費できない
環境変数として渡す(envFrom.secretRef)
spec:
containers:
- name: my-kserve-container
image: centaur
envFrom:
- secretRef:
name: MY_GENERIC_SECRET
シークレットにハイフン(-)などの無効な文字が含まれる場合、この方法ではエラーになる。その場合はenv.valueFrom.secretKeyRefで有効な変数名にマッピングする。
環境変数として渡す(env.valueFrom.secretKeyRef)
spec:
containers:
- name: kserve-container
image: centaur
env:
- name: MY_GENERIC_SECRET
valueFrom:
secretKeyRef:
name: my-generic-secret
key: some-credential
ボリュームマウントとして渡す
spec:
containers:
- name: kserve-container
image: centaur
volumeMounts:
- name: my-generic-secret
mountPath: "/etc/my-generic-secret"
readOnly: true
volumes:
- name: my-generic-secret
secret:
secretName: my-generic-secret
テンプレートパラメータとして渡す
シークレット名はテンプレートのパラメータとして含め、AI API configurationから供給することもできる。
envFrom:
- secretRef:
name: "{{inputs.parameters.secretName}}"
エンドポイント一覧
リソースグループ
- 作成:
POST /v2/admin/resourceGroups - 編集:
PATCH /v2/admin/resourceGroups/{{resource_group_name}} - 削除:
DELETE /v2/admin/resourceGroups/{{resource_group_name}}
オブジェクトストアシークレット
- 登録:
POST /v2/admin/objectStoreSecrets - 編集:
PATCH /v2/admin/objectStoreSecrets/{{objectStoreName}} - 削除:
DELETE /v2/admin/objectStoreSecrets/{{objectStoreName}}
Dockerレジストリシークレット
- 登録:
POST /v2/admin/dockerRegistrySecrets - 編集:
PATCH /v2/admin/dockerRegistrySecrets/{{dockerRegistryName}} - 削除:
DELETE /v2/admin/dockerRegistrySecrets/{{dockerRegistryName}}
汎用シークレット
- 作成:
POST /v2/admin/secrets - 取得(単一):
GET /v2/admin/secrets/{{secretName}} - 取得(一覧):
GET /v2/admin/secrets - 更新:
PATCH /v2/admin/secrets/{{secretName}} - 削除:
DELETE /v2/admin/secrets/{{secretName}}
公式ドキュメントの記載不整合
本記事の対象範囲では、公式ドキュメントに以下の記載不整合が確認できた。実装時は最新の公式APIリファレンスで確認することを推奨する。
-
6.3.3 リソースグループ削除:curlの例のHTTPメソッドが
POSTになっている(サードパーティAPIプラットフォームの説明ではDELETE) -
6.4.1 GCSの登録例:GCSのみエンドポイントが
/objectStoreSecrets/{{objectStoreName}}と記載されている(他のストレージタイプは/objectStoreSecrets) - 6.5.2 Dockerレジストリシークレット編集:「サードパーティAPIプラットフォーム」の節の内容が、DockerではなくGitリポジトリ編集の説明になっている(#4の6.1.2と同一文面)
-
6.6.2/6.6.3のJSON例:
{{secret_name]]という括弧の誤り、dataオブジェクト後のカンマ欠落、引用符の不整合が含まれている(本記事では補正して掲載)
設定時の要注意ポイント
本記事で扱った設定には、ミスをしてもすぐにはエラーにならず、後から原因が分かりにくい形で顕在化するものが複数ある。運用開始前に以下を点検しておくとよい。
-
各リソースグループに
defaultという名前のオブジェクトストアシークレットが登録されているか(無いと学習パイプラインが失敗する) -
シークレット操作時に
AI-Resource-Groupヘッダを明示しているか(省略するとdefaultに登録され、エラーにならない) - テナント全体シークレットと同名のシークレットが、リソースグループレベルで作成されていないか(一時的な上書きが発生する)
- リソースグループユーザーのシークレット作成権限が、運用方針に沿って制限されているか
- Dockerレジストリがインターネットに公開されているか(VPN・社内ネットワーク配下は使用不可)
- テナント全体シークレットが5個の上限に達していないか
- リソースグループが50個の上限に達していないか
-
SAP HANA Data Lakeを使う場合、ワークフローテンプレートで
archive: none: {}を使っていないか
まとめ
- リソースグループIDは3〜253文字。先頭・末尾は英数字、中間はピリオドとハイフンのみ許容される
- 学習出力を保存するには
defaultという名前のオブジェクトストアシークレットが必須。無いと学習パイプラインが失敗する - 対応オブジェクトストアはS3・Azure Blob・GCS・OSS・HANA Cloud Data Lakeの5種類。Azure以外は
dataフィールドが全て必須 - Dockerレジストリはインターネット公開されている必要がある。VPN・社内ネットワーク配下は使用不可
- 汎用シークレットはメインテナント・テナント全体・リソースグループの3スコープを持ち、ヘッダの組み合わせで指定する
- 同名シークレットのスコープ重複は一時的な上書きを引き起こし、メータリング等に影響しうる。リソースグループユーザーのシークレット作成権限を制限して防ぐ
- コンテナに接続できるのは汎用シークレットのみ。システムシークレットはテンプレートで消費できない
- 次回(#6)は高度な機能(AI Content as a Service)を扱う
用語解説
- システムシークレット:オブジェクトストア・Dockerレジストリ向けのシークレット。テンプレートでコンテナに引き渡すことはできない
- 汎用シークレット(Generic Secret):システムシークレットが該当しない機密情報を格納するシークレット。コンテナに環境変数・ボリュームとして渡せる
- pathPrefix:オブジェクトストアのバケット名に続くパス接頭辞。同一バケットを複数プロジェクトで共有する際のフォルダ分けに使う
- imagePullSecrets:Kubernetesテンプレートで、プライベートDockerイメージのpull認証に使うシークレットを指定するフィールド
- resourceGroupSecretReplicationStatus:テナント全体シークレットが各リソースグループへ複製されたかを示すステータスフィールド