Azure AI Translator のドキュメント翻訳を Language Studio から使うと、Blob ストレージを選択した瞬間に <!doctype html> から始まるHTML全文が表示され、アップロード画面へ進めないことがあります。本記事では原因を分解し、CORS設定とRBACロールを中心に、最短で直す手順をまとめます。
現象の整理:Blobストレージを選んだだけで失敗する
Language Studio(language.cognitive.azure.com)の「ドキュメント翻訳」で、ソース/ターゲットに使う Blob ストレージアカウントを選択したタイミングで、画面にエラーメッセージではなく <!doctype html> から始まるHTMLがそのまま表示されるケースがあります。
よくある状況は次のとおりです。
- コンテナー一覧を出す前、または出そうとした直後に落ちる(ソース文書アップロード画面に進めない)。
- Translator リソース自体(テキスト翻訳など)は正常に動作している。
- 以前は「Language Product と User に Storage Blob Data Contributor が必要」などの権限不足エラーが出たが、ロール付与は済んでいる。
- テスト用の小さなDOCXでも同じで、ストレージ選択の時点で止まる。
- ストレージのアクセス階層(Hot / Cool / Cold)やリージョン、リソースグループを変えても改善しない。
なぜ <!doctype html> が出るのか:内部エラーの“中身”が露出している
本来、Language Studio はバックエンド(Language サービス側)との通信結果を「人が読めるメッセージ」に整形して表示します。ところが、特定の条件で通信が失敗すると、失敗レスポンスとして返ってきた“HTMLページ”がそのまま画面に表示されることがあります。
代表例は次のようなパターンです。
- ブラウザからのリクエストが CORS(クロスオリジン制約)でブロックされ、期待したJSONではなくエラー応答(または別ページ)が返る。
- 認証やプロキシの都合で、API応答ではなく「ログインページ」「エラー説明ページ」などのHTMLが返り、フロントエンドがそれを文字列として扱ってしまう。
つまり、HTMLが見えている時点で「アプリが受け取るべき形式(JSONなど)になっていない」可能性が高く、原因はストレージ側の設定(特にCORS)に寄っていることが多いです。
原因の本命:Language Studioはブラウザ経由でBlobへアクセスする
Language Studio の操作はブラウザ上で行われます。Blobストレージを選択したとき、画面は「このストレージにアクセスできるか」「コンテナーを列挙できるか」などを確認するため、ブラウザから Blob のエンドポイントへリクエストを投げる場面があります。
ブラウザはセキュリティのため、別ドメインへのアクセスを厳しく制限します。Language Studio のドメイン(https://language.cognitive.azure.com)から、あなたのストレージアカウント(https://<account>.blob.core.windows.net)へ直接アクセスするのは別オリジン通信なので、Blob側が CORS ヘッダーを返さない限り、ブラウザはレスポンスを利用できません。
特に、ストレージの一覧取得やアップロード前の検証では、ブラウザが事前リクエスト(Preflight)として OPTIONS を送ることがあります。ここで拒否されると、以降の GET / PUT がブロックされ、Language Studio 側は正常系のレスポンス(JSON)を受け取れずに内部エラーになります。
解決策:BlobサービスのCORSを正しく設定する
結論から言うと、Blob ストレージ側の CORS を、Language Studio のオリジンに合わせて許可するのが最優先です。これで「Blob選択時に <!doctype html> が出る」現象が解消するケースが非常に多いです。
設定場所(Azureポータル)
- Azure ポータルで対象のストレージアカウントを開く
- 左メニューの 「設定」→「リソース共有 (CORS)」 を開く
- Blob サービスの CORS を編集する(File/Queue/Table ではありません)
- 新しい CORS 規則を追加して保存する
推奨CORSルール(まずは動かす)
まずは再現性を優先して、次の値で設定してみてください。
| 項目 | 設定値 | 意図 |
|---|---|---|
| Allowed origins | https://language.cognitive.azure.com | Language Studio からのブラウザアクセスのみ許可 |
| Allowed methods | DELETE, GET, POST, OPTIONS, PUT | アップロード/一覧取得/検証で使う可能性があるメソッドを許可 |
| Allowed headers | * | 要求ヘッダーの検証で詰まらないようにまずは全許可 |
| Exposed headers | * | レスポンスヘッダーをJSから参照できるようにする |
| Max age | 500 秒 | Preflight 結果のキャッシュ時間。短すぎると往復が増える |
保存後すぐに反映されないことがあります。反映まで数分のタイムラグが出ることがあるので、保存したら少し待ってから Language Studio をリロードして再試行してください。
CORSの各項目を“意味”で理解しておく
値をコピペして終わりにせず、なぜそれが必要かを理解しておくと、次回のトラブル対応が速くなります。
| 項目 | ここでの役割 | 間違えると起きること |
|---|---|---|
| Allowed origins | 「どのWebサイトからのアクセスを許すか」 | Origin不一致でブラウザが応答を読めず、画面が内部エラーになる |
| Allowed methods | 「どのHTTPメソッドを許すか」 | OPTIONS を許可しないとPreflightで詰まりやすい |
| Allowed headers | 「要求ヘッダーの検証」 | 特定ヘッダーが原因でPreflightが失敗する |
| Exposed headers | 「JSから参照できるレスポンスヘッダー」 | アプリ側が必要なヘッダーを読めずに失敗することがある |
| Max age | 「Preflightの結果をどれだけキャッシュするか」 | 短すぎると通信回数が増え、遅延や一時的な失敗が増える |
CORSでつまずきやすいポイント
| つまずき | ありがちな状態 | 対処 |
|---|---|---|
| オリジンの書き方が違う | http になっている/末尾に / を付けた/別サブドメインを許可した | https://language.cognitive.azure.com を完全一致で登録 |
| サービス種別を間違える | File サービスのCORSを編集している | Blob サービス側を編集 |
| OPTIONS を許可していない | Allowed methods に OPTIONS がない | Preflight のために OPTIONS を追加 |
| 保存したが反映待ち | 直後は同じエラーが出る | 数分待つ/シークレットウィンドウで再試行 |
アクセス権限(RBACロール)もセットで再確認する
CORS は「ブラウザがレスポンスを使えるか」の問題で、RBAC は「そもそもストレージにアクセスしてよいか」の問題です。どちらも満たさないと動きません。
必要になりやすい主体とロール
ドキュメント翻訳は、(1) Language Studio を操作するユーザー、(2) Translator リソース(または Language リソース)のマネージド ID の両方がストレージへアクセスする構成になりやすいです。一般的には次の割り当てを確認します。
| 主体(誰に付けるか) | 推奨ロール | スコープ | 理由 |
|---|---|---|---|
| あなた(操作ユーザー) | Storage Blob Data Contributor | ストレージアカウント(または対象コンテナー) | Language Studio からコンテナーを列挙・読み書きするため |
| Translator リソースのマネージド ID | Storage Blob Data Contributor | ストレージアカウント(または対象コンテナー) | 翻訳ジョブ実行時にソースの読み取りとターゲットへの書き込みが必要 |
注意:「共同作成者(Contributor)」や「所有者(Owner)」は管理プレーンのロールで、データプレーン(Blobの中身)へのアクセス権とは別です。ドキュメント翻訳のトラブルシュートでは、データプレーンのロール(Storage Blob Data Contributor など)を見落としやすいので、必ず確認してください。
マネージドIDを使うときの確認事項
- Translator(または Language)リソースで、システム割り当てマネージド ID が有効になっているか。
- IAM の割り当て先が「アプリケーション」ではなく、リソースのマネージドIDになっているか。
- 割り当てスコープが狭すぎないか(対象コンテナーを間違えていないか)。
- ロール付与直後は反映に時間がかかることがあるため、数分待ってから再試行する。
ポータルでの確認手順(最短ルート)
- Translator リソース(または Language リソース)を開く
- 「ID」(Identity)で「システム割り当て」をオンにする
- 対象のストレージアカウントを開き、「アクセス制御(IAM)」→「ロールの割り当ての追加」
- ロールに Storage Blob Data Contributor を選び、割り当て先に「マネージドID」を指定して Translator リソースを選択
Azure CLIで付与する場合(例)
ポータル操作が難しい環境や、IaC化したい場合はCLIでも割り当てできます。以下は“考え方”を掴むための例です(実際のIDやスコープは環境に合わせて置き換えてください)。
az role assignment create \
--assignee-object-id <ObjectId> \
--assignee-principal-type ServicePrincipal \
--role "Storage Blob Data Contributor" \
--scope "/subscriptions/<SUB>/resourceGroups/<RG>/providers/Microsoft.Storage/storageAccounts/<ACCOUNT>"
コンテナー単位に絞るなら、スコープを次のようにします。
/subscriptions/<SUB>/resourceGroups/<RG>/providers/Microsoft.Storage/storageAccounts/<ACCOUNT>/blobServices/default/containers/<CONTAINER>
「Hot / Cool / Cold」は原因になり得る?
結論として、今回の「ストレージを選択した瞬間に HTML エラーになる」現象の直接原因である可能性は低いです。アクセス階層は、主にコストと取得レイテンシの設計の話で、CORS や認可のように「画面が次に進めない」タイプのエラーを起こす要因ではありません。
ただし、アクセス階層の理解は運用で役に立つので整理しておきます。
| 階層 | オンライン/オフライン | 特徴 | 翻訳ジョブへの影響 |
|---|---|---|---|
| Hot | オンライン | 頻繁に読むデータ向け | 基本的に問題になりにくい |
| Cool | オンライン | 読み取り頻度が低いデータ向け | 通常は問題になりにくい |
| Cold | オンライン | さらに低頻度向け(オンラインのまま) | 選択時エラーの原因にはなりにくい |
| Archive | オフライン | 再水和(rehydrate)が必要 | ソースがArchiveだと読み取れずジョブが失敗する可能性がある |
もし「既に置いてあるファイルを翻訳したい」のに失敗する場合は、ソースBlobが Archive になっていないか、あるいは再水和が完了しているかも確認するとよいです。しかし、ストレージ選択の段階で落ちる問題は、やはり CORS と RBAC の影響が大きいです。
まだ直らない場合の追加チェックリスト
CORS と RBAC を整えても改善しない場合は、次の観点を上から順に潰すと最短で原因に到達できます。
ストレージのネットワーク設定(ファイアウォール/パブリックアクセス)
- ストレージアカウントの「パブリック ネットワーク アクセス」が無効になっていないか。
- ファイアウォールで「選択したネットワーク」のみに制限している場合、あなたのアクセス元IPが許可されているか。
- Private Endpoint のみで公開している場合、Language Studio(ブラウザ)からは到達できず、選択段階で失敗しやすい。
Language Studio はブラウザ上で動くため、あなたのPC(またはVDI)からストレージに到達できるネットワークである必要があります。セキュリティ要件が高い場合は、まず検証用にパブリックアクセスで動作確認し、動いたことを確認してから段階的に制限するのが安全です。
ブラウザ/拡張機能の影響
- 広告ブロッカーやセキュリティ拡張が、Storage へのリクエストを遮断していないか。
- トラッキング防止が強い設定になっていて、必要なCookieやリダイレクトが阻害されていないか。
- シークレットウィンドウで再現するか(拡張が無効化されるため切り分けに有効)。
コンテナー設計(入力/出力の分離)
ドキュメント翻訳は、ソースとターゲットでコンテナー(またはプレフィックス)を分けておくと運用が楽になります。
- ソース専用コンテナー:アップロード用。翻訳対象だけが置かれる。
- ターゲット専用コンテナー:翻訳結果の出力先。結果が混ざりにくい。
権限をコンテナー単位で付与している場合、Language Studio 側で選択したコンテナーと、実際にロール付与したコンテナーが一致しているかも確認してください。
リージョンとリソースの組み合わせ
Translator リソースとストレージは、運用上は同一リージョンに寄せるのが無難です(レイテンシ、データ移動、コンプライアンス面で有利)。ただし、今回のような HTML エラーは、リージョン差よりも CORS/RBAC が原因であることがほとんどです。
トラブルシュートを速くする“見るべき症状”早見表
| 症状 | 可能性が高い原因 | 最初にやること |
|---|---|---|
ストレージ選択直後に <!doctype html> | CORS未設定/Preflight失敗 | BlobサービスのCORSに https://language.cognitive.azure.com を追加 |
| 「権限がない」系の明確なメッセージ | RBAC不足(データプレーン) | Storage Blob Data Contributor をユーザーとMIに付与 |
| 特定のPCだけ失敗する | ネットワーク制限/拡張機能 | シークレットで再現確認、ストレージのファイアウォール確認 |
| 既存ファイルだけ翻訳に失敗する | Archive階層/ファイルロック等 | Blobのアクセス階層と状態、再水和の有無を確認 |
実運用でのおすすめ設定(最小権限+切り分けしやすさ)
検証で動いた後、運用で「安全に」「次回も迷わない」状態にするためのコツです。
- CORS の Allowed origins は
https://language.cognitive.azure.comのみに絞る(安易に*にしない)。 - RBAC はストレージアカウント全体ではなく、可能なら入力/出力コンテナーにスコープを絞って付与する。
- 入力と出力はコンテナーを分け、誤翻訳・上書き・混在を避ける。
- テスト用の小さなDOCXで「選択→アップロード→実行→出力確認」までの手順をテンプレ化しておく。
- 本番運用では、Network 制限や Private Endpoint など“強い制限”を入れる前に、まずはCORS/RBACを含めた正常系の手順が再現できる状態を作る。
最終的な解決イメージ
最後に、作業を短くまとめます。困ったらこの順で見直してください。
- BlobサービスのCORSに
https://language.cognitive.azure.comを追加し、DELETE/GET/POST/OPTIONS/PUTとヘッダー*を許可(Max age=500)。 - 操作ユーザーと Translator のマネージドIDに Storage Blob Data Contributor を付与(スコープの付け先を間違えない)。
- 数分待ってから Language Studio をリロードし、再度 Blob ストレージを選択してアップロード画面へ進めるか確認する。
ここまで整えると、<!doctype html> から始まる不可解な内部エラーは解消し、Language Studio からドキュメント翻訳ジョブを最後まで実行できるようになります。

コメント