前回はAmazon S3の概要を説明した上でバケットを作成し、ファイルをアップロードしました。今回はアプリケーションにおけるS3アクセス方式について説明し、前回アップロードしたファイルに「Spring Cloud AWS」を使ってアクセスするアプリケーションを実装していきます。
アプリケーションでのS3アクセス方式
EC2やECSなどをベースに構築したスケールイン/スケールアウト可能なアプリケーション実行環境は、揮発性の(インスタンスの終了と共にデータが失われる)ものです。そのため、従来、アプリケーションサーバのファイルシステムへアクセスして行なっていた処理はS3などの共通ストレージに対して行うほうが良いでしょう。
アプリケーションからS3へファイルをアップロード/ダウンロードする場合、大きく分けて次の2つの方法があります。
●VPC内にあるEC2やECSなどで実行されているアプリケーション内からAmazonSDK(SoftwareDevelopersKit:要はライブラリ)を使ってS3へアクセスする方法
●AWS STS(SecurityTokenSerivce)を使って作成する、一時的に有効な署名付きURLやクレデンシャルキーなどを使って、クライアントから直接S3へアクセスする方法
アプリケーションから直接アクセスするやり方は、後述する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のアクセス権限を付与しておいてください。
それでは、実装していくクラスについて説明していきましょう。まず最初は、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では、以下の順序で情報を取得していく実装になっています。
- 環境変数AWS_ACCESS_KEY_IDとAWS_SECRET_ACCESS_KEY
- システムプロパティaws.accessKeyIdとaws.secretKey
- ユーザーのAWS認証情報ファイル
- 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 まで。