Azure App Configurationを試す(3)ポーリングモデルを使用した動的な構成を試す

はじめに

先日より試してみている機能の一つとして、アプリの設定や機能フラグを一元的に管理するためのサービスとしてAzure App Configurationというものがあります。

docs.microsoft.com

上のドキュメントを読んでもあまり嬉しさがわからないのですが、その嬉しさを理解するためにもSpring Bootアプリで試してみることにしました。今回はポーリングモデルを使用した動的な構成を試してみました。

これまで試した記事は以下の通り。

サンプル

例によってチュートリアルが用意されているのでそれを参考にします。

docs.microsoft.com

例によって幾つか注意点がありましたので、それらを記していきます。

ライブラリのバージョン

com.azure.spring:azure-spring-cloud-appconfiguration-config-webが必要ということなので、build.gradleに追記しておきます。追記する際、com.azure.spring:azure-spring-cloud-appconfiguration-config-webのバージョンを確認しておきます。先日com.azure.spring:azure-spring-cloud-appconfiguration-configを試したときには、2.5.0だったのですが、執筆時点(2022年5月8日)では2.6.0が出ていたので、両方のバージョンを合わせておきます。

mvnrepository.com

実装

実装というほどでもないですが、src/main/resources/boostrap.propertiesに以下の2行を追加します。

spring.cloud.azure.appconfiguration.stores[0].monitoring.enabled=true
spring.cloud.azure.appconfiguration.stores[0].monitoring.triggers[0].key=sentinel

このとき、spring.cloud.azure.appconfiguration.stores[0].monitoring.triggers[0].key=sentinelを記述していない場合はSpring Bootアプリの起動時に以下の例外をはいて起動に失敗します。

Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'spring.cloud.azure.appconfiguration-com.azure.spring.cloud.config.properties.AppConfigurationProperties': Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: Triggers need to be set if refresh is enabled.

spring.cloud.azure.appconfiguration.stores[0].monitoring.triggers[0].keyの値に指定するのはAzure App Configurationの構成エクスプローラーで指定したキーとなります。ちなみに、この値をAzure App Configurationで設定したものとは違うもの(sentinel2とか)に指定した場合、Spring Bootアプリは以下の例外をはいて起動に失敗します。

com.azure.core.exception.ResourceNotFoundException: Setting not found.

試してみる

この状態でAzure App Configuration上の/application/config.messageの値を変更します。変更は3点リーダーをクリックしたら出てくるEditを押した画面にて実施します。

値の変更

これだけだとダメで、spring.cloud.azure.appconfiguration.stores[0].monitoring.triggers[0].keyで指定したキーの値も変更します。

変更が完了後、少し(30秒ぐらい)待ってSpring Bootアプリにアクセスします。最初のアクセス時に/application/config.messageが変更されたことが検出されます。Spring Bootアプリのログにも、変更が検出されRefresh keys changedと出力されていることがわかります。

2022-05-08 11:22:10.507  INFO 7265 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2022-05-08 11:22:10.509  INFO 7265 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 2 ms
2022-05-08 11:27:07.580  INFO 7265 --- [        task-16] c.a.s.c.config.AppConfigurationRefresh   : Configuration Refresh Event triggered by sentinel
2022-05-08 11:27:08.360  INFO 7265 --- [        task-16] b.c.PropertySourceBootstrapConfiguration : Located property source: [BootstrapPropertySource {name='bootstrapProperties-/application/https://xxxxxxxxxxxx.azconfig.io/'}]
2022-05-08 11:27:08.363  INFO 7265 --- [        task-16] o.s.boot.SpringApplication               : No active profile set, falling back to 1 default profile: "default"
2022-05-08 11:27:08.367  INFO 7265 --- [        task-16] o.s.boot.SpringApplication               : Started application in 0.784 seconds (JVM running for 312.312)
2022-05-08 11:27:08.417  INFO 7265 --- [        task-16] o.s.c.e.event.RefreshEventListener       : Refresh keys changed: [config.message]

この段階ではブラウザの表示に変更はありません。

再度Spring Bootアプリにアクセスすることで/application/config.messageの値が反映され、ブラウザの表示が変更されます。

変更が検出される時間を短く/長くする

