AWS S3をJavaで

JavaでAWS S3を試す

AWS S3のパフォーマンス測定を行うため、JavaでS3の操作をしてみました。

準備

事前準備として以下のSDKやらライブラリを事前に用意します。

Java 用 AWS SDKは読んで時のごとく、JavaでAWSを操作するためのSDKです。
Apache HttpComponents Clientは↑を使うために必要です。

次に必要な情報として、AWSのセキュリティ証明書のページでaccess key IDとSecret access keyを用意しておきます。

最後にAWSのManagement ConsoleでBucketを作っておきます。
Bucketはそう繰り返し作るものでもないので、Javaでやらなくても良いでしょ?

Bucketリストを取得

まずは接続テストを兼ねてBucketリストを取得してみます。
以下のようにS3Test.javaを書きます。

package s3test;

import com.amazonaws.auth.PropertiesCredentials;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.model.Bucket;

public class S3Test {

  static void getList(AmazonS3 s3){
    for(Bucket bucket:s3.listBuckets()){
      System.out.println(bucket.getName());
    }
  }
  
  public static void main(String[] args) {
    
    System.out.println("START");
    AmazonS3 s3 = null;
    try {
      s3 = new AmazonS3Client(new PropertiesCredentials(
            S3Test.class.getResourceAsStream("AwsCredentials.properties")));
    }catch(Exception e){
      System.err.println(e.getMessage());
      System.exit(1);
    }
    
    System.out.println("======= Bucket List ========");
    getList(s3);
  }
}

そして、そのjavaファイルと同じディレクトリにAwsCredentials.propertiesというaccesskeyとSecretKeyが書いてあるファイルを置きます。

accessKey = xxxxxxxxxxxxxxxxxxxxxx
secretKey = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

適当にコンパイルして、実行してみます。

$ java -jar s3test.jar
START
======= Bucket List ========
your-original-bucket

アップロードするファイルの準備

ファイルアップロードを実装する前にローカルの環境にアップロードするファイルを用意しておきます。
Javaでそこからやっても良いのですが、面倒なので事前に用意することにしました。
種となる10KBのファイルを用意します。

$ i=0; while [ $i -lt 1024 ]; do echo -n '0123456789' >> orig01; i=$(($i+1)); done
$ cat orig01
012345678901234567890123456789012345678901234567890123456789
.
.
.

そしてこの種ファイルで10KBのファイルを量産します。

$ mkdir /var/tmp/files
$ cd /var/tmp/files
$ i=0; while [ $i -lt 300 ]; do cp orig01 test-${i}.txt; i=$(($i+1)); done

とりあえず、test-??.txtというファイルを300個作りました。

ファイルのアップロード

アップロードするファイルができましたのでさっそくファイルのアップロードを実装します。
冒頭で書いた通り、今回の目的はパフォーマンステストなので並列でアクセスできるようにthreadで実装します。

class S3TestThread implements Runnable {
  
  String mode = "";
  int index = 0;
  String localPath = "/var/tmp/files";
  String filePrefix = "test-";
  String bucket = "your-original-bucket";
  String s3Path = "test01/" + filePrefix;
  AmazonS3 s3;
  long start, end;
  
  S3TestThread(AmazonS3 s, String m, int i){
    s3 = s;
    mode = m;
    index = i;
  }
  
  void uploadFile(int i){
    
    // uploadするファイル
    File localFile = new File(localPath + "/" + filePrefix + i + ".txt");
    String s3File = s3Path + i + ".txt";
    
    System.out.println("UploadFile: " + localFile.getName());
    
    // 時間計測用のスタート時間
    start = System.currentTimeMillis();
    
    // upload
    s3.putObject(bucket, s3File, localFile);
    
    //時間計測用終了時間
    end = System.currentTimeMillis();
    
    //処理時間出力
    System.out.println(s3File + ":" + (end - start));
    
    //認証無しでアクセスできるように設定(GETのパフォーマンステスト用)
    s3.setObjectAcl(bucket, s3File, CannedAccessControlList.PublicRead);
    
  }
  
  @Override
  public void run(){
    if(mode.equals("put")){
      uploadFile(index);
    }
  }
}

コンストラクタではs3の接続オブジェクトと、動作モード(今回はput)、そしてthreadのインデックスを渡すようにしました。
動作モードについてはこれからdeleteなども必要になるかもしれないので、そのためのスイッチ。
そして、パフォーマンスを測るためにuploadする前と後で時間を取ります。
最後にGETのパフォーマンス測定はApache JMeterを使いたいのでACLをPublicReadに設定しています。

mainは省略。

実行結果は以下の通り。

