AWS Fargate BlueGreenDeployment

はじめに

AWS FargateはContainerインスタンスの管理をAWSにお任せすることができるサービスです。

現状、ECS(LaunchType EC2)を使っているのですが、JenkinsからECSにBlueGreenDeployするときにecs-deployを使っています。
ecs-deployはaws cliとjqには依存していますがshellだけで書かれてるので持ち運びが便利なんですね。

ecs-deployはFargateに対応していないので対応させてみました。

https://github.com/uzresk/ecs-deploy.git

使い方

1. aws cliはFargateに対応しているバージョンをお使いください。

ちなみに私の環境はこちら

aws-cli/1.14.7 Python/2.7.12 Linux/4.9.62-21.56.amzn1.x86_64 botocore/1.8.11

2. コマンドはecs-deployと全く同じです

./ecs-deploy -c [cluster-name] -n [service-name] -i [registry-url]:[tag] -t 300 -r us-east-1

デフォルトのタイムアウトは90秒なのですが、終わらないことが何回かあったので少し長めにしておくのがおススメです。

実行結果

Using image name: xxxx.dkr.ecr.ap-northeast-1.amazonaws.com/xxxx:0.0.1-SNAPSHOT
Current task definition: arn:aws:ecs:us-east-1:xxxx:task-definition/xxxx:25
Current requires compatibilities FARGATE
New task definition: arn:aws:ecs:us-east-1:xxxx:task-definition/xxxx:26
Service updated successfully, new task definition running.
Waiting for service deployment to complete...
Service deployment successful.

変更点

Fargateが追加されたことによりrequiresCompatibilitiesの指定を引き継ぐようにしたのと、
cpu, memoryの設定も合わせて引き継ぐようにしました。
LaunchTypeがEC2の場合はcpu,memoryは設定されません。

[root@ip-10-0-0-100 ecs-deploy]# git diff
diff --git a/ecs-deploy b/ecs-deploy
index 637e793..8ad1cb1 100755
--- a/ecs-deploy
+++ b/ecs-deploy
@@ -261,11 +261,17 @@ function createNewTaskDefJson() {
     fi

     # Default JQ filter for new task definition
-    NEW_DEF_JQ_FILTER="family: .family, volumes: .volumes, containerDefinitions: .containerDefinitions"
+    NEW_DEF_JQ_FILTER="family: .family, volumes: .volumes, containerDefinitions: .containerDefinitions, requiresCompatibilities: .requiresCompatibilities"

     # Some options in task definition should only be included in new definition if present in
     # current definition. If found in current definition, append to JQ filter.
-    CONDITIONAL_OPTIONS=(networkMode taskRoleArn placementConstraints)
+    LAUNCH_TYPE=$(echo "$TASK_DEFINITION" | jq -r '.taskDefinition.requiresCompatibilities[0]')
+    echo "Current requires compatibilities $LAUNCH_TYPE"
+    if [ $LAUNCH_TYPE == FARGATE ]; then
+      CONDITIONAL_OPTIONS=(networkMode taskRoleArn executionRoleArn placementConstraints memory cpu)
+    else
+      CONDITIONAL_OPTIONS=(networkMode taskRoleArn executionRoleArn placementConstraints)
+    fi
     for i in "${CONDITIONAL_OPTIONS[@]}"; do
       re=".*${i}.*"
       if [[ "$DEF" =~ $re ]]; then

おわりに

もう少し動作確認したらプルリクエスト送ろうと思いますが、だいぶメンテされていないようなので多分マージされない気がします。。。

続きを読む

[初心者向け] 実務開始の前に知っておきたいAWSのこと

この投稿は、チュートリアルを終えて実務に入る前の人に知っておいて欲しいことをまとめています。
チュートリアルは終えている前提なので、AWSのサービスに関する説明は省略しています。
ベンチャー企業の複数あるうちの1サービスくらいの規模感のアプリケーションを構築すること(移行ではない)を前提としています。
社内メンバー宛なので偏りがあるかもしれないですが、みなさまの役にも経てば幸いです。

設計に役立つツール

AWSを実務で利用する際は、構成図を書くかと思います。その際に利用できるのが Cloudcraft です。
構成図とともに料金もざっくりわかります。
cloudcraft例.png

もっと詳細に料金を出す必要があるならば、AWSが提供している料金計算ツールを使うと良いかもしれません。
SIMPLE MONTHLY CALCULATOR

また、サーバーレス構成を検討している方は、re:Invent2017で発表された「AWS Serverless Application Repository」を利用すると構成案が公開されていますので、参考になると思います(2017年12/11時点ではまだpreview版)

リザーブドインスタンス

リザーブドインスタンスはEC2やRDSのようなサービスで、前払いをすると料金を節約)できる制度です。 (参考: AWSリザーブドインスタンスについて)
インスタンスタイプや期間によりますが、「半年以上使うことが決まってるなら、1年分はリザーブドインスタンス買っちゃった方がお得」ということも少なくありません
ただし、リザーブドインスタンスはリージョン(もしくはアバイラビリティーゾーン)ごとで、インスタンスタイプも指定するので、インスタンスを立て直したり、何も知らずにインスタンスタイプを変更してしまうと「リザーブドインスタンスを買っているのに使えてない」状態になりえます。
リザーブドインスタンスは売買もできますし、t2.small2つをt2.medium1つに変更などもできるので、一度買ったらできるだけ今の構成を追随するようにしましょう。

IAM

