前回はAmazon S3の概要を説明した上でバケットを作成し、ファイルをアップロードしました。今回はアプリケーションにおけるS3アクセス方式について説明し、前回アップロードしたファイルに「Spring Cloud AWS」を使ってアクセスするアプリケーションを実装していきます。

アプリケーションでのS3アクセス方式

EC2やECSなどをベースに構築したスケールイン/スケールアウト可能なアプリケーション実行環境は、揮発性の(インスタンスの終了と共にデータが失われる)ものです。そのため、従来、アプリケーションサーバのファイルシステムへアクセスして行なっていた処理はS3などの共通ストレージに対して行うほうが良いでしょう。

アプリケーションからS3へファイルをアップロード/ダウンロードする場合、大きく分けて次の2つの方法があります。

●VPC内にあるEC2やECSなどで実行されているアプリケーション内からAmazonSDK(SoftwareDevelopersKit:要はライブラリ)を使ってS3へアクセスする方法

VPC内のアプリケーションからSDKを使ってS3へアクセスする方法

●AWS STS(SecurityTokenSerivce)を使って作成する、一時的に有効な署名付きURLやクレデンシャルキーなどを使って、クライアントから直接S3へアクセスする方法

一時的に有効な署名付きURLやクレデンシャルキーを使って、クライアントからダイレクトアクセスする方法

アプリケーションから直接アクセスするやり方は、後述するSpring Cloud AWSなどを使用して、比較的容易に実装することが可能です。反面、取り扱うファイルの量やサイズが大きくなる場合、アプリケーション自体に負荷がかかってしまうので、2つ目の方法のようにクライアントから直接S3へアクセスする方法のほうが望ましくなります。

ただし、ダイレクトアクセス方式にはデメリットもあります。AWS上での一時的な認証情報取得やCORS設定、PostPolicyに施す署名のエンコード処理などの実装が複雑になるのです。

以下にポイントを整理するので、取り扱うユースケースやファイルの特性に応じて、処理を使い分けてください。

  VPC内のアプリケーションからアクセス クライアントからダイレクトアクセス
メリット ・実装が容易
・VPC内でアクセスが完結するのでセキュリティが担保される
・アプリケーションサーバに負荷がかからない
・直接S3へアクセスするため、処理が高速
デメリット ・ファイルサイズが大きいとサーバに負荷がかかる ・セキュリティ設定やアプリケーション実装が複雑
ユースケース ・テキストファイルなど比較的サイズが小さいものを扱う処理 ・画像や動画などの比較的サイズが大きいものを扱う処理
・クライアントから大量にファイルをやり取りする処理

また、前回も説明した通り、S3は各リージョンごとに提供されるストレージサービスです。そのため、上記の構成のようにリージョンをまたがない、複数のアベイラビリティゾーンで構成されるアプリケーションであれば、いずれの方式でも特に問題はありません。

しかし、同じアプリケーションを複数のリージョン(例えば、東京と大阪)で運用する場合は、アクセスしたアプリケーションのリージョンによってファイルの有無に差が生じるため、クロスリージョンレプリケーションの適用を検討しましょう。

設定クラスの実装

Spring Cloud AWSは第12回でも説明した通り、AWSのサービスに対してサポートを提供していますが、S3に関しては、org.springframework.core.io.ResourceLoaderを使用したBucketアクセス手段を提供しています。

ResourceLoaderはSpringによって提供されているリソースを抽象化したインタフェースです。アプリケーション上の実装で、クラスパスやファイルシステム上などリソースの保存先の実体を意識せずに済みます。

S3オブジェクトに対しては、AmazonSDK上で以下のような操作がサポートされていますが、それらの内Spring Cloud AWSが提供しているオブジェクト操作は以下の通りです。

S3の操作 詳細 Spring Cloud AWSのサポート
GET オブジェクト全体、もしくはオブジェクトの一部を取得する。バイトレンジの指定が可能
List Key プレフィックスと区切り記号でオブジェクトキーを一覧表示する
PUT オブジェクトのアップロード及び更新を行う
DELETE オブジェクトの削除を行う ×

その他、バケットの操作やダイレクトアップロード時に利用するAWS STSへ一時認証情報の取得処理などは、AmazonSDKのAPIを直接実行する必要があることに留意してください。

