エンジニアリング

へたれエンジニアが社内環境みたいなところで使っているjava6のレガシーシステムの画像サーバーをS3に移設するケーススタディを考えてみた

どーも へたれエンジニアです。

レガシーってどこでもありますよね。ごにょごにょされ続けてコストもかけられない・・・・そんな中オンプレにある画像ファイルのコスト削減じゃ!みたいになって偉い人から「クラウドとかどうなの?」みたいな事を言われて・・・みたいなケーススタディを考えてみることにしました。

ケーススタディ(妄想設定)

上司:「ヘタレくん、ウチの会社の社内稟議確認システムなんだけどさ、画像毎回あげているもの全部蓄積しておく必要はあるものの、オンプレだとコストがメッチャクチャ高くてこのままだと年間500万くらいいっちゃうんだよね。これを5分の1くらいにしたいんだけど今流行ってるクラウドとか行けるのかな?」

へたれ:「上司氏、マジっすか ん〜今のシステム古くてイケるか・・・わからんす。」

上司:「うん、とりあえずなんとかして」

・・・・・・どうにかします。(あくまでも妄想設定ですよ)

インフラ構成イメージ

イメージこんな感じの構成です

処理は日時でバッチ処理で画像取り込み処理が走り、配置された画像をWebアプリから参照する形のシステムとします。

今まで社内からストレージに向いているものをS3に変更する形ですよね。

確認するところはどこか?

僕はただの、アプリ寄りのへたれエンジニアなのでAWSの部分とかの知識も怪しいため、以下3点の検証が問題なく行えればできれば出来ると考えました。

  1. Java1.6でAWSにそもそもアクセス可能なのか?
  2. S3まわりの設定をどのように行うのか?
  3. バッチ・Webサーバーとの通信まわりをどのようにするか?

1に関しては「そもそもjavaアップデートしろよ」みたいな声が飛んできそうだと思いますけどレガシーでテスト周りガッと出来ないシステムとか・・・皆さんのところでも心当たりありますよね?orz

1. java1.6でAWSにそもそもアクセス可能なのか?

実際にやって見る時に以下のハマリポイントがありました。

・CLIは2系だと依存関係気にしないで済むので楽
・SDKは1系を使いましょう。ライブラリは全部載せではなく個別にいれないと何故かビルド出来なかったので注意

CLIは2.X系だと依存関係気にしないで済むので楽

まずぶち当たるのが、もろもろ作業する際にサーバーにCLIをインストールする必要があるのですが、選択肢が複数提示されてます。

AWS CLI のインストール

AWS CLI バージョン 1 / AWS CLI バージョン 2 どっちを選ぶかということですが、レガシーシステムが載っているサーバーは得てしてレガシーなOSだったりするので出来るだけ依存関係が無い方が良いですよね。

という観点を見ると CLI 1.X系だとPythonの制約が前提であるから 2.X系を選ぶほうが良いと考えました。

前提条件

  • Python 2 バージョン 2.7 以降または Python 3 バージョン 3.4 以降

引用:AWS CLI のインストールバージョン1

CLIのインストール

[XXX] ~
% curl "https://awscli.amazonaws.com/AWSCLIV2.pkg" -o "AWSCLIV2.pkg"
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 20.4M 100 20.4M 0 0 3140k 0 0:00:06 0:00:06 --:--:-- 3631k
[XXX] ~
% sudo installer -pkg ./AWSCLIV2.pkg -target /
installer: Package name is AWS Command Line Interface
installer: Installing at base path /
installer: The install was successful.
[XXX] ~
% which aws
/usr/local/bin/aws
XXX% aws --version
aws-cli/2.0.6 Python/3.7.4 Darwin/17.4.0 botocore/2.0.0dev1

SDKは1系を使いましょう。ライブラリは全部載せではなく個別にいれないと何故かビルド出来なかったので注意

今回コードはJavaで実施します。SDKも1系と2系があり悩まされます。

AWS SDK for Java 2.0 は一般利用可能となり、本番使用がサポートされています。SDK のバージョン 2.0 では、1.11. のコードベースが大幅に書き換えられています。AWS SDK for Java 2.0 は Java 8 以上をサポートしており、非ブロッキング I/O のサポート、スタートアップパフォーマンスの向上、およびページ区切りされたレスポンスの自動反復計算など、要望の多かった機能がいくつか追加されています。こうした新機能に加え、整合性、不変性、使いやすさを重視して、SDK の多くの側面でリファクタリングが行われています。

引用:AWS SDK for JAVA

今回はJava 6を使うので SDK 2.X系だとそもそもビルドが通りませんでした・・・・(コード見たけどJava8以上の関数や記法だらけだった)

なので、1.X系のSDKの利用は必須です。