IAMはAWS内の権限をつかさどるサービスです。各種サービスへの参照・書き込み・実行などを細かく設定できます。
権限を操作できてしまうので、一度作るとなかなか変更するのが怖いところです。
ここでのtipsはポリシーはできるだけ細かく設定することです。めんどくさいですが、サービスAのs3参照ポリシーとサービスBのs3参照ポリシーは分けた方が良いと思います。たとえ1つしかサービスをローンチしてなくても、サービスAに限定するようなポリシーを設定しておきましょう。
最小単位であるポリシーが細かければその組み合わせで柔軟に設定が可能です。

awsを操作するためのツール

  • aws-cli … コマンドラインからawsを操作できる
  • ansibleのec2.py … ansibleでipを指定するのではなく、ec2についているタグ名で絞ってec2をプロビジョニングするためのツール(というかpython script)

最後に

クラウドになるとlinuxではないもの(IAMやセキュリティグループなど)の設定をたくさんしなければならず、(aws-cliもありますが)guiでの設定も増えてしまいます。
面倒な部分はありますが、慣れてしまえば早いですし、コスパもよいのでどんどん利用していきたいですね
では、よいaws lifeを

[初心者向け] 実務開始の前に知っておきたいシリーズ

[初心者向け] 実務開始の前に知っておきたいSpreadSheetのこと
[初心者向け] 実務開始の前に知っておきたいAnsibleのこと
[初心者向け] 実務開始の前に知っておきたいGoogleAnalyticsのこと
[初心者向け] 実務開始の前に知っておきたいWordPressのこと
[初心者向け] 実務開始の前に知っておきたいRailsのこと

続きを読む

絶対的に使った方がいいLogstashのMultiple Pipelinesについて書いてみた

はじめに

おはです!
Logstashのフィルタの中でもGrokが好きなぼくが、Advent Calendar11日目を書かせていただきますー
あ、でも今回は、Grokについては書かないですよ!

じゃあ、何書くの?Grokしか脳のないお前が何を書くのさー
そりゃ、あれだよ!Logstash 6.0がGAされたので、待ちに待ったMultiple Pipelinesについて書くしかないでしょ!

てことで、LogstashのMultiple Pipelinesについて、ゆるーく書いていきます( ゚Д゚)ゞビシッ

構成について

今回テストするにあたって使用した構成は以下です。

  • Amazon Linux AMI 2017.09.1 (HVM)
  • Logstash 6.0
  • logstash-input-s3
  • Elasticsearch 6.0
  • Kibana 6.0
  • X-Pack 6.0

ちなみに、もろもろインストールされている前提で記載しています。
もし、インストールする場合は、以下のインストール手順を参考にして頂ければと思います。

Logstashについて

Logstashは、Elasticsearch社が提供するオープンソースのデータ収集管理ツールです。

LogstashのPipelineは、以下のような流れで処理されます。
例として、インプットデータをApacheのログファイルで、ログファイルに対してフィルタをかけ、Elasticsearchにストアするといった流れにしてます。1

logstash01.png

INPUT

様々なデータソースを取り込みたい!そんな要望に応えるべく用意されたのがLogstash。
Logstashは、様々なデータ形式に対応しています。
例えば、ソフトウェアのログ、サーバのメトリクス、Webアプリケーションのデータや、クラウドサービスなど。

FILTER

INPUTしたデータソースをフィルタで解析し、構造化します。
データソースの変換には、正規表現でデータをパースするためのGrokフィルタや、IPアドレスから地理情報を得るためのGeoIPフィルタなど様々なフィルタライブラリが用意されています。

OUTPUT

データを構造化したのち、任意の出力先にデータをストアします。
Elasticsearch以外の出力先も多数提供されているので、環境に合わせてデータをストアします。

Logastashのディレクトリ構成

上記までが処理の流れで、これらの処理を定義するファイルをLogstashでは準備する必要があります。

以下のようにLogstashのディレクトリは構成されており、conf.d配下に定義ファイル(hoge.confと記載)を配置します。

/etc/logstash
  ├ conf.d
  │ └ hoge.conf
  ├ jvm.options
  ├ log4j2.properties
  └ logstash.yml

hoge.confに様々なデータソースに対して定義します。
例えば、AWSのALBのアクセスログとApacheのアクセスログを定義すると以下のようになります。2