なお、Spring Cloud AWSを使ったアプリケーションの実装方法は、NTTが提供する Macchinetta Frameworkのガイドライン「ファイル管理」でよく整理されています。必要に応じて、こちらも参照してください。

本連載で実際に作成するアプリケーションは、GitHub上にコミットしています。以降に記載するソースコードでは、import文など本質的でない記述を省略している部分があるので、実行コードを作成する際は、必要に応じて適宜GitHubにあるソースコードも参照してください。

Spring Cloud AWSを使用するには、まず、Mavenプロジェクトのpom.xmlでspring-cloud-starter-awsのライブラリを定義します。また、ファイルを画面上からアップロード/ダウンロードする処理を実装するためにspring-boot-starter-webおよびspring-boot-starter-tymeleafを追加し、ファイルの入出力処理やモデルオブジェクトを簡素化するためにApacheのcommons-ioライブラリやLombokライブラリを追加してください(以下参照)。

<dependencies>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
  <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-aws</artifactId>
  </dependency>
  <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
  </dependency>
</dependencies>

なお、thymeleafテンプレートエンジンに関する説明は省略しますが、必要に応じて、Thymeleaf公式ドキュメントやMacchinetta Frameworkの「テンプレートエンジン」の項目を参照してください。

それでは、アプリケーションの実装に進みます。今回作成するアプリケーションの構成は以下の通りです。

コンポーネント 説明 必須
WebApp SpringBootアプリケーションを実行する起動クラス
MvcConfig SpringMVCの設定を行うクラス
S3Config S3への接続に関する設定クラス
SampleController ダウンロード/アップロードを呼び出し、画面もしくはJSONレスポンスを返却するController
XxxxxHelper ダウンロード/アップロード各処理を実装したクラス。なお、HelperとはTERASOLUNAのガイドライン「Helper」と同様、Controllerの複雑化を避けるために補助的に作成するクラス

以降、各々のクラスについて解説を進めていきます。事前にAWSコンソールでアプリケーション用のユーザーを作成し、AWS公式ページ「設定ファイルと認証情報ファイル」を参考にユーザーホームフォルダに.awsディレクトリを作成し、「credential」というファイル名で、CSV形式の認証キーに記載しているユーザー認証情報を保存してください。形式は以下の通りです。

[default]
aws_access_key_id=XXXXXXXXXXXXXXXX
aws_secret_access_key=YYYYYYYYYYYYYYYYYYYYYYYYYYYYY

また、上記のクレデンシャルを持つユーザーは、S3への接続権限を持つ必要があります。以下のように、AWSコンソールで「IAM」サービスメニューから、ユーザーにS3のアクセス権限を付与しておいてください。

ユーザーにS3のアクセス権限を付与

それでは、実装していくクラスについて説明していきましょう。まず最初は、SpringBoot起動クラスと各種設定クラスです。

@SpringBootApplicaitonアノテーションが付与された起動クラスは、同一パッケージにある@Configurationアノテーションが付与された設定クラスと、設定クラス内で@ComponentScanされたパッケージにあるクラスを読み取ります。今回は、目的に応じて以下の2つに分類して設定クラスを作成します。

  • SpringMVCの設定クラス:MvcConfigクラス
  • S3の接続を行う設定クラス:S3Configクラス

設定クラスは必ずしも複数である必要はなく、1つにまとめても動作上問題ありません。ただし、クラス名と役割を対応づけて作成しておいたほうが、後々設定内容を混乱することなく、クラス名から識別できるのでベターでしょう。

package org.debugroom.mynavi.sample.aws.s3.config;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class WebApp {
    public static void main(String[] args) {
        SpringApplication.run(WebApp.class, args);
    }
}

同一パッケージに配置するMvcConfigクラスでは、HTMLやCSSなどの静的リソースのURLと実際のリソースの物理配置の対応づけの定義をResourceHandlerRegistryに追加し、Controller、Helperクラスを読み取るために、ComponentScanアノテーションに該当のパッケージを指定しておきます。また今回は、S3から取得した画像ファイル、およびテキストデータを返却するので、必要なMessageConverterを追加します。

package org.debugroom.mynavi.sample.aws.s3.config;

// omit

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.http.converter.BufferedImageHttpMessageConverter;
import org.springframework.http.converter.ByteArrayHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@ComponentScan("org.debugroom.mynavi.sample.aws.s3.app.web")
@Configuration
public class MvcConfig implements WebMvcConfigurer {