1.X系の利用についてもAWSは全部のせSDKと個別に使いたいものを選択して使うことが出来ます。今回全部のせのSDKだとビルドがこちらも出来ませんでした(なんでだろ?)なので、個別にMavenに記載してます。

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>test</groupId>
  <artifactId>aws</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>aws</name>
  <url>http://maven.apache.org</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <java.version>1.6</java.version>
  <maven.compiler.target>${java.version}</maven.compiler.target>
  <maven.compiler.source>${java.version}</maven.compiler.source>
  </properties>

  <dependencies>
  <!-- Amazon S3  -->
  <dependency>	
      <groupId>com.amazonaws</groupId>
      <artifactId>aws-java-sdk-s3</artifactId>
      <version>1.11.760</version>
  </dependency>
  <dependency>
      <groupId>com.amazonaws</groupId>
      <artifactId>aws-java-sdk-core</artifactId>
      <version>1.11.760</version>
  </dependency>
  <dependency>
      <groupId>org.apache.httpcomponents</groupId>
      <artifactId>httpclient</artifactId>
      <version>4.5.9</version>
  </dependency>
  <dependency>
      <groupId>org.apache.httpcomponents</groupId>
      <artifactId>httpcore</artifactId>
      <version>4.4.11</version>
  </dependency>
  <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>2.6.7.3</version>
  </dependency>
  <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-core</artifactId>
      <version>2.6.7</version>
  </dependency>
  <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-annotations</artifactId>
      <version>2.6.0</version>
  </dependency>
  <dependency>
      <groupId>joda-time</groupId>
      <artifactId>joda-time</artifactId>
      <version>2.8.1</version>
  </dependency>
  </dependencies>
</project>

 

これでライブラリ周りの準備は整いました。

2. S3まわりの設定をどのように行うのか?

さて、S3のバケットを作ったりもろもろ設定を行っていきましょう。

AWSのアカウントはゲット済前提で進めます。

※AWSのコンソールは頻繁にデザインが変わるので、頑張って読み替えて乗り切りましょう

コンソールからS3を選択

バケット作成をクリック

  1. バケット名は適当に
  2. リージョンは日本に近いところにしておきましょう。なおS3は EC2でいうMulti-AZ的な概念は内部的に持っているみたいで気にしないで良さそう?です。
  3. パブリックアクセスを全てブロックにチェックを入れる文字通り全てのパブリックアクセスをブロックになるので、今回のようなAWSの外からアクセスする形になるのでチェックを外し、あとでバケットポリシーで定義していく方向でアクセス管理をします。

バケットが完成しました、ここからアクセス設定を行うため IAMとバケットポリシーの設定を行います。

 

3. バッチ・Webサーバーとの通信まわりをどのようにするか?

 

IAMのユーザ追加をクリック

ベストはユーザー作ってロールを作ってそのロールをユーザーにアタッチするのが正しいです。・・・が今回は既存ポリシーのS3FullAccessをいったんつけちゃいます。

AdminS3というユーザーを作りました

S3のバケットポリシーで、アクセス出来るユーザー、外部アクセスのIPを絞る

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "XXXXXXXXXXXXXXXX" ユーザー設定
            },
            "Action": "s3:*",
            "Resource": [
                "arn:aws:s3:::XXXXXXX", ←バケット
                "arn:aws:s3:::XXXXXXX/*"
            ],
            "Condition": {
                "IpAddress": {
                    "aws:SourceIp": [
                        "XXX.XXX.XXX.XXX", ←IP
                        "xxx.xxx.xxx.xxx/yy"
                    ]
                }
            }
        }
    ]
}

これでS3側の設定が終わりました。

超ざっくりこれでアップロードのコードを書いてみましょう

※本来 トークンまわりはcredentialに登録するやり方のほうがセキュアです。ざっくりのためこうしてます。

いったん認証したあと画像をS3にアップロードするサンプル

public class App 
{
    static final String S3_ACCESS_KEY           = "";
    static final String S3_SECRET_KEY           = "";
    static final String S3_SERVICE_END_POINT    = "https://XXXXXXXX.s3-ap-northeast-1.amazonaws.com"; //バケットのフルパス
    static final String S3_REGION               = "ap-northeast-1";
    static final String S3_BUCKET_NAME          = "/test"; //作られたバケット内でフォルダを作る
    static final String STRING_OBJ_KEY_NAME = "sample";
    static final String FILE_NAME = "filenamesample";
    
    @SuppressWarnings("deprecation")
  public static void main( String[] args ) throws IOException {
    		//稼働ver確認
        System.out.print("Javaバージョン(java.version):");
        System.out.println(System.getProperty("java.version"));
        
        System.out.println("auth start");

        // AWSの認証情報
        AWSCredentials credentials = new BasicAWSCredentials(S3_ACCESS_KEY, S3_SECRET_KEY);

        // クライアント設定
        ClientConfiguration clientConfig = new ClientConfiguration();

        // エンドポイント設定
        EndpointConfiguration endpointConfiguration = new EndpointConfiguration(S3_SERVICE_END_POINT,  S3_REGION);

        // S3アクセスクライアントの生成
        AmazonS3 client = AmazonS3ClientBuilder.standard().withCredentials(new AWSStaticCredentialsProvider(credentials))
                .withClientConfiguration(clientConfig)
                .withEndpointConfiguration(endpointConfiguration).build();

        System.out.println("auth end");

       //アップロードファイル
        File file = new File("/XXX/XXXX/XXXX/XXX.png");
        FileInputStream fis = new FileInputStream(file);


        ObjectMetadata om = new ObjectMetadata();
        om.setContentLength(file.length());

        final PutObjectRequest putRequest = new PutObjectRequest(S3_BUCKET_NAME, file.getName(), fis, om);

        // 権限の設定
        putRequest.setCannedAcl(CannedAccessControlList.PublicRead);

        // アップロード
        client.putObject(putRequest);

        fis.close();
 
    }    
}

 

無事アップロードできました。

最後に

もちろん、既存の画像をS3に移行したり、バッチ・オンラインの細かい実装などは色々ありますが、検証レベルだと上司氏の要望をこれで叶えれそうですね。

脳内設定シュミレーションによるケーススタディですがやってて楽しかったのでまたやってみたいと思います。