hoge.conf
input {
  s3 {
    tags => "alb"
    bucket => "hoge"
    region => "ap-northeast-1"
    prefix => "hoge/"
    interval => "60"
    sincedb_path => "/var/lib/logstash/sincedb_alb"
  }
  file {
    path => "/etc/logstash/log/httpd_access.log"
    tags => "httpd"
    start_position => "beginning"
    sincedb_path => "/var/lib/logstash/sincedb_httpd"
  }
}
filter {
  if "alb" in [tags] {
    grok {
      patterns_dir => ["/etc/logstash/patterns/cloudfront_patterns"]
      match => { "message" => "%{ALB_ACCESS}" }
      add_field => { "date" => "%{date01} %{time}" }
    }
    date {
      match => [ "date", "yy-MM-dd HH:mm:ss" ]
      locale => "en"
      target => "@timestamp"
    }
    geoip {
      source => "c_ip"
    }
    useragent {
      source => "User_Agent"
      target => "useragent"
    }
    mutate {
      convert => [ "time_taken", "float" ]
      remove_field => [ "message" ]
    }
  else if "httpd" in [tags] {
    grok {
      patterns_dir => ["/etc/logstash/patterns/httpd_patterns"]
      match => { "message" => "%{HTTPD_COMMON_LOG}" }
    }
    geoip {
      source => "clientip"
    }
    date {
      match => [ "date", "dd/MMM/YYYY:HH:mm:ss Z" ]
      locale => "en"
      target => "timestamp"
    }
    mutate {
      remove_field => [ "message", "path", "host", "date" ]
    }
  }
}
output {
  if "alb" in [tags] {
    elasticsearch {
      hosts => [ "localhost:9200" ]
      index => "elb-logs-%{+YYYYMMdd}"
    }
  }
  else if "httpd" in [tags] {
    elasticsearch {
      hosts => [ "localhost:9200" ]
      index => "httpd-logs-%{+YYYYMMdd}"
    }
  }
}

このようにデータソース毎にタグを付与し、if文で判定させるといったかたちになってます。
今回は、データソースが少ないからいいですが、更に増やしていくとなると可読性も悪くなるのと、リソースのハンドリングも難しくなってきます。
また、データソースが増えたり、フィルタ変更などの際には、一つの定義ファイルを更新するため、全体に影響を及ぼす可能性があります。。
なんてこった(´□`;)

Multiple Pipelinesになると何がいいのさ

そこで!Multiple Pipelinesの出番なのです!
hoge.confに対して複数のデータソースを組み込んでいたものを、分割することができちゃうのですー
また、リソースの割り当てとして、Worker数などもPipeline毎に割り当てることができるのです!歓喜!!

ここからは、Multiple Pipelinesをどのように使うのかを書いてきます。

Multiple Pipelinesを試してみる。

Logstashのディレクトリ構成は変わらないのですが、新しくpipelines.ymlという定義ファイルを作成します。
また、先ほどのhoge.confをデータソース毎にファイルを以下のように分けます。

  • alb.cfg
  • httpd.cfg

alb.cfgの定義は以下です。

alb.cfg
input {
  s3 {
    bucket => "hoge"
    region => "ap-northeast-1"
    prefix => "hoge/"
    interval => "60"
    sincedb_path => "/var/lib/logstash/sincedb_alb"
  }
}
filter {
  grok {
    patterns_dir => ["/etc/logstash/patterns/cloudfront_patterns"]
    match => { "message" => "%{ALB_ACCESS}" }
    add_field => { "date" => "%{date01} %{time}" }
  }
  date {
    match => [ "date", "yy-MM-dd HH:mm:ss" ]
    locale => "en"
    target => "@timestamp"
  }
  geoip {
    source => "c_ip"
  }
  useragent {
    source => "User_Agent"
    target => "useragent"
  }
  mutate {
    convert => [ "time_taken", "float" ]
    remove_field => [ "message" ]
  }
 }
output {
  elasticsearch {
    hosts => [ "localhost:9200" ]
    index => "elb-logs-%{+YYYYMMdd}"
  }
}

httpd.cfgの定義は以下です。

httpd.cfg
input {
  file {
    path => "/etc/logstash/log/httpd_access.log"
    start_position => "beginning"
    sincedb_path => "/var/lib/logstash/sincedb_httpd"
  }
}
filter {
  grok {
    patterns_dir => ["/etc/logstash/patterns/httpd_patterns"]
    match => { "message" => "%{HTTPD_COMMON_LOG}" }
  }
  geoip {
    source => "clientip"
  }
  date {
    match => [ "date", "dd/MMM/YYYY:HH:mm:ss Z" ]
    locale => "en"
    target => "timestamp"
  }
  mutate {
    remove_field => [ "message", "path", "host", "date" ]
  }
}
output {
  elasticsearch {
    hosts => [ "localhost:9200" ]
    index => "httpd-%{+YYYYMMdd}"
  }
}

ディレクトリ構成は以下になります。

/etc/logstash
  ├ logstash.yml
  ├ conf.d
  │ └ alb.cfg
  │ └ httpd.cfg
  ├ jvm.options
  ├ log4j2.properties
  └ pipelines.yml

ここでやっと登場するpipelines.ymlの定義は以下です。

pipelines.yml
- pipeline.id: alb
  pipeline.batch.size: 125
  path.config: "/etc/logstash/conf.d/alb.cfg"
  pipeline.workers: 1
- pipeline.id: httpd
  pipeline.batch.size: 125
  path.config: "/etc/logstash/conf.d/httpd.cfg"
  pipeline.workers: 1

めっちゃシンプルですね。
呼び出すファイルパスの指定とパラメータの定義を記載するだけです。
データソース単位でpipeline.workersを割り当てられることになったので、柔軟にコアの配分ができるようになりました。
詳細の設定については、settings fileを確認してもらえればと思います。

動かすよ!

今回、サービス起動を前提としているため、pipelines.ymlを読み込ませるには、logstash.confのpath.configをコメントアウトする必要があります。
(ここでハマりました。。公式サイトにサービス起動について書かれているところがみつからず。。)

logstash.conf
# path.config: /etc/logstash/conf.d/*.conf

てことで、起動しますー
これでデータソース単位で動かすことができます。

$ initctl start logstash
logstash start/running, process 3121

おまけ

今回説明していないですが、X-Packを導入することで、MonitoringでPipelineの状況をみることができます。
データの流れを把握したり、分岐などのロジックが有効に働いてるかなども確認することが可能です。

先ほど、alb.cfgを取り込んだ際のPipelineは以下の様に表示されます。

logstash03.png

さいごに

いかがでしたか?
Multiple Pipelinesは、常にデータを取り込んでいるLogstashを支えるための根幹たる機能ではないでしょうか?
ぜひぜひ、みなさんもMultiple Pipelinesを使って、よりよいLogstsah Lifeを送って頂ければとヽ(*゚д゚)ノ

それでは、Advent Calendar11日目を終わりますー
明日の12日目は、”Elasticsearch v1.7 -> v2.4 -> v5.5 と段階的にバージョンアップした話し”ですね!
楽しみだすー


  1. Elasticsearch社の公開されているグローバルIPを利用しています 

  2. 指定しているパスは、環境に合わせて指定してください 

続きを読む

Windows 10 ローカルに AWS S3 のクローン(minio)を手軽につくる

2017/12/10

Dockerのほうが動かなかったのでWSL側で動かしてみたらすぐにできたのでメモ。
(windowsでkitematic上で手軽に終えたかったのですが)

WSL: Windows Subsystem for Linux

(minioにたどり着くまでに、fakes3とs3ninjaをdockerで使ってみましたが、aws-sdkでput時にエラーが出たので乗り換えました)

minio のインストールと起動まで

以下からLinux向けをダウンロード
https://github.com/minio/minio

  • rootユーザーでやりましたが、rootである必要はないかも・・・
cd /root
wget https://dl.minio.io/server/minio/release/linux-amd64/minio

chmod +x minio
mkdir /minio_data

./minio server /root/minio_data

動作すると下記のような表示が出ます。

Created minio configuration file successfully at /root/.minio
Endpoint: ~省略~ http://127.0.0.1:9000
AccessKey: ~省略~
SecretKey: ~省略~

Browser Access: ~省略~ http://127.0.0.1:9000

Command-line Access: ~省略~ 

Object API (Amazon S3 compatible):
   Go:         https://docs.minio.io/docs/golang-client-quickstart-guide
   Java:       https://docs.minio.io/docs/java-client-quickstart-guide
   Python:     https://docs.minio.io/docs/python-client-quickstart-guide
   JavaScript: https://docs.minio.io/docs/javascript-client-quickstart-guide
   .NET:       https://docs.minio.io/docs/dotnet-client-quickstart-guide

Drive Capacity: 532 GiB Free, 910 GiB Total

minio の UI を使ってみる

  • ブラウザで http://127.0.0.1:9000 にアクセス

image.png

  • 上記のメッセージに出てきたアクセスキーとシークレットキーを入力してログイン

image.png

  • バケットを作る (入力してenter)

image.png

  • バケットを消す(消す機能はUIにない?)

仕方なく・・・ rm -rf で削除

# ll /root/minio_data/
合計 0
drwxrwxrwx 0 root root 4096 12月 11 01:17 ./
drwx------ 0 root root 4096 12月 11 00:50 ../
drwxrwxrwx 0 root root 4096 12月 11 00:57 .minio.sys/
drwxrwxrwx 0 root root 4096 12月 11 01:16 sample/
drwxrwxrwx 0 root root 4096 12月 11 01:17 sample2/
rm -rf /root/minio_data/sample2

ちゃんと消えました

spring boot から minio にアクセスしてみたときのポイント

接続情報としては下記でOKでした(SDKバージョンなどで書き方は少し違いがあるかもしれません)

エンドポイント:http://127.0.0.1:9000
アクセスキーとシークレットキーは上記のもの

ClientConfiguration clientConfig = new ClientConfiguration();
clientConfig.setProtocol(Protocol.HTTP); <- ここがポイント!

EndpointConfiguration endpointConfiguration = new EndpointConfiguration("http://127.0.0.1:9000", null); <- ここがポイント!

AWSCredentials credentials = new BasicAWSCredentials(ACCESS_KEY, SECRET_KEY); <- ここがポイント!
AWSStaticCredentialsProvider credentialsProvider = new AWSStaticCredentialsProvider(credentials);

AmazonS3 client = AmazonS3ClientBuilder.standard()
                            .withCredentials(credentialsProvider)
                            .withClientConfiguration(clientConfig)
                            .withEndpointConfiguration(endpointConfiguration).build();

今日はここまで m(_ _)m

続きを読む

S3をGoでマウントする

S3をファイルシステムとしてマウントするためのデーモンをつくっています。ホームディレクトリ全体を共有できてしまえば、dotfilesまわりのセットアップを頑張らなくてもよいのでは、というのが開発の動機です。
なので、既存のS3を便利に見れるものと言うよりも、セキュアかつパフォーマンスがなるべく(ファイルによらず)フラットで高速なファイルシステムを目指しています。

juntaki/bucketsync: S3 backed FUSE Filesystem written in Go with dedup and encryption.

使い方

まずは、設定ファイルを作成します。$HOME/.bucketsync/config.ymlにファイルができます。S3のクレデンシャルが平文で入ってしまうので取扱は注意です。出力されたファイルには暗号化や圧縮のオプションがありますが、記事公開の時点では実装が間に合ってないため意味はないです。。

bucketsync config --bucket <Bucket name> \
                  --region <Region, e.g. ap-northeast-1> \
                  --accesskey <AWS access key> \
                  --secretkey <AWS secret key> \
                  --password <Password for data encryption>
config.yml
bucket: ""
region: ""
access_key: ""
secret_key: ""
password: ""
logging: production
log_output_path: /home/juntaki/.bucketsync/bucketsync.log
cache_size: 1024
extent_size: 65536
encryption: true
compression: true

あとは適当なディレクトリに、マウントするだけです。

bucketsync mount --dir /path/to/mountpoint

アンマウントするには、このコマンドです。

bucketsync unmount

仕組み

使っているライブラリを中心に、全体的な設計を説明します。

FUSE

Linuxでは、FUSEという仕組みでユーザー空間でファイルシステムを作ってマウントすることが出来ます。
カーネルのFUSEモジュールを使うlibfuseを使って実装する方法もありますが、今回の実装ではGoで書かれたライブラリの、hanwen/go-fuse: FUSE bindings for Goを使っています。go-fuseで定義されているインターフェースを実装すれば、簡単にファイルシステムが作れます。

bucketsyncでは、pathwalkのあたりをガッツリ書きたかったので、pathfsを使っています。なので、実装したインターフェースはpathfs.Filesystemです。

データの格納方法

S3コンソール方式の問題

既存のS3を便利に見れるようにFUSEでマウントしようとすると、(S3のコンソールから見てもそうなってるように)オブジェクトのキーとファイルパスを1対1で対応させると思います。たとえば、マウントポイントからdirディレクトリの下に色々とファイルを格納すると、こんな感じのキーのオブジェクトが複数できます。

/dir/test1.txt
/dir/test2.txt
/dir/test3.txt
/dir/test4.txt

ここで、dirディレクトリをリネームすると、なんと、配下の全オブジェクトをリネームしなくてはなりません。(4つくらいならまだいいですが)
下記にもあるように、これはデータ構造の問題なので、プログラムの工夫でどうにかするにも限界があります。
Support for deep directory rename · Issue #312 · s3fs-fuse/s3fs-fuse

それに加えて、クライアントサイドでオブジェクトの暗号化や圧縮をしようと考えたとき、ランダムリード性と両立させようとすると、それらの実装の選択肢はかなり狭くなります。

bucketsyncのデータ構成

ファイルをS3のオブジェクトとして見ることを諦めると、いろいろな実装が考えられます。bucketsyncでは普通のファイルシステムのようにメタデータのオブジェクトとデータのオブジェクトを分けて格納しています。先程のリネーム問題はディレクトリのメタデータを一つ書き換えれば済むようになります。

メタデータの形式はfilesystem.goに書いています。

image.png

高速化の工夫

ローカルへのキャッシュ

メタデータだけは、pathwalkで頻繁に参照されるのでローカルにLRUでキャッシュします。@cache.go
また、S3へのAPIアクセスはAWS公式SDKを利用しています。
aws/aws-sdk-go: AWS SDK for the Go programming language.

ハッシュ:murmur3 hash

また、データ格納用のオブジェクトにはそのデータのハッシュをキーとしてつけることで、重複排除を実現しています。ハッシュはセキュアでなくてもよいので高速なmurmur3を採用しました。
spaolacci/murmur3: Native MurmurHash3 Go implementation

データのシリアライズ

メタデータはGoのオブジェクトで扱いたいので格納と読み出しで頻繁にSerializeとDeserializeする必要があります。ベンチマークを見ると、JSONのようなリフレクションを必要とする実装より、コード生成してしまうprotocol buffersなどの方が高速なようです。(あるいは、手で作ってしまっても良さそうですが。)
alecthomas/go_serialization_benchmarks: Benchmarks of Go serialization methods
※実装はまだJSONですが・・

あとがき

まだマウントできた!というレベルですが、徐々に使い物になるように改善していきます。

参考

juntaki/bucketsync: S3 backed FUSE Filesystem written in Go with dedup and encryption.
Understanding Filesystem using go-fuse, from scratch // Speaker Deck

続きを読む

AWS SDK for JavaScript with Angular で Upload to S3.

欠員が出たということで、穴埋めさせていただきます。

概要

本記事は、AngularAWS SDK for JavaScriptを利用して、S3にファイルをアップロードするという内容です。
Angular メインですので、AWSサービスの使い方や設定については割愛いたします。ご了承ください。

環境

$ uname -a
Linux ip-10-4-0-188 4.9.62-21.56.amzn1.x86_64 #1 SMP Thu Nov 16 05:37:08 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux
$ ng -v

    _                      _                 ____ _     ___
   /    _ __   __ _ _   _| | __ _ _ __     / ___| |   |_ _|
  / △  | '_  / _` | | | | |/ _` | '__|   | |   | |    | |
 / ___ | | | | (_| | |_| | | (_| | |      | |___| |___ | |
/_/   __| |_|__, |__,_|_|__,_|_|       ____|_____|___|
               |___/

Angular CLI: 1.5.5
Node: 8.9.1
OS: linux x64
Angular: 5.0.0
... animations, common, compiler, compiler-cli, core, forms
... http, language-service, platform-browser
... platform-browser-dynamic, router

@angular/cli: 1.5.5
@angular-devkit/build-optimizer: 0.0.35
@angular-devkit/core: 0.0.22
@angular-devkit/schematics: 0.0.41
@ngtools/json-schema: 1.1.0
@ngtools/webpack: 1.8.5
@schematics/angular: 0.1.10
@schematics/schematics: 0.0.10
typescript: 2.4.2
webpack: 3.8.1
package.json
{
  "name": "qiita",
  "version": "0.0.0",
  "license": "MIT",
  "scripts": {
    "ng": "ng",
    "start": "ng serve",
    "build": "ng build",
    "test": "ng test",
    "lint": "ng lint",
    "e2e": "ng e2e"
  },
  "private": true,
  "dependencies": {
    "@angular/animations": "^5.0.0",
    "@angular/common": "^5.0.0",
    "@angular/compiler": "^5.0.0",
    "@angular/core": "^5.0.0",
    "@angular/forms": "^5.0.0",
    "@angular/http": "^5.0.0",
    "@angular/platform-browser": "^5.0.0",
    "@angular/platform-browser-dynamic": "^5.0.0",
    "@angular/router": "^5.0.0",
    "core-js": "^2.4.1",
    "rxjs": "^5.5.2",
    "zone.js": "^0.8.14"
  },
  "devDependencies": {
    "@angular/cli": "1.5.5",
    "@angular/compiler-cli": "^5.0.0",
    "@angular/language-service": "^5.0.0",
    "@types/jasmine": "~2.5.53",
    "@types/jasminewd2": "~2.0.2",
    "@types/node": "~6.0.60",
    "codelyzer": "^4.0.1",
    "jasmine-core": "~2.6.2",
    "jasmine-spec-reporter": "~4.1.0",
    "karma": "~1.7.0",
    "karma-chrome-launcher": "~2.1.1",
    "karma-cli": "~1.0.1",
    "karma-coverage-istanbul-reporter": "^1.2.1",
    "karma-jasmine": "~1.1.0",
    "karma-jasmine-html-reporter": "^0.2.2",
    "protractor": "~5.1.2",
    "ts-node": "~3.2.0",
    "tslint": "~5.7.0",
    "typescript": "~2.4.2"
  }
}

手順

1) AWS SDK for JavaScriptをインストール

$ npm i --save-prod aws-sdk

2) src/app/tsconfig.app.json を編集

tsconfig.app.json
{
  "extends": "../tsconfig.json",
  "compilerOptions": {
    "outDir": "../out-tsc/app",
    "baseUrl": "./",
    "module": "es2015",
-    "types": []
+    "types": ["node"]
  },
  "exclude": [
    "test.ts",
    "**/*.spec.ts"
  ]
}

参照: Usage_with_TypeScript

3) S3アップロード用コンポネントを作成

$ ng g component s3-upload
  create src/app/s3-upload/s3-upload.component.css (0 bytes)
  create src/app/s3-upload/s3-upload.component.html (28 bytes)
  create src/app/s3-upload/s3-upload.component.spec.ts (643 bytes)
  create src/app/s3-upload/s3-upload.component.ts (280 bytes)
  update src/app/app.module.ts (408 bytes)

4) 各種ファイルを編集

src/app/app.component.html

+ <app-s3-upload></app-s3-upload>

src/app/app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { HttpClientModule } from '@angular/common/http';
import { NgModule } from '@angular/core';


import { AppComponent } from './app.component';
import { S3UploadComponent } from './s3-upload/s3-upload.component';
import { S3Service } from './s3-upload/s3.service';

@NgModule({
  declarations: [
    AppComponent,
    S3UploadComponent,
  ],
  imports: [
    BrowserModule,
    HttpClientModule,
  ],
  providers: [
    S3Service,
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

src/app/s3-upload/s3-upload.html

<div class="form">
  <div class="form-group">
    <label class="col-xs-2">select file</label>
    <div class="col-xs-10">
      <input type="file" (change)="upload($event)">
    </div>
  </div>
</div>
<div class="debug_print">
  <p>httpUploadProgress {{ httpUploadProgress | json }}</p>
</div>

src/app/s3-upload/s3-upload.component.ts

import { Component, OnInit } from '@angular/core';
import { S3Service } from './s3.service';
import * as AWS from 'aws-sdk';

@Component({
  selector: 'app-s3-upload',
  templateUrl: './s3-upload.component.html',
  styleUrls: ['./s3-upload.component.css']
})
export class S3UploadComponent implements OnInit
{
  public httpUploadProgress: {[name: string]: any} = {
    ratio : 0,
    style : {
      width: '0',
    }
  };


  /**
   * @desc constructor
   */
  constructor(private s3Service: S3Service)
  {
    this.s3Service.initialize()
      .subscribe((res: boolean) => {
        if (! res) {
          console.log('S3 cognito init error');
        }
      })
  }


  /**
   * @desc Angular LifeCycle
   */
  ngOnInit()
  {
    this.s3Service.progress
      .subscribe((res: AWS.S3.ManagedUpload.Progress) => {
        this.httpUploadProgress.ratio = res.loaded * 100 / res.total;
        this.httpUploadProgress.style.width = this.httpUploadProgress.ratio + '%';
      });
  }


  /**
   * @desc file upload
   */
  public upload(event: Event): void
  {
    this.httpUploadProgress.ratio = 0;
    this.httpUploadProgress.style.width = '0';
    this.s3Service.onManagedUpload((<HTMLInputElement>event.target).files[0]);
  }
}

src/app/s3-upload/s3.service.ts

import { Injectable, EventEmitter } from '@angular/core';
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/of';
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/map';
import * as AWS from 'aws-sdk';
import { AWS_ENV } from '../../environments/environment';

@Injectable()
export class S3Service
{
  private s3: AWS.S3;
  public progress: EventEmitter<AWS.S3.ManagedUpload.Progress> = new EventEmitter<AWS.S3.ManagedUpload.Progress>();


  constructor(private http: HttpClient) { }


  /**
   * @desc set CognitoIdentityId
   * 
   * @return string IdentityId: ex) ap-northeast-1:01234567-9abc-df01-2345-6789abcd
   */
  public initialize(): Observable<boolean>
  {
    return this.http.get('/assets/cognito.php')
      .map((res: any) => {
        // resに対する例外処理を追加する
        // ...

        // Amazon Cognito 認証情報プロバイダーを初期化します
        AWS.config.region = AWS_ENV.region;
        AWS.config.credentials = new AWS.CognitoIdentityCredentials({
          IdentityId: res.results[0].IdentityId,
        });
        this.s3 = new AWS.S3({
          apiVersion: AWS_ENV.s3.apiVersion,
          params: {Bucket: AWS_ENV.s3.Bucket},
        });
        return true;
      })

      .catch((err: HttpErrorResponse) => {
        console.error(err);
        return Observable.of(false);
      });
  }

  /**
   * @desc AWS.S3
   */
  public onManagedUpload(file: File): Promise<AWS.S3.ManagedUpload.SendData>
  {
    let params: AWS.S3.Types.PutObjectRequest = {
      Bucket: AWS_ENV.s3.Bucket,
      Key: file.name,
      Body: file,
    };
    let options: AWS.S3.ManagedUpload.ManagedUploadOptions = {
      params: params,
      partSize: 64*1024*1024,
    };
    let handler: AWS.S3.ManagedUpload = new AWS.S3.ManagedUpload(options);
    handler.on('httpUploadProgress', this._httpUploadProgress.bind(this));
    handler.send(this._send.bind(this));
    return handler.promise();
  }

  /**
   * @desc progress
   */
  private _httpUploadProgress(progress: AWS.S3.ManagedUpload.Progress): void
  {
    this.progress.emit(progress);
  }

  /**
   * @desc send
   */
  private _send(err: AWS.AWSError, data: AWS.S3.ManagedUpload.SendData): void
  {
    console.log('send()', err, data);
  }
}

src/environments/environment.ts

// The file contents for the current environment will overwrite these during build.
// The build system defaults to the dev environment which uses `environment.ts`, but if you do
// `ng build --env=prod` then `environment.prod.ts` will be used instead.
// The list of which env maps to which file can be found in `.angular-cli.json`.

export const environment = {
  production: false
};

export const AWS_ENV = {
  region: 'YOUR_REGION',
  s3: {
    apiVersion: 'YOUR_VERSION',
    Bucket: 'YOUR_BACKET',
  },
};

5) ビルド&実行&確認

test.png

$ aws s3 ls s3://YOUR_BACKET/
2017-12-10 08:13:54   10485760 10MB.bin

解説

AWS SDKを使ってファイルをアップロードするには、PutObject()を使うのが手っ取り早いですが
JSからファイルをアップロードするときには、UI/UXの観点からプログレスを表示してあげるのがよいので
それに対応したメソッドである、ManagedUpload()を利用しました。

5.0.0 では、zoneを意識することなく、プログレスがきちんとレンダリングされましたので
プログレスバーの実装は容易に行なえます。

以上、穴埋め記事でした。

7日目は、@segawm さんです。

続きを読む

TerraformでAWS AMIの最新を常に使うようにする

TerraformとAWSに同時入門する
の続きで、

最新のAMIが常に指定されるようなami設定にする

を行えるようにします。

確認環境

$ terraform version
Terraform v0.11.1
+ provider.aws v1.5.0

取得するAMIのスペック

今回は2017/12/10時点でのAmazon Linuxの下記スペックにおける最新AMI ID(ami-da9e2cbc)を取得します。
最新AMI IDはこちらで確認できます: Amazon Linux AMI ID

  • リージョン: アジアパシフィック東京
  • 仮想化タイプ: HVM
  • ルートデバイスタイプ: EBS-Backed
  • アーキテクチャ: x86_64
  • ボリュームタイプ: gp2

:warning: 注意点
AMI IDはリージョン毎に異なるようなので複数リージョンにまたがった環境の場合は注意が必要かもしれません

結論

簡易版

フィルタ条件はAMI Image名の条件のみ

aws_ami.tf
data "aws_ami" "amazon_linux" {
  most_recent = true
  owners = ["amazon"]

  filter {
    name   = "name"
    values = ["amzn-ami-hvm-*-x86_64-gp2"]
  }
}

詳細版

フィルタ条件として今回の指定スペックをすべて指定

参考: Terraformでもいつでも最新AMIからEC2を起動したい

aws_ami.tf
data "aws_ami" "amazon_linux" {
  most_recent = true
  owners = ["amazon"]

  filter {
    name   = "architecture"
    values = ["x86_64"]
  }

  filter {
    name   = "root-device-type"
    values = ["ebs"]
  }

  filter {
    name   = "name"
    values = ["amzn-ami-hvm-*"]
  }

  filter {
    name   = "virtualization-type"
    values = ["hvm"]
  }

  filter {
    name   = "block-device-mapping.volume-type"
    values = ["gp2"]
  }
}

EC2側の設定

ec2.tf
resource "aws_instance" "web-server" {
  ami                    = "${data.aws_ami.amazon_linux.id}"
  instance_type          = "t2.micro"
}

確認

$ terraform plan

  + aws_instance.web-server
      id:                           <computed>
      ami:                          "ami-da9e2cbc"
      ...

AWS CLIでAmazon Linux AMI image名を取得してみる

準備

AWS Command Line Interface のインストール

簡易版のフィルタ条件の説明

AWS CLIで最新のAmazon LinuxのAMI IDを取得する
上記でのシェルを参考に今回取得したい条件に変更します

get-aws-ec2-image.sh.sh
...

describe_images(){
    echo $timestamp > $timestamp_file
    aws ec2 describe-images 
-       --owners self amazon 
+       --owners amazon 
        --filters 
+         Name=name,Values="amzn-ami-hvm-*" 
          Name=virtualization-type,Values=hvm 
          Name=root-device-type,Values=ebs 
          Name=architecture,Values=x86_64 
-         Name=block-device-mapping.volume-type,Values=standard
+         Name=block-device-mapping.volume-type,Values=gp2
}

...

実行結果

$ zsh ./get-aws-ec2-image.sh

amzn-ami-hvm-2014.03.2.x86_64-gp2: ami-df470ede
amzn-ami-hvm-2014.09.0.x86_64-gp2: ami-45072844
amzn-ami-hvm-2014.09.1.x86_64-gp2: ami-4585b044
amzn-ami-hvm-2014.09.2.x86_64-gp2: ami-1e86981f
amzn-ami-hvm-2015.03.0.x86_64-gp2: ami-cbf90ecb
amzn-ami-hvm-2015.03.1.x86_64-gp2: ami-1c1b9f1c
amzn-ami-hvm-2015.09.0.x86_64-gp2: ami-9a2fb89a
amzn-ami-hvm-2015.09.1.x86_64-gp2: ami-383c1956
amzn-ami-hvm-2015.09.2.x86_64-gp2: ami-59bdb937
amzn-ami-hvm-2016.03.0.x86_64-gp2: ami-f80e0596
amzn-ami-hvm-2016.03.1.x86_64-gp2: ami-29160d47
amzn-ami-hvm-2016.03.2.x86_64-gp2: ami-6154bb00
amzn-ami-hvm-2016.03.3.x86_64-gp2: ami-374db956
amzn-ami-hvm-2016.09.0.20160923-x86_64-gp2: ami-1a15c77b
amzn-ami-hvm-2016.09.0.20161028-x86_64-gp2: ami-0c11b26d
amzn-ami-hvm-2016.09.1.20161221-x86_64-gp2: ami-9f0c67f8
amzn-ami-hvm-2016.09.1.20170119-x86_64-gp2: ami-56d4ad31
amzn-ami-hvm-2017.03.0.20170401-x86_64-gp2: ami-859bbfe2
amzn-ami-hvm-2017.03.0.20170417-x86_64-gp2: ami-923d12f5
amzn-ami-hvm-2017.03.1.20170617-x86_64-gp2: ami-bbf2f9dc
amzn-ami-hvm-2017.03.1.20170623-x86_64-gp2: ami-3bd3c45c
amzn-ami-hvm-2017.03.1.20170812-x86_64-gp2: ami-4af5022c
amzn-ami-hvm-2017.03.rc-0.20170320-x86_64-gp2: ami-be154bd9
amzn-ami-hvm-2017.03.rc-1.20170327-x86_64-gp2: ami-10207a77
amzn-ami-hvm-2017.09.0.20170930-x86_64-gp2: ami-2a69be4c
amzn-ami-hvm-2017.09.1.20171103-x86_64-gp2: ami-2803ac4e
amzn-ami-hvm-2017.09.1.20171120-x86_64-gp2: ami-da9e2cbc
amzn-ami-hvm-2017.09.rc-0.20170913-x86_64-gp2: ami-d424e7b2

最新AMI ID(ami-da9e2cbc)が取得できているのがわかります。

amzn-ami-hvm-2017.09.1.20171120-x86_64-gp2: ami-da9e2cbc

取得結果からわかるようにImage名にはスペック名も含まれていることがわかります。

よって今回のスペックのAMI IDはImage名でのフィルタ条件のみで取ることができるため、簡易版ではImage名の値が"amzn-ami-hvm-*-x86_64-gp2"とするフィルタ条件1つで最新のAMI IDを取得していました。

また、aws_ami.tfではmost_recent = trueという設定により取得した中で最新のAMI IDが取得されます。

:warning: Image名の命名規則が変更された場合に指定スペックの最新が取得できなくなるので注意ください

フィルタ条件をnameのみにしてAWS CLIでも確認

AWS CLI側でも一応確認

get-aws-ec2-image.sh
describe_images(){
    echo $timestamp > $timestamp_file
    aws ec2 describe-images 
        --owners amazon 
        --filters 
          Name=name,Values="amzn-ami-hvm-*-x86_64-gp2" 
-          Name=virtualization-type,Values=hvm 
-          Name=root-device-type,Values=ebs 
-          Name=architecture,Values=x86_64 
-          Name=block-device-mapping.volume-type,Values=gp2
}
$ zsh ./get-aws-ec2-image.sh | grep ami-da9e2cbc

amzn-ami-hvm-2017.09.1.20171120-x86_64-gp2: ami-da9e2cbc

最新AMI ID(ami-da9e2cbc)が取得できているのがわかります。

その他の参考

続きを読む

17/12 千代田区/AWSなど】大手衣料系小売業のAWS環境構築支援

職種, SE. 場所, 千代田区. 必要スキル, ・コミュニケーションスキル・1人称で以下のOS/MWの設計・構築・試験ができること -OS : Amazon Linux / RHEL7 -WEB: Apache / Nginx -AP : Tomcat -DB : PostgreSQL. ・AWSの各サービスの概念理解(特にIAM) ・未経験の技術や製品に積極的に取り組める方. 期間, 11月~2018 … 続きを読む

AIで競輪予想 その3

AIで競輪予想 その2で、参考にしたページでうまくいかないので、別のやり方を模索。

以下の本を参考にした。


Pythonによるスクレイピング&機械学習 開発テクニック BeautifulSoup,scikit-learn,TensorFlowを使ってみよう

環境準備編

以下の順番で環境設定を行っていきます。
1.AWS(EC2)にubuntuを立ち上げる
2.MySql(5.6.16-1~exp1 (Ubuntu))のインストール
3.Pythonのインストール
4.seleniumのインストール

3.Pythonのインストール

shell
$ apt-get update
$ apt-get install -y python3 python3-pip

4.seleniumのインストール

shell
$ pip3 install selenium

4.BeautifulSoup4のインストール

shell
$ pip3 install beautifulsoup4

5.Phanton.JSのインストール

shell
$ apt-get install -y wget libfontconfig
$ mkdir -p /home/root/src && cd $_
$ wget https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-2.1.1-linux-x86_64.tar.bz2
$ tar jxvf phantomjs-2.1.1-linux-x86_64.tar.bz2
$ cd cd phantomjs-2.1.1-linux-x86_64/bin/
$ cp phantomjs /usr/local/bin/

6.日本語フォントのインストール

shell
$ apt-get install -y fonts-migmix

続きを読む