【Azure】Blob を読み込む SSIS を Data Factory で実行する！【SSISDBなし】

# はじめに

どうも、ATL のヴィリアスです。

先日、とある業務にて「Data Factory のパイプラインで『Blob コンテナーからファイルを読み込み、データベースに反映させる SSIS パッケージ』を実行する」という
処理を実装する機会がありました。

SSIS は以前から触れていたのですが、Data Factory や Blob などを扱ったことがなく
怒涛のエラーとの格闘によって実装に長時間を費やしてしまったので、今後の時短と同じようなことを実装したい方に向けて設定方法などのメモを残しておきます。

# 前提情報

## SSIS の構成

当初は SSIS と Blob 間の接続検証をしていたので、本当に簡易的な構成になっています。

### 制御フロー

[データフロー] タスクがあるだけ。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2124150/e327f93c-8fdc-4d29-22d0-ff016f814488.png)

### データフロー

[Flexible File Source] タスクがあるだけ。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2124150/5f43745c-6535-3605-1a4a-b4845d512bbf.png)

:::note info
記事の本筋とは少しズレるのですが、
Blob をソースとして扱うタスクとして [Flexible File Source] の他に [Azure Blob Source] があります。
[Flexible File Source] では、読み込む Blob の文字コードを設定できるので今回採用しています。

([Azure Blob Source] の場合、Shift-JISのファイルの日本語を読み込もうとすると、文字化けを回避できない)
:::

## よく出たエラーメッセージ

エラーメッセージで検索をかけて SSIS と Blob 間のエラー解決方法を探る方がいるかもしれないので、ペタリ。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2124150/96c13ac4-2e79-c0c7-115e-6d7dc2d5f393.png)