   @Override
   public void addResourceHandlers(ResourceHandlerRegistry registry) {
       registry.addResourceHandler("/static/**")
          .addResourceLocations("classpath:/static/");
   }

   @Override
   public void configureMessageConverters(List> converters) {
       converters.add(new BufferedImageHttpMessageConverter());
       converters.add(byteArrayHttpMessageConverter());
       converters.add(new StringHttpMessageConverter());
       converters.add(new MappingJackson2HttpMessageConverter());
   }

   @Bean
   public ByteArrayHttpMessageConverter byteArrayHttpMessageConverter() {
       ByteArrayHttpMessageConverter arrayHttpMessageConverter = new ByteArrayHttpMessageConverter();
       arrayHttpMessageConverter.setSupportedMediaTypes(getSupportedMediaTypes());
       return arrayHttpMessageConverter;
   }

   private List getSupportedMediaTypes() {
       List list = new ArrayList();
       list.add(MediaType.IMAGE_JPEG);
       list.add(MediaType.IMAGE_PNG);
       list.add(MediaType.APPLICATION_OCTET_STREAM);
       return list;
   }

}

次に、S3への接続情報を設定するS3Configですが、AmazonSDKで提供されているAmazonS3をBean定義するだけでOKです。

package org.debugroom.mynavi.sample.aws.s3.config;

import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class S3Config {

    @Bean
    public AmazonS3 amazonS3(){
        return AmazonS3ClientBuilder.standard().build();
    }

}

よくWebの古い記事でAccessKeyなどをプロパティファイルに設定している例を見かけますが、その方法はAWSでは非推奨となっています。本番環境では、EC2やECSが持つインスタンスプロファイルから、開発環境は~/.awsフォルダに保存してあるCredential情報などからアクセスキーを取得するのがベターです。

詳細については、第17回の囲み記事「AmazonDynamoDBClientBuilderについて」でも記載していますが、Spring Cloud AWSは、com.amazonaws.auth.DefaultAWSCredentialsProviderChainを利用して、AWSの認証情報を取得します。DefaultAWSCredentialsProviderChainでは、以下の順序で情報を取得していく実装になっています。

  1. 環境変数AWS_ACCESS_KEY_IDとAWS_SECRET_ACCESS_KEY
  2. システムプロパティaws.accessKeyIdとaws.secretKey
  3. ユーザーのAWS認証情報ファイル
  4. AWSインスタンスプロファイルの認証情報

アクセスキーなどは設定せずに、開発端末下だけで有効となる「application-dev.yml」で、「cloud.aws.credentials.profile」に空白を、「instaceProfile」をfalseに設定しておきましょう。また、リージョンの自動検出をOFFにするよう、「cloud.aws.region.auto」に「false」を、今回アプリケーションが実行されるリージョンを「cloud.aws.region.static」に手動設定しておきます。

applicaiton-dev.yml

cloud:
  aws:
    credentials:
      profileName:
      instanceProfile: false
    region:
      auto: false
      static: ap-northeast-1

なお、アプリケーション起動時に、Spring Cloud AWSのAuto-configurationであるContextStackAutoConfigurationによって、アプリケーションのスタック名自動検出が有効になり、AWS CloudFormationのスタックが見つからない場合、AmazonServiceExceptionが発生してアプリケーションが起動しなくなります。これを回避するには、application.ymlで「cloud.aws.stack.auto = false」を設定し、スタックの自動検出を無効化します。

application.yml

cloud:
  aws:
    stack:
      auto: false

以上で、設定クラスの実装まで完了しました。次回は、Spring Cloud AWSを使用したアプリケーションのS3アクセス処理実装を進めていきます。

著者紹介


川畑 光平(KAWABATA Kohei) - NTTデータ 課長代理

金融機関システム業務アプリケーション開発・システム基盤担当を経て、現在はソフトウェア開発自動化関連の研究開発・推進に従事。

Red Hat Certified Engineer、Pivotal Certified Spring Professional、AWS Certified Solutions Architect Professional等の資格を持ち、アプリケーション基盤・クラウドなどさまざまな開発プロジェクト支援にも携わる。2019 APN AWS Top Engineers & Ambassadors選出。

本連載の内容に対するご意見・ご質問は Facebook まで。