今回、変更が検出されるためにspring.cloud.azure.appconfiguration.stores[0].monitoring.triggers[0].keyで指定したキーの値の変更後30秒ほど待ちました。これは、com.azure.spring:azure-spring-cloud-appconfiguration-config-webで設定されているspring.cloud.azure.appconfiguration.stores[0].monitoring.refresh-intervalという設定値のデフォルト値が30秒になっているためです。これを短くする/長くするにはsrc/main/resources/boostrap.propertiesにて次のように記述すればよいです。

# 10秒に変更
spring.cloud.azure.appconfiguration.stores[0].monitoring.refresh-interval=10s

上記のように設定すると、10秒で変更が検出されるようになりました。

テストの時Azure App Configurationを参照しないようにする

ポーリングモデルを使用した動的な構成のテストは以上です。ここからは、少し発展的な話題です。

JUnitなどでテストを書いた時、Azure App Configurationを設定しなくてもJUnitのテストは動くようにしたい場合があります。このような場合はsrc/test/resources/bootstrap.propertiesにて以下の記述をすればJUnitのテスト実行時にAzure App Configurationを参照することはなくなります。

spring.cloud.azure.appconfiguration.enabled=false

このときAzure App Configurationで設定する各種設定値を使ったテストをしたい場合は、以下の通り@SpringBootTestproperties属性を使って設定してあげたらテストごとに異なった設定値のテストができてよいかなと思います。

@SpringBootTest(properties = "config.message=hogehoge")
@AutoConfigureMockMvc
public class HelloControllerTest {
    @Autowired
    private MockMvc mockMvc;

    @Test
    public void shouldReturnMessage() throws Exception {
        this.mockMvc.perform(get("/"))
                .andDo(print())
                .andExpect(status().isOk())
                .andExpect(content().string(containsString("Message:hogehoge")));
    }
}

サンプルソース

ここまでのソースです。

github.com

Azure App Configurationを試す(2)

はじめに

先週より試してみている機能の一つとして、アプリの設定や機能フラグを一元的に管理するためのサービスとしてAzure App Configurationというものがあります。

docs.microsoft.com

上のドキュメントを読んでもあまり嬉しさがわからないのですが、その嬉しさを理解するためにもSpring Bootアプリで試してみることにしました。今回は機能フラグを試してみました。

先週試した記事は以下の通り。

サンプル

例によってクイックスタートがあるので、それに従って実装します。

docs.microsoft.com

例によって幾つか注意点がありましたので、それを書いていきます。

ライブラリのバージョン

前回に続き、ライブラリをバージョンを確認します。今回はcom.azure.spring:azure-spring-cloud-feature-management-webを追加したので、そのライブラリの最新バージョンをMaven Repositoryで確認します。

mvnrepository.com

Azure App ConfigurationのFeature Managementのドキュメント

チュートリアルだけを書くのはあまり身につかないので、合わせてドキュメントを見てみます。

microsoft.github.io

今回は試していませんが、いろんな細かい制御ができるようです。

実装

実装としては、FeatureManagerisEnabledAsyncメソッドを使って機能のオン/オフを確認します。

featureManager.isEnabledAsync("Beta").block()

isEnabledAsyncの引数はApp ConfigurationのFeature managerで設定したNameの値を指定します。

Enabledにチェックを入れたらその瞬間に有効化され、保存ボタンとかは無いようです。

アプリは、再起動しないと反映されませんでした。

挙動

App ConfigurationのFeature managerでEnabledにチェックを入れていない状態で起動するとヘッダー部分にあるHomeとPrivacyには何も表示されていません。

App ConfigurationのFeature managerでEnabledにチェックを入れた状態で起動すると、ヘッダー部分にあるHomeとPrivacyにはBetaが表示されています。

ソース

ここまでのソースは以下のものです。

github.com

Azure App Configurationを試す(1)

はじめに

アプリの設定や機能フラグを一元的に管理するためのサービスとしてAzure App Configurationというものがあります。

docs.microsoft.com

上のドキュメントを読んでもあまり嬉しさがわからないのですが、その嬉しさを理解するためにもSpring Bootアプリで試してみることにしました。

サンプル

まずは簡単なサンプルを試します。例によってクイックスタートがあるので、これを試します。

docs.microsoft.com