$ java -jar s3test.jar 10 put
START
=======put==========
UploadFile: test-0.txt
UploadFile: test-1.txt
UploadFile: test-2.txt
UploadFile: test-3.txt
UploadFile: test-4.txt
UploadFile: test-5.txt
UploadFile: test-6.txt
UploadFile: test-7.txt
UploadFile: test-8.txt
UploadFile: test-9.txt
test01/test-2.txt:1575
test01/test-3.txt:1580
test01/test-8.txt:1584
test01/test-1.txt:1591
test01/test-4.txt:1588
test01/test-6.txt:1588
test01/test-9.txt:1610
test01/test-7.txt:1615
test01/test-5.txt:1621
test01/test-0.txt:1638

まとめ

SDKがあるのでご覧の通りとても簡単。
ちなみにSDKはJavaだけでなくPythonもあるので、機会があればそっちもやってみたいです。

おしまい。



ChefのRecipeを書く(2)

ntpのインストール

引き続きyumでntpのインストールを行います。
cookbook/COOKBOOK/recipes/default.rbに以下の内容を追加します。

package "ntp" do
 action :install
end

で、実行をしてみます。

$ sudo chef-solo -c .chef/solo.rb -j .chef/chef.json
INFO: *** Chef 0.10.8 ***
.
.
.
INFO: Processing package[ntp] action install (base_packages::default line 53)
INFO: package[ntp] installed version 4.2.2p1-15.el5.centos.1
INFO: Chef Run complete in 25.093701 seconds
INFO: Running report handlers
INFO: Report handlers complete

$ rpm -qa | grep ntp
ntp-4.2.2p1-15.el5.centos.1

無事インストールできました!

これで予定していたものは全て終了ですが、物足りないのでntp.confをAWSのS3から取ってくるのをやってみたいと思います。

S3に接続するためのs3cmdの設定から

S3に接続するときにはs3cmdを使います。
s3cmdは始めに

s3cmd --configure

というコマンドで~/.s3cfgという設定ファイルを作るのですが、このコマンドは対話型で使いづらいので、この.s3cfgをテンプレートとして使いたいと思います。

まずは.s3cfgをテンプレート化します。
.s3cfgでaccess keyとsecret keyの部分を以下のようにして変数扱いにします。

access_key = <%= @access_key %>
secret_key = <%= @secret_key %>

そしてcookbooks/COOKBOOK/templates/defaults3cfg.erbという名前で保存します。

次にrecipeでこのテンプレートを使うように定義を加えます。

cookbooks/COOKBOOK/recipes/default.rb

template "/home/piyo/.s3cfg" do
 source "s3cfg.erb"
 owner "piyo"
 group "piyo"
 mode 0600
 variables(
  :access_key => "xxxxxxxxxxxxxxxxxxxxx",
  :secret_key => "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
 )
end

chef-soloを実行し、s3cmdを実行すると無事s3に接続することができました。

Resource s3_fileのセットアップ

このs3_fileですが、chef 11.0.0にアップデートしたら使えなくなりました。調査はしていませんが、取り急ぎ!

これでs3cmdコマンドが使えるようになりました。
が、よくよく調べてみるとs3cmdコマンドなんて使わなくてもs3のファイルのresourceのs3_fileというものがあるらしいのでさっそくそれを使えるようにセットアップします。

githubからs3_file.rbを取得

このs3_fileは公式ではなく、サードパーティなのでgithubとかよくわからないですが、そこからダウンロードしてきます。

S3 File Resource for Chef(gist.github.com)

ダウンロードしたs3_file.rbcookbooks/COOKBOOK/libraries/に置きます。

cookbooks/COOKBOOK/recipes/default.rbには以下を追加します。

s3_file "/etc/ntp.conf" do
 source "s3://your_bucket/directory/ntp.conf"
 owner "root"
 group "root"
 mode 0644
 access_key_id "xxxxxxxxxxxxxxxxxxxx"
 secret_access_key "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
end

実行してみると

LoadError: no such file to load -- aws/s3

と怒られましたのでaws/s3をインストールします。

sudo gem install aws-s3

再び実行すると今度はうまくいきました。
/etc/ntp.confの中を見てみると、、、


<Error><Code>PermanentRedirect</Code>
.
.

むむむ、なんかエラーのようです。どうやらエラーのハンドリングを省いているようで、エラーでもERRORと出ないのか。
どうにかならないもんでしょうか。

それはちょっと置いておいてなぜエラーになったのかを調べます。
どうやらsourceの部分でregionを指定する必要があったみたいです。
なので以下のように書き直します。

s3_file "/etc/ntp.conf" do
 source "s3-ap-northeast-1://your_bucket/directory/ntp.conf"
 owner "root"
 group "root"
 mode 0644
 access_key_id "xxxxxxxxxxxxxxxxxxxx"
 secret_access_key "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
end

再び実行して/etc/ntp.confを見るとうまくできてました!
ちょっと問題はありますが、目的は達成。
これで実用化できそうです。
あと、AWSのaccesskeyとsecretkeyの指定が冗長なのでここは一つの変数にまとめたいところです。

おしまい。