Azure SQL Managed Instance(MI)で SQLCLR を使い、Azure Key Vault のシークレットを取得しようとすると「依存アセンブリが見つからない」「CREATE ASSEMBLY の取り込み方法が違う」「ライブラリ追加で System 系が不足する」など、ローカル SQL Server では起きにくい壁に当たります。現場で再現しやすいエラーと、回避しやすい設計まで含めて整理します。
Azure SQL Managed Instance で SQLCLR を使うときの前提
Azure SQL Managed Instance は “SQL Server 互換” の機能が多い一方で、運用・セキュリティの都合から SQLCLR 周りは特に制約が強めです。まずは「SQLCLR を動かすための最低限」と「MI ならではの落とし穴」を押さえておくと、調査が早くなります。
| 観点 | ローカル SQL Server での感覚 | Managed Instance でハマりやすい点 | 実務的な方針 |
|---|---|---|---|
| CLR 有効化 | 設定して終わり | 設定はできても、後続の「信頼・権限」で止まりやすい | 最初に “動作条件チェック” をテンプレ化 |
| アセンブリ投入 | ファイルパス指定が一般的 | ファイルパス指定が使えない/使いにくいケースがある | 0x(varbinary)で投入できるルートを確保 |
| 依存関係 | メイン DLL を入れれば動くことがある | 依存 DLL をすべて SQL カタログに登録しないと止まる | 依存 DLL 一式を“順番に”登録する運用に寄せる |
| 外部通信 | EXTERNAL_ACCESS/UNSAFE で頑張る | セキュリティ/ネットワーク制約で実行時に失敗しやすい | Key Vault 参照は外部コンポーネントへ逃がすのが現実的 |
最低限の設定(SQLCLR を有効化)
SQLCLR を使うなら、まずは有効化が必要です。環境によっては権限不足で実行できない場合もあるため、運用チームと役割分担を決めておくと安全です。
-- SQLCLR を有効化
EXEC sp_configure 'clr enabled', 1;
RECONFIGURE;
加えて、環境によっては “信頼されたアセンブリ” として扱う設定(trusted assembly)や、CLR strict security(環境既定)への対応が必要になります。後述の sp_add_trusted_assembly を使うケースは、この文脈です。
Azure.Identity が見つからない(依存アセンブリ不足)で CREATE ASSEMBLY が失敗する
よくある症状
Azure SQL Managed Instance から Azure Key Vault のシークレットを取得したく、SQLCLR(UDF)で Azure.Identity(DefaultAzureCredential)+ Azure.Security.KeyVault.Secrets を利用してアセンブリを登録しようとすると、次のようなエラーに遭遇します。
Msg 6503, Level 16, State ...
Assembly 'azure.identity, version=1.12.0.0 ...' was not found in the SQL catalog.
これは「SQL Server が依存 DLL を勝手に探してロードしてくれる」タイプの実行環境ではないためです。SQLCLR は “SQL カタログ(sys.assemblies)に登録されたアセンブリ” しか参照できず、参照先が欠けていると登録時点で止まります。
原因の整理:SQLCLR は “参照している DLL 一式” が必要
たとえば、あなたの UDF DLL が Azure.Security.KeyVault.Secrets を参照している場合、その先で Azure.Core や System.*、さらに内部で参照される周辺 DLL が連鎖します。ローカルのアプリ実行であれば NuGet の依存解決で勝手に配置されますが、SQLCLR では SQL 側へ「登録」しない限り存在しません。
| 対象 | 不足していると起きること | 典型メッセージ | 対処の方向性 |
|---|---|---|---|
| Azure.Identity | 認証クラスの参照が解決できない | Assembly ‘azure.identity …’ was not found | Azure.Identity 自体を登録 |
| Azure.Core | Azure SDK の土台が欠けて動かない | Assembly ‘azure.core …’ was not found | Azure.Core を先に登録 |
| Azure.Security.KeyVault.Secrets | SecretClient が解決できない | Assembly ‘azure.security.keyvault.secrets …’ was not found | KeyVault.Secrets を登録 |
| System 系(例:System.Runtime.Serialization) | JSON/シリアライズなどで詰まる | Assembly ‘system.runtime.serialization …’ was not found | 追加投入は慎重に(後述) |
解決策:依存 DLL を “一式” 登録する(順番が重要)
結論として、参照している DLL は 1 本だけでは足りません。依存 DLL 一式を SQL 側に順番に登録します。順番を意識する理由は単純で、登録するアセンブリがさらに別アセンブリを参照していると、その参照先が未登録の時点で CREATE ASSEMBLY が失敗するためです。
依存関係の把握は、次のどれかが実務で早いです。
- ビルド成果物の出力フォルダ(bin)にある DLL を一覧化し、Azure.* 系を中心に依存を洗い出す
- ILSpy 等でメイン DLL の参照を確認する
- ローカル SQL Server に一度デプロイして “必要だったもの” を逆算する(後述のスクリプト化と相性が良い)
登録済みかの確認(sys.assemblies)
「足りているか」「名前が想定どおりか」を確認するには、まずは SQL カタログを見ます。
-- ユーザー定義アセンブリを確認
SELECT
name,
permission_set_desc,
create_date
FROM sys.assemblies
WHERE is_user_defined = 1
ORDER BY create_date DESC;
依存が足りていない場合、CREATE ASSEMBLY で止まることもありますが、いったん登録できたように見えても実行時に別の依存で落ちるケースもあります。したがって「登録されたアセンブリ」と「実行時の例外」の両方をセットで追うのが近道です。
“Azure SDK が SQLCLR と相性が悪い” 現実
依存 DLL を登録しても、MI の SQLCLR は制約が強く、Azure SDK 系が動かない/通らない可能性があります。主に次が原因になりがちです。
- 認証フローが SQLCLR 内に馴染まない:
DefaultAzureCredentialは複数の認証手段を順に試す設計で、SQLCLR の実行コンテキストだと “試すだけで重い/失敗する” 経路が増えがちです。 - 外部通信・TLS・プロキシ:Key Vault は HTTPS 前提です。SQLCLR の外部通信(権限/ネットワーク)が整っていないと、依存登録が通っても実行で落ちます。
- 追加の System 系アセンブリ要求:Azure SDK や JSON 系は、環境によって System.* の不足に波及しやすいです(後述)。
このため「どうしても SQL から Key Vault を叩きたい」よりも、「安全に運用できる範囲で実現する」に寄せた設計が、トータルで失敗しにくいです。具体的な回避策は後半でまとめます。
Managed Instance ではファイルパス指定の CREATE ASSEMBLY が使えない/使いにくい
よくある誤解:FROM ‘C:\path\to\xxx.dll’ の前提が崩れる
回答例や古いナレッジでは、次のようにファイルから読み込む形がよく登場します。
CREATE ASSEMBLY [MyClr]
FROM 'C:\path\to\MyClr.dll'
WITH PERMISSION_SET = SAFE;
しかし Managed Instance では、この “C:\ のファイルを SQL Server が読み込む” 前提が成立しないことがあります。MI は PaaS として管理されるため、OS/ファイルシステムを利用者が自由に扱えるとは限りません。結果として「パスが存在しない」「アクセスできない」「そもそもサポートされない」など、環境依存の詰まり方をします。
解決策:varbinary(max) の 16 進リテラル(0x…)で登録する
MI 側に確実に持ち込める形として実務で強いのが、アセンブリを 0x(16進)で登録する方法です。つまり CREATE ASSEMBLY ... FROM 0x... の形にします。
CREATE ASSEMBLY [MyClr]
FROM 0x4D5A900003000000...
WITH PERMISSION_SET = SAFE;
ポイントは「0x の中身をどう作るか」です。現実的には、ローカル SQL Server に一度デプロイして、そこからバイナリを取り出してスクリプト化し、MI に移植するのが安定します。
実務的な移植手順(ローカル → MI)
手順のイメージは次のとおりです。
| 手順 | やること | 狙い | 注意点 |
|---|---|---|---|
| ローカル | SQL Server に SQLCLR を通常の方法でデプロイ | 依存 DLL 一式を確定させる | ローカル環境と MI の差分は残るため、最終的には MI で検証 |
| ローカル | sys.assembly_files の content(varbinary)を取得 | 0x 化の元データを得る | サイズが大きいと出力が扱いにくい |
| ローカル | 16 進文字列に変換し、CREATE ASSEMBLY ... FROM 0x... を生成 | 移植スクリプトを作る | 出力文字数・改行位置に注意 |
| MI | 依存 DLL → メイン DLL の順に CREATE ASSEMBLY | 依存解決しながら登録 | permission_set / trusted / 権限に注意 |
sys.assembly_files から content を取り出す例
ローカル SQL Server 側で、対象アセンブリのバイナリを取得します。複数ファイルがある場合は file_id や file_name を見て扱います。
-- 対象アセンブリのファイル情報とバイナリサイズ
SELECT
a.name AS assembly_name,
af.file_id,
af.file_name,
DATALENGTH(af.content) AS bytes
FROM sys.assemblies AS a
INNER JOIN sys.assembly_files AS af
ON a.assembly_id = af.assembly_id
WHERE a.name = N'MyClr'
ORDER BY af.file_id;
0x 化は、環境によっては master..fn_varbintohexstr を使うのが簡単です。ただし巨大な varbinary を 1 行で出すと取り回しが悪くなるため、現場では「分割して出力」「SQLCMD でファイルに落とす」などの工夫が必要になります。
イメージ(分割出力の発想):
- content を一定サイズに分割し、順に 16 進化して連結する
- スクリプト生成は SQL だけで頑張りすぎず、PowerShell などで整形する
信頼済みアセンブリ(sp_add_trusted_assembly)を絡める場合
環境によっては、単に登録するだけではなく、sp_add_trusted_assembly による信頼登録が必要になるケースがあります。これは “CLR のセキュリティを厳格にする” 設定が有効な場合に、未署名/未信頼のアセンブリがブロックされるのを回避するための仕組みです。
手元でハッシュを作る典型パターンは次の発想になります(内容は環境に合わせて調整してください)。
-- 例:アセンブリバイナリから SHA2_512 のハッシュを作って trusted に追加する考え方
DECLARE @hash varbinary(64);
SELECT @hash = HASHBYTES('SHA2_512', af.content)
FROM sys.assemblies a
JOIN sys.assembly_files af ON a.assembly_id = af.assembly_id
WHERE a.name = N'MyClr' AND af.file_id = 1;
EXEC sys.sp_add_trusted_assembly @hash = @hash, @description = N'MyClr';
ここで重要なのは「trusted にすれば何でも動く」ではなく、MI の制約の中で許される範囲を超えると、最終的には実行時に詰まる点です。trusted はあくまで “信頼の入口” であり、外部通信や OS 依存機能を無制限に解放するものではありません。
依存 DLL を 1 本化(ILMerge)しようとして MSBuild タスクで失敗する
よくある症状
「依存 DLL を減らしたい」「sp_add_trusted_assembly に渡す対象を 1 本にしたい」と考えて ILMerge(MSBuild.ILMerge.Task など)を導入すると、次のようなビルドエラーで止まることがあります。
MSB4062 ... MSBuild.ILMerge.Task could not be loaded ...Microsoft.Build.Utilities.v4.0 ... が見つからない
これは ILMerge 自体が古い前提(.NET Framework のビルド環境や MSBuild バージョン)に強く依存し、現代的な SDK-style プロジェクトや新しめの Visual Studio / MSBuild との相性で崩れることがあるためです。
解決策:ILMerge は “使わなくてよい” が最短
結論として、依存 DLL は 1 本にまとめず、必要な DLL をそれぞれ CREATE ASSEMBLY で登録する運用がいちばん堅いです。SQLCLR はアプリ配布ではなく “DB カタログへの登録” が中心になるため、アセンブリ数の増減よりも、次の点のほうが支配的に効きます。
- 登録順が正しいか(依存解決できるか)
- 必要な DLL が漏れていないか
- trusted / 権限 / permission_set が環境要件を満たすか
| 方針 | メリット | デメリット | おすすめ度 |
|---|---|---|---|
| 依存 DLL を個別登録 | 構造が素直でトラブルシュートしやすい | アセンブリ数が増える | 高 |
| ILMerge で 1 本化 | 見た目はシンプル | ビルド環境・ライセンス・強名・リフレクションで壊れやすい | 低 |
| ローカル SQL にデプロイ→スクリプト化 | 必要 DLL の確定と移植が一体化できる | ローカル環境が必要 | 高 |
「ビルドを通す」こと自体に時間を溶かすより、個別登録+スクリプト移植に寄せたほうが、結果として短期で収束しやすいです。
Newtonsoft などを追加すると system.runtime.serialization が無いと言われる
よくある症状
Newtonsoft.Json 等のライブラリを追加して別アセンブリとして登録しようとしたところ、次のように止まるケースがあります。
Msg 6503, Level 16, State ...
Assembly 'system.runtime.serialization, version=4.0.0.0 ...' was not found in the SQL catalog.
これは「アプリの実行環境(.NET Framework/ランタイム)には存在するはずの System 系アセンブリ」が、SQLCLR ホスト環境の取り扱いとしては “SQL カタログに存在しない” 扱いになり、参照解決ができない状態です。SQLCLR の世界では、System 系であっても、必要なものが “利用可能” とは限りません。
結論:System 系 DLL の追加投入運用は推奨しにくい
不足している System 系を追加で投入して乗り切りたくなりますが、Managed Instance では特に慎重になるべきです。理由は次のとおりです。
- OS/サーバー更新と整合している必要がある:System 系 DLL は環境の更新とセットで整合が取れている前提になりがちです。
- MI のパッチ適用は利用者が制御できない:将来の更新で挙動が変わったとき、追加投入した DLL が足を引っ張る可能性があります。
- 依存が芋づる式に増える:Newtonsoft だけのつもりが、別の System.* や周辺 DLL を要求して止まる、が繰り返されやすいです。
現実的な落としどころ
「SQLCLR の中で JSON を扱いたい」などが目的なら、次の選択肢を優先すると安全側に倒せます。
| 目的 | SQLCLR に寄せた場合 | おすすめの代替 | 理由 |
|---|---|---|---|
| JSON 生成 | Newtonsoft を入れて組み立て | T-SQL の FOR JSON | 追加 DLL 不要で安定 |
| JSON 解析 | Newtonsoft でパース | OPENJSON / JSON 関数 | SQL 側の標準機能で完結 |
| 複雑な変換 | SQLCLR で頑張る | 外部処理(Functions / アプリ層)に逃がす | 依存地獄と将来不整合を避けやすい |
もしどうしても SQLCLR が必要な場合でも、「重い外部ライブラリを増やす」のではなく、最小限の自前実装に寄せるほうが、MI では生存率が上がります。
Key Vault 参照を SQLCLR に寄せない “現実解” の設計
ここまでの話をまとめると、Azure SQL Managed Instance の SQLCLR で Azure Key Vault を直接参照するのは、次の理由でリスクが高いです。
- 依存 DLL を揃えるだけで工数が読みにくい
- 登録できても実行時に外部通信・認証で落ちる可能性がある
- System 系不足に波及すると、将来の更新リスクが大きい
そのため、Key Vault へのアクセスは SQLCLR に寄せず、外部コンポーネント(Azure Functions 等)に任せ、SQL は結果だけを受け取る構成が実務では強いです。
代表的なアーキテクチャ
| 構成 | Key Vault を読む場所 | SQL への渡し方 | 向いているケース | 注意点 |
|---|---|---|---|---|
| アプリ層で取得 | アプリ(Web/API/バッチ) | パラメータ・接続文字列などで SQL に渡す | いちばん一般的、運用しやすい | 秘密情報の取り回し設計(ログ/例外)に注意 |
| Azure Functions で取得 | Functions(Managed Identity) | アプリ経由で呼び出して SQL へ反映 | Key Vault 取得を共通化したい | SQL から直接 HTTP できない前提なら呼び出し経路が必要 |
| イベント駆動で同期 | Functions/Logic Apps | シークレット更新時に SQL 側の設定テーブルへ同期 | 回転(ローテーション)がある | DB に保存するなら暗号化/権限設計は必須 |
“SQL から Key Vault を取りに行く” 発想を変える
そもそも「SQL がシークレットを取得しないといけない」要件は、掘ると別の置き方ができることがあります。たとえば次のような整理です。
- SQL が外部 API を呼ぶためのキーが必要 → 呼ぶのは SQL ではなくアプリ/API に寄せられないか
- 暗号鍵が必要 → Key Vault と統合された専用機能(TDE/Always Encrypted 等)で目的を満たせないか
- バッチ処理の接続情報が必要 → バッチ実行基盤側で Key Vault 参照して接続する設計にできないか
SQLCLR は “最後の手段” にしておくと、将来の保守が圧倒的に楽になります。
SQLCLR(MI)トラブルシューティングのチェックリスト
最後に、ハマりやすい論点をチェックリスト化します。切り分けの順番が重要で、上から順に潰していくと迷子になりにくいです。
| チェック項目 | 確認方法 | ダメだった場合の次の一手 |
|---|---|---|
| CLR が有効か | sp_configure 'clr enabled' | 有効化して RECONFIGURE |
| アセンブリが登録されているか | sys.assemblies を参照 | 依存 DLL の漏れを洗う |
| 依存 DLL を順番に登録しているか | エラーメッセージの “見つからない DLL” を辿る | 土台(Azure.Core 等)から入れ直す |
| permission_set が適切か | sys.assemblies.permission_set_desc | 必要なら EXTERNAL_ACCESS/UNSAFE の要件を整理(ただし安易に上げない) |
| trusted / 署名が必要な環境か | 登録は通るが実行でブロックされる等 | sp_add_trusted_assembly または署名方式を検討 |
| System 系不足に波及していないか | system.runtime.serialization 等の不足エラー | 外部ライブラリの増量を止め、代替(T-SQL/外部処理)へ切替 |
まとめ
Azure SQL Managed Instance で SQLCLR を使い Azure Key Vault を参照する場合、最大の壁は「依存 DLL の登録」と「MI 特有の実行・セキュリティ制約」です。まずは sys.assemblies を軸に依存関係を揃え、ファイルパス投入に頼らず 0x(varbinary)で持ち込める運用を作るのが近道です。一方で、Azure SDK や Newtonsoft のようなライブラリを SQLCLR に持ち込むほど、System 系不足や将来不整合のリスクが跳ね上がります。Key Vault 参照は外部(Azure Functions/アプリ層)へ逃がし、SQL は結果を扱うだけにすると、安定性と保守性の両方が上がります。

コメント