注意点

自分の中で注意した点を記します。

com.azure.spring:azure-spring-cloud-appconfiguration-configのバージョン

クイックスタートで使っているcom.azure.spring:azure-spring-cloud-appconfiguration-configのバージョンが最新であるかどうか確認します。Maven Repositoryのサイトから最新バージョンを確認しました。この文章を書いている2022年4月時点、2.5.0が最新でした。

mvnrepository.com

Spring Cloud Azure App COnfigurationのドキュメント

クイックスタートを読むだけだとサラッと動いてしまうので、合わせて公式ドキュメントも少しは目を通しておきます。

microsoft.github.io

個人的な注意点としては、

  • bootstrap.propertiesにAzure App Configurationの接続先を設定する
  • Azure App Configurationのキーには使用したいキーの名前の先頭に/applicationをつける

でした。

Azure App Configurationを作成する

Azure App ConfigurationはAzure Portalの検索でappconと入力したら出てきました。

Azure App Configurationのページは多くの場所が英語でした。

クイックスタートでは日本語で書かれていたのでちょっと戸惑いましたが、なんとか設定することができました。

アプリを動かす

クイックスタートにあるアプリを動かします。@ConfigurationProperties@EnableConfigurationPropertiesというものが出てきてちょっとびっくりしましたが、以下のページで大体の概要を掴みました。

www.baeldung.com

あとはクイックスタートの通りに実装して、アプリを動かしてブラウザで動かすと、無事設定を取得できました。

この時点では、アプリを動かしたままAzure App Configuration上での設定値を変えてもアプリを終了するまでは値の変更は反映されませんでした。これは後で動的に変更する機能があるようなのでまた試してみようと思います。

今日はここまで。

ここまでのソース

github.com

Azure App ServiceにてプライベートエンドポイントがBasicでも使えるようになった

ぼけっとネットを見ていたら、以下の記事を見かけました。

techcommunity.microsoft.com

今まではStandardやPremium以上でしか使えなかった仮想ネットワーク接続やプライベートエンドポイントがBasic機能でも使えるというもの。料金ページの反映はまだっぽいです。

f:id:miyohide:20220417160255p:plain

せっかくなので、ちょっと試してみました。ちなみに、プライベートエンドポイントの理解には以下のページが大変参考になりました。

zenn.dev

本題に戻り、プライベートエンドポイントを試します。チュートリアルが用意されているので、それを試します。

docs.microsoft.com

難しいことは何もなく、BasicでWeb Appsをつくります。

f:id:miyohide:20220417160519p:plain

ネットワークページにあるプライベートエンドポイントを手順通りに作成します。

f:id:miyohide:20220417160615p:plain

手元(mac)ではError 403が返りますが、Bastion経由でのWindows Server VMからアクセスするとWeb Appsのアプリに接続できていることが確認できます。

f:id:miyohide:20220417161042j:plain

プライベートエンドポイントの利用にはいくらか料金がかかります。データ転送量によっては注意が必要かもしれません。

azure.microsoft.com

Azure SDK for Javaを使ってAzure Storage Blobにアクセスする(3)Java SDK v12を使ったクライアント暗号化

はじめに

先日から書いているAzure SDK for Javaを使ってAzure Storage Blobにアクセスする方法の続きです。今回は、Azure Storage Blobに保存するデータのクライアント暗号化を試したいと思います。

これまでの記事はこちらを。

Azure Storageでの暗号化

以下のドキュメントにあるように、デフォルトではAzure Storageに対してデータを保存するときに暗号化されるとのこと。これは無効化できないようです。

docs.microsoft.com

今回試したいのは、クライアント暗号化というもの。詳細は以下のドキュメントに記載があります。

docs.microsoft.com

Java SDK v12を使ったサンプルは、記事執筆時点でドキュメント内には書かれていませんでしたので、試してみることにしました。

Azure Key Vaultにおけるキーの作成と権限付与

まずはAzure Key Vaultにてキーを作成します。あまり難しいことはなく、ポータルで簡単に作成します。

docs.microsoft.com

作成したら、前回の記事で作成したエンタープライズアプリケーションに対してアクセス制御(IAM)とアクセスポリシーを設定します。

docs.microsoft.com