> [Flexible File Source] エラー: Microsoft.DataTransfer.Common.Shared.HybridDeliveryException: Blob operation Failed. ContainerName: *\<ここは Blob コンテナーのエンドポイント>*, path: *\<ここはコンテナー内のファイルパス>*. ---> Microsoft.WindowsAzure.Storage.StorageException: リモート サーバーがエラーを返しました: (400) 要求が不適切です ---> System.Net.WebException: リモート サーバーがエラーを返しました: (400) 要求が不適切です
	   場所 Microsoft.WindowsAzure.Storage.Shared.Protocol.HttpResponseParsers.ProcessExpectedStatusCodeNoException[T](HttpStatusCode expectedStatusCode, HttpStatusCode actualStatusCode, T retVal, StorageCommandBase\`1 cmd, Exception ex)
	   場所 Microsoft.WindowsAzure.Storage.Blob.CloudBlobClient.<>c__DisplayClass13.<GetBlobReferenceImpl>b__12(RESTCommand\`1 cmd, HttpWebResponse resp, Exception ex, OperationContext ctx)
	   場所 Microsoft.WindowsAzure.Storage.Core.Executor.Executor.EndGetResponse[T](IAsyncResult getResponseResult)
	   --- 内部例外スタック トレースの終わり ---
	   場所 Microsoft.WindowsAzure.Storage.Core.Executor.Executor.EndExecuteAsync[T](IAsyncResult result)
	   場所 Microsoft.WindowsAzure.Storage.Core.Util.AsyncExtensions.<>c__DisplayClass2`1.<CreateCallback>b__0(IAsyncResult ar)
	--- 直前に例外がスローされた場所からのスタック トレースの終わり ---
	   場所 System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
	   場所 System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
	   場所 Microsoft.DataTransfer.ClientLibrary.BlobMultipartReadable.<>c__DisplayClass32_0.<GetMetadata>b__0()
	   場所 Microsoft.Practices.TransientFaultHandling.RetryPolicy.<>c__DisplayClass1.<ExecuteAction>b__0()
	   場所 Microsoft.Practices.TransientFaultHandling.RetryPolicy.ExecuteAction[TResult](Func\`1 func)
	   場所 Microsoft.DataTransfer.Runtime.AzureBlobConnectorHelper.ExecuteBlobOperationWithRetry(String operationName, Action action, Guid jobId, Guid activityId, CancellationToken cancelToken, IDictionary\`2 properties)
	   場所 Microsoft.DataTransfer.ClientLibrary.BlobMultipartReadable.GetMetadata()
	   --- 内部例外スタック トレースの終わり ---
	   場所 Microsoft.DataTransfer.ClientLibrary.BlobMultipartReadable.GetMetadata()
	   場所 Microsoft.DataTransfer.ClientLibrary.BlobMultipartReadable.get_Length()
	   場所 Microsoft.DataTransfer.ClientLibrary.MultipartReadSource.OpenStream(IMultipartReadable readable)
	   場所 Microsoft.DataTransfer.ClientLibrary.MultipartReadSource.Read()
	   場所 Microsoft.SqlServer.IntegrationService.ExtensibleFileCommon.DataTransferClientHelper.GetTabularReadResult(IDictionary\`2 sourceProperties, Guid transferId, Guid activityId)
	   場所 Microsoft.SqlServer.IntegrationService.ExtensibleFileComponents.Source.ExtensibleFileSource.GetExternalTabularSourceReader()
	   場所 Microsoft.SqlServer.IntegrationService.ExtensibleFileComponents.PipelineComponentSource.TransferToOutputBuffers(Int32 outputs, Int32[] outputIDs, PipelineBuffer[] buffers)

# SSIS

## 準備: Azure Feature Pack

SSIS で Azure リソース (今回は Blob) と接続するには "**Azure Feature Pack**" をインストールする必要があります。

https://learn.microsoft.com/ja-jp/sql/integration-services/azure-feature-pack-for-integration-services-ssis

### インストール

SQL Server のバージョンにあったものをインストールします。
私の場合は SQL Server 2017 ver. をインストールしました。


### TLS 1.2 の設定

Azure Feature Pack は **.Net Framework 4.0** を使用しています。

Blob と接続するには、[TLS 1.2 が必要になる](https://learn.microsoft.com/ja-jp/azure/storage/common/transport-layer-security-configure-migrate-to-tls2) のですが、.Net Framework 4.0 ではデフォルトで TLS 1.2 をサポートしていません。
そのため、**レジストリを変更して TLS 1.2 を有効**にしていきます。

(このことは公式ドキュメントにも記載されているのですが、私は完全にスルーしてしまってちょっと詰まりました)

https://learn.microsoft.com/ja-jp/sql/integration-services/azure-feature-pack-for-integration-services-ssis?view=sql-server-ver16#use-tls-12

https://andybrownsword.co.uk/2024/10/01/solving-2-common-errors-with-the-azure-feature-pack/

#### `ScheUseStrongCrypto`

公式ドキュメントから引用します。

> 次の 2 つのレジストリキーの下に SchUseStrongCrypto という名前の REG_DWORD 値をデータ 1 と共に追加します。
> 1. `HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\.NETFramework\v4.0.30319`
> 2. `HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework\v4.0.30319`

[WOW6432Node] の方は、32bit ver. ですね。

私の環境は 64bit ver. なので、2. だけでも動作しましたが、
ドキュメントのとおりにどちらも追加しました。

こんな感じに追加できれば OK です。
※ `SystemDefaultTlsVersions` については後述。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2124150/bdded1fd-9a8a-e3c7-0706-040be8e7b211.png)

#### `SystemDefaultTlsVersions`

私の環境では、先ほどの `ScheUseStrongCrypto` を追加しただけでは TLS 1.2 が有効になりませんでした。
そこで [Perplexity](https://www.perplexity.ai) 先生に質問してみると…

> **クライアント環境のTLS設定確認**
> Windowsレジストリの設定: TLS 1.2を有効にするために、Windowsのレジストリ設定を確認します。
> 具体的には、以下のキーを確認し、`SystemDefaultTlsVersions`を1に設定します。
>
> `HKLM\SOFTWARE\Microsoft\.NETFramework\v4.0.30319`

お、なんかそれっぽい情報！

実際に`SystemDefaultTlsVersions` で検索をかけるとこんな記事がヒット。

https://learn.microsoft.com/ja-jp/dotnet/framework/network-programming/tls#systemdefaulttlsversions>

> `HKEY_LOCAL_MACHINE\SOFTWARE\[Wow6432Node\]Microsoft\.NETFramework\<VERSION>: SystemDefaultTlsVersions` レジストリ エントリの値は DWORD 型です。
> ~
> **アプリの対象が .NET Framework 4.6.1 以前のバージョンの場合、このキーの既定値は 0 です。 その場合は、値を 1 に明示的に設定する必要があります。**

**~~(Azure Feature Pack が .Net Framework 4.0 を使ってるんだから、そっちのドキュメントにも明記してくれ！)~~**

そんなわけで、こんな感じにレジストリを変更できれば OK です。
(さっきと同じスクリーンショット)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2124150/bdded1fd-9a8a-e3c7-0706-040be8e7b211.png)


### 補足: SSIS から Blob に接続できないときに確認すること

レジストリを変更しても、Blob と接続できないことがあるようです。
その場合、ネットワーク周りの設定を見直すと良いかもしれないです。

https://stackoverflow.com/questions/68039764/azure-blob-source-400-bad-request-when-using-azure-blob-source-in-ssis-to-pull

## 接続マネージャー

ここから SSIS パッケージ内の設定値を記載していきます。

Azure Feature Package をインストールすると、選択肢として Azure 系の接続マネージャーが追加されます。
今回は Blob に接続するので **AzureStorage** を選択しました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2124150/7e5f0db7-cd27-29b0-a8a6-a1501acf5ec9.png)

設定ウィンドウが表示されるので設定します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2124150/a581286e-f9d0-6189-c277-51766dcdc5e3.png)

* **Service**
    * Blob Storage
* **Account name**
    * ※ 任意 (ストレージアカウントのリソース名)
* **Authentication**
    * ※ 任意 (今回は AccessKey を選択しました)
* **Account key**
    * ※ Azure Portal で確認します。
(Azure Portal にて、対象のストレージアカウント > 左メニューの [アクセスキー])
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2124150/ad5c9e16-8248-e135-957b-5b32f94eb089.png)

* **マネージドID**
    * 今回はチェックしていません。
私の場合、チェックすると ADF のパイプラインでエラーになりました。
正しく設定すればエラーにならないと思います。(未検証)

https://learn.microsoft.com/ja-jp/sql/integration-services/connection-manager/azure-storage-connection-manager

* **Environment**
    * Microsoft Azure Default

設定できたら、ウィンドウ左下の [Test Connection] で接続テストをします。
成功すれば OK です。失敗した場合、設定値を見直します。

## プロジェクト

今回は接続マネージャーで、ストレージアカウントのアクセスキー (機微な情報) を入力したので、その情報を保持するためにプロジェクトを保護します。
**保護しない場合、機微な情報がプロジェクト内に保存されません。**

ソリューションエクスプローラーにて、プロジェクトを右クリック、[プロパティ] を選択します。
[セキュリティ] グループ内に **ProtectionLevel** (保護レベル) があるので、
以下のうち、いずれかを選択します。

* `EncryptSensitiveWithUserKey` (機微なデータをユーザー キーで暗号化する)
* `EncryptSensitiveWithPassword` (機微なデータをパスワードで暗号化する)
* `EncryptAllWithPassword` (すべてのデータをパスワードで暗号化する)

今回は、`EncryptSensitiveWithPassword` を選択しました。
選択後、任意のパスワードを入力し、設定完了です。

:::note warn
以下の2つは、Data Factory に配置する SSIS パッケージの保護レベルとしては使用できないようです。
* `DontSaveSensitive` (機微なデータを保存しない) 
→ アクセスキーをパッケージ内に保持できない。
* `EncryptAllWithUserKey` (すべてのデータをユーザー キーで暗号化する) 
→ そもそも Data Factory でサポートされていない。
:::
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2124150/dfa5bd2e-e1aa-533a-d320-b1e8d2c8a4aa.png)

https://learn.microsoft.com/ja-jp/sql/integration-services/security/access-control-for-sensitive-data-in-packages

## パッケージ (.dtsx)

プロジェクトの ProtectionLevel を変更後、「パッケージの保護レベルも変更してね」と警告メッセージが表示されるので従います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2124150/dc6b85c5-1802-3117-bf18-979ea8795efe.png)

ソリューションエクスプローラーにて、SSIS パッケージ (例: Package.dtsx) をダブルクリックします。
そうするとプロパティが開くので、プロジェクトと同様の ProtectionLevel を選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2124150/a16659c2-57ac-36ee-5807-1b2b13252125.png)

**パッケージの ProtectionLevel を変更後、その上部にある PackagePassword の設定をお忘れなく！**

ここまで設定できたら、SSIS パッケージを Visual Studio 上で実行し、Blob に問題なくアクセスができたら OK です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2124150/6732ace5-7aba-a7f1-79c3-bfb9f2ae5741.png)


# Data Factory

続いて、Data Factory の パイプラインにパッケージを配置していきます。

公式ドキュメントにかなり詳しく手順が記載されています。(ちょっと読みづらい (主観))

https://learn.microsoft.com/ja-jp/azure/data-factory/how-to-invoke-ssis-package-ssis-activity?tabs=data-factory

なお、Azure-SSIS IR (統合ランタイム) は事前に作成しておきます。
以降、特に記載のない設定項目については任意の値で OK です。

## パイプライン

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2124150/b00767e3-c390-8357-dfda-39a6bf03d065.png)

### [SSIS パッケージの実行] タスク

#### [設定]

* **パッケージの場所**
    * 埋め込みパッケージ
※ SSISのプロジェクトフォルダ内に含まれている Package.dtsx をアップロードします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2124150/6b8035bd-6b0a-8566-25c5-6e546b439314.png)
* **暗号化用パスワード**
    * ※ ProtectionLevel (保護レベル) で `EncryptSensitiveWithPassword` に設定したときのパスワード
				
#### [SSIS パラメーター], [接続マネージャー] , [プロパティのオーバーライド]
※ 今回は SSIS パッケージの ProtectionLevel を `EncryptSensitiveWithPassword` にしているので特段設定は不要です。
`EncryptSensitiveWithUserKey` にした場合、いずれかのタブにて設定が必要になるみたいです。(先述の公式ドキュメント参照)

ここまで設定すれば、Data Factory 内の [デバッグ] からパイプラインを実行し、
問題なく処理が完了することを確認できるはずです！

# おわりに
パイプライン実行エラー時にエラーメッセージを返してくれるのは良いのですが、その内容が「リモートサーバーがエラーを返しました。(400) 要求が不適切です」みたいに具体的な原因を出力してくれないので、解決するまで業を煮やしまくりました。(エラーメッセージがふてほど)

今回いろいろと調べられたので、次の機会があればスムーズに実装できる……といいなと思っています。

それではまた、次の記事で。