アクセスポリシーの権限はとりあえず暗号化操作に関するものに対して全てチェックを入れました。

f:id:miyohide:20220410162618j:plain

Javaを使った実装

実装はAzure Storage Blobs Cryptography client library for Javaを使って行います。

azuresdkdocs.blob.core.windows.net

build.gradleに対して関連ライブラリを追記します。Azure Key Vaultでのキー用のcom.azure:azure-security-keyvault-keysのほかcom.azure:azure-storage-blob-cryptographyを追記します。

dependencies {
    implementation platform('com.azure:azure-sdk-bom:1.2.0')
    implementation 'com.azure:azure-storage-blob'
    implementation 'com.azure:azure-security-keyvault-keys'
    implementation 'com.azure:azure-identity'
    implementation 'com.azure:azure-storage-blob-cryptography'
    implementation 'ch.qos.logback:logback-classic:1.2.11'
    testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2'
    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.2'
}

具体的な実装方法については、Azure Storage Blobs Cryptography client library for JavaのREADMEを参考にします。まずAsyncKeyEncryptionKeyを返すメソッドを作成します。

public KeyVaultKey getKeyVaultKey(String keyName) {
    return (new KeyClient()).getKey(keyName);
}

public AsyncKeyEncryptionKey createAsyncKeyEncryptionKey(String keyName) {
    KeyVaultKey key = this.getKeyVaultKey(keyName);
    AsyncKeyEncryptionKey asyncKeyEncryptionKey = new KeyEncryptionKeyClientBuilder()
            .credential(new DefaultAzureCredentialBuilder().build())
            .buildAsyncKeyEncryptionKey(key.getId())
            .block();
    return asyncKeyEncryptionKey;
}

これを使ってEncryptedBlobClientを作成します。

EncryptedBlobClient encryptedBlobClient = new EncryptedBlobClientBuilder()
                .key(this.keyVaultHelper.createAsyncKeyEncryptionKey("Azure Key Vaultのキーの名前"), KeyWrapAlgorithm.RSA_OAEP.toString())
                .credential(new DefaultAzureCredentialBuilder().build())
                .endpoint("Azure Storageのエンドポイント")
                .containerName("コンテナ名")
                .blobName("Blob名")
                .buildEncryptedBlobClient();

keyメソッドのアルゴリズムについてJava Docには書かれていなかったので何を指定すれば分からなかったのですが、今回はKeyWrapAlgorithm.RSA_OAEP.toString()を指定することでうまく動きました。他にもKeyWrapAlgorithm.RSA_OAEP_256.toString()KeyWrapAlgorithm.RSA1_5.toString()を指定してもうまく動きました。以下のドキュメントをみると良いのかもしれません。

docs.microsoft.com

作成したEncryptedBlobClientupdateStringDataメソッドを使ってデータをBlobとして保存します。

encryptedBlobHelper.updateStringData("This is a sample string2");

この状態でAzure Storage Blobを確認します。

f:id:miyohide:20220410170040j:plain

メタデータencryptiondataというのが書かれています。ドキュメントを見るとこれらのメタデータは削除してはいけないようです。

このデータをダウンロードして中身をみてみると、暗号化されているようです。

f:id:miyohide:20220410170840j:plain

EncryptedBlobClientdownloadBlobDataメソッドを使って復号化された状態でファイルがダウンロードできます。

encryptedBlobHelper.downloadBlobData("ファイル名");

ダウンロードしたファイルを見てみると、復号化できているようです。

f:id:miyohide:20220410171306j:plain

Azure SDK for Javaを使ってAzure Storage Blobにアクセスする(2)

はじめに

先週から書いているAzure SDK for Javaを使ってAzure Storage Blobにアクセスする方法の続きです。今回は、接続文字列ではなくAzure ADを使った認証方法について記します。

前回の記事はこちらを。

Azure SDK for Javaを使ってAzure Storage Blobにアクセスする - miyohide's blog

Azure ADを使った認証

Azure ADを使った認証を実装するには、以下の手順を踏む必要がありました。

  1. サービスプリンシパルを作成して、環境変数AZURE_CLIENT_IDAZURE_TENANT_IDAZURE_CLIENT_SECRETを取得する
  2. Azure Storage Blobに対して適切な権限を付与する
  3. プログラムを修正する

詳細は以下の手順に記されています...が、分かりにくかったので自分の経験を踏まえて補足していきます。

docs.microsoft.com

1. サービスプリンシパルを作成する

サービスプリンシパルを作成するのにaz ad sp create-for-rbacコマンドを使っても良いのですが、なんとなく怖かったのでまずはポータルで作成してみることにします。マイクロソフトのドキュメントには以下のドキュメントがあります。

docs.microsoft.com

これだけだとちょっと分かりにくかったので、SISO Tech.LABの以下のブログ記事と合わせて勉強しました。

tech-lab.sios.jp

サービスプリンシパルを作成したら、アプリケーション(クライアント)IDはAZURE_CLIENT_IDとして、ディレクトリ(テナント)IDはAZURE_TENANT_IDとして、クライアントシークレットはAZURE_CLIENT_SECRET`として環境変数に設定しておきます。

2. Azure Storage Blobに対して適切な権限を設定する

個人的に一番ハマったのがここです。権限として、以下のドキュメントにも書かれているように次のロールが割り当てられている必要があります。

・ストレージ BLOB データ閲覧者、ストレージ BLOB データ共同作成者などのデータ アクセス ロール ・少なくとも Azure Resource Manager 閲覧者ロール

docs.microsoft.com

マスク化していますが、こんな感じで割り当てました。

f:id:miyohide:20220403183453j:plain

3. プログラムを修正する

プログラムの修正は、

  1. com.azure:azure-identityを依存ライブラリとして追加する
  2. BlobServiceClientの作成時にcredentialメソッドを使う

の二つを実施しました。

まず、build.gradlecom.azure:azure-identityを追加します。こんな感じです。

dependencies {
    implementation platform('com.azure:azure-sdk-bom:1.2.0')
    implementation 'com.azure:azure-storage-blob'
    implementation 'com.azure:azure-identity'
    implementation 'ch.qos.logback:logback-classic:1.2.11'
    testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2'
    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.2'
}

次にBlobServiceClientの作成です。これまでは以下のように実装していました。

BlobServiceClient blobServiceClient = new BlobServiceClientBuilder()
            .connectionString(connectionString)
            .buildClient();

これを次のように実装します。

BlobServiceClient blobServiceClient = new BlobServiceClientBuilder()
                .endpoint(storageAccountURL)
                .credential(new DefaultAzureCredentialBuilder().build())
                .buildClient();

ポイントは以下2点です。

  • connectionStringメソッドの代わりにcredentialメソッドを使う
    • 認証に必要なデータはDefaultAzureCredentialBuilderメソッドが環境変数AZURE_CLIENT_IDAZURE_TENANT_IDAZURE_CLIENT_SECRETを使っていい感じに作ってくれるっぽい
  • endpointメソッドを使って接続先を指定する
    • connectionStringに接続先が含まれていたが、connectionStringを使わなくなったため、endpointメソッドを使ってAzure Storage Blobの接続先を指定する

ここまで設定することで無事Azure ADを使った認証を行うことができました。

Azure SDK for Javaを使ってAzure Storage Blobにアクセスする

はじめに

Azure Storage Blobに対してJavaプログラムからアクセスする方法についてちょっと調査をしていたら色々とハマったので、備忘録として記します。

Azure SDK for Java

Azure SDK for Javaのドキュメントは以下にあります。

docs.microsoft.com

Azure Storage Blobに対するJavaプログラムからのアクセスについては以下にサンプルがあります。

docs.microsoft.com

Azure SDK for Javaにはv8系とv12系の二つがあるようです。Javaのバージョンとはあまり関係がない?みたいで、今はv12系を使うようです。

ここに書いてあることで多くのことを満たせるかなと思います。以下では実際に調査をしてハマったことを記します。

BOMを使う

Azure SDK for Javaではさまざまなライブラリが提供されているのですが、それらの依存関係やバージョンを統一するためにBOMが提供されています。

devblogs.microsoft.com

Gradleでは5.2からBOMをサポートしているようです。

docs.gradle.org

build.gradle上で以下のように書けばOKです。

dependencies {
    implementation platform('com.azure:azure-sdk-bom:1.2.0')
    implementation 'com.azure:azure-storage-blob'
}

BOMを使うとv12系が利用されるようでした。

ログを出力する

実行した際、以下のメッセージが出力されることがあります。

SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.

これはLogbackなどのロギングライブラリを導入しておけば出力されません。Logbackを使う場合、Gradleに以下の記述をします。

dependencies {
    implementation platform('com.azure:azure-sdk-bom:1.2.0')
    implementation 'com.azure:azure-storage-blob'
    implementation 'ch.qos.logback:logback-classic:1.2.11'  // ← 追加
}

Logbackの設定をsrc/main/resources/logback.xmlに書きます。内容は以下のような感じ。

<configuration>
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>
                %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
            </pattern>
        </encoder>
    </appender>
    <root level="info">
        <appender-ref ref="STDOUT" />
    </root>
</configuration>

このようにすることで、以下のようなログが出力されました。

2022-03-27 15:38:38.603 [main] INFO  c.a.c.i.jackson.JacksonVersion - Package versions: jackson-annotations=2.13.1, jackson-core=2.13.1, jackson-databind=2.13.1, jackson-dataformat-xml=2.13.1, jackson-datatype-jsr310=2.13.1, azure-core=1.26.0, Troubleshooting version conflicts: https://aka.ms/azsdk/java/dependency/troubleshoot

詳細は以下にも書かれています。

docs.microsoft.com

JavaDoc

細かいメソッドなどを調べるためにJavaDocは欠かせません。以下のページにてAzure SDK for Javaとして提供しているサービスごとのJavaDocのリンクがまとまっています。

azure.github.io

Azure Storage Blobは以下のページから辿れるJavaDocを参照すると良いかと思います。

azure.github.io

CONTENT-TYPEなどを設定する

上記のサンプルをもとに実行するとCONTENT-TYPEapplication/octet-streamになっていました。

これを修正するためには色々とコードを書く必要があります。

blobClientの型をBlobClientからBlockBlobClientにします。

BlockBlobClient blobClient = blobContainerClient.getBlobClient(blobName).getBlockBlobClient();

CONTENT-TYPEを設定するにはBlobHttpHeadersインスタンスを作成します。

BlobHttpHeaders blobHttpHeaders = new BlobHttpHeaders().setContentDisposition("attachment").setContentType("text/plain");

あとはBlockBlobClientuploadWithResponseメソッドを使ってアップロードします。

以下のような実装になりました(package文やimport文は省略)。

public class BlobHelper {
    private BlobServiceClient blobServiceClient;
    private BlobContainerClient blobContainerClient;

    public BlobHelper() {
    }

    public void createBlobServiceClient(String connectionString) {
        // blobに接続するためのクライアントを作成する
        this.blobServiceClient = new BlobServiceClientBuilder()
                .connectionString(connectionString)
                .buildClient();
    }

    public void createBlobContainerClient(String containerName) {
        // コンテナーにアクセスするクライアントを作成する
        this.blobContainerClient = this.blobServiceClient
                .getBlobContainerClient(containerName);
        if (!this.blobContainerClient.exists()) {
            this.blobContainerClient.create();
        }
    }

    void createBlobWithData(String blobName, String blobContents) throws NoSuchAlgorithmException {
        // Blobを作成する
        BlockBlobClient blobClient = blobContainerClient.getBlobClient(blobName).getBlockBlobClient();
        // メタデータの設定
        Map<String, String> blobMetadata = Collections.singletonMap("myblobmetadata", "sample");
        // 各種ヘッダーの設定
        BlobHttpHeaders blobHttpHeaders = new BlobHttpHeaders().setContentDisposition("attachment")
                .setContentType("text/plain");
        byte[] md5 = MessageDigest.getInstance("MD5").digest(blobContents.getBytes(StandardCharsets.UTF_8));
        InputStream dataStream = new ByteArrayInputStream(blobContents.getBytes(StandardCharsets.UTF_8));
        blobClient.uploadWithResponse(dataStream, blobContents.length(), blobHttpHeaders, blobMetadata, null, md5, null, null, null);
    }
}

以下のような結果となりました。無事、CONTENT-TYPEtext/plainになっています。他にも各種メタデータも設定されています。

ソース

この時点のソースは以下です。

github.com