AnsibleでAWS操作 CloudFrontディストリビューション編

AnsibleでAWS操作シリーズ

  1. aws-cliインストール編
  2. EC2インスタンス編
  3. S3バケット編
  4. CloudFrontディストリビューション編

関連記事

aws-cli コマンド一覧(随時追記)

やりたかったこと

  • CloudFrontディストリビューションの作成
  • S3バケットとの連携
  • GUIを使わずに黒い画面でコマンドを「ッターーン!」してかっこつけたい

やったこと

前提

  • CIサーバー(ansible実行サーバー)構築済み
  • CLIサーバー(aws-cli実行サーバー)構築済み
  • Ansibleインストール済み
  • aws-cliインストール済み
  • 各サーバーへのSSH接続設定済み

${~}は各環境に合わせて値を設定してください。

作業フロー

1. CloudFrontディストリビューションを新規作成

command
ansible-playbook -i inventory/production create-aws-cf-distribution.yml

ディレクトリ構成


├── ansible.cfg
├── create-aws-cf-distribution.yml
├── files
│   └── production
│       └── cf
│           └── distribution.json
├── inventory
│   └── production
│       └── inventory
├── roles
│   └── create-aws-cf-distribution
│       └── tasks
│           └── main.yml
└── vars
    └── all.yml

Ansible構成ファイル

inventory

inventory/production/inventory
[ciservers]
${CIサーバーホスト}

[cliservers]
${CLIサーバーホスト}

[all:vars]
ENV=production

vars

vars/all.yml
AWS:
  S3:
    BUCKET:
      NAME: ${バケット名}

files

files/production/cf/distribution.json
{
  "Id": "",
  "IfMatch": "",
  "DistributionConfig": {
    "Comment": "For S3 Bucket.",
    "CacheBehaviors": {
      "Quantity": 0
    },
    "IsIPV6Enabled": true,
    "Logging": {
      "Bucket": "",
      "Prefix": "",
      "Enabled": false,
      "IncludeCookies": false
    },
    "WebACLId": "",
    "Origins": {
      "Items": [
        {
          "S3OriginConfig": {
            "OriginAccessIdentity": ""
          },
          "OriginPath": "",
          "CustomHeaders": {
            "Quantity": 0
          },
          "Id": "${一意となるID ※1}",
          "DomainName": "${S3バケットの静的サイトホスティングのエンドポイント}"
        }
      ],
      "Quantity": 1
    },
    "DefaultRootObject": "index.html",
    "PriceClass": "PriceClass_All",
    "Enabled": true,
    "DefaultCacheBehavior": {
      "TrustedSigners": {
        "Enabled": false,
        "Quantity": 0
      },
      "LambdaFunctionAssociations": {
        "Quantity": 0
      },
      "TargetOriginId": "${※1で指定したID}",
      "ViewerProtocolPolicy": "allow-all",
      "ForwardedValues": {
        "Headers": {
          "Quantity": 0
        },
        "Cookies": {
          "Forward": "all"
        },
        "QueryStringCacheKeys": {
          "Quantity": 0
        },
        "QueryString": true
      },
      "MaxTTL": ${最大有効TTL},
      "SmoothStreaming": false,
      "DefaultTTL": ${デフォルトTTL},
      "AllowedMethods": {
        "Items": [
          "HEAD",
          "GET"
        ],
        "CachedMethods": {
          "Items": [
            "HEAD",
            "GET"
          ],
          "Quantity": 2
        },
        "Quantity": 2
      },
      "MinTTL": ${最小TTL},
      "Compress": false
    },
    "CallerReference": "${一意となる文字列}",
    "ViewerCertificate": {
      "CloudFrontDefaultCertificate": true,
      "MinimumProtocolVersion": "SSLv3",
      "CertificateSource": "cloudfront"
    },
    "CustomErrorResponses": {
      "Quantity": 0
    },
    "HttpVersion": "http2",
    "Restrictions": {
      "GeoRestriction": {
        "RestrictionType": "none",
        "Quantity": 0
      }
    },
    "Aliases": {
      "Items": [
        "${割り当てたいドメイン ※Route53にて紐付ける際に利用}"
      ],
      "Quantity": 1
    }
  }
}

playbook

create-aws-cf-distribution
- hosts: cliservers
  roles:
    - create-aws-cf-distribution
  vars_files:
    - vars/all.yml

tasks

role/create-aws-cf-distribution/tasks/main.yml
- name: Create Distribution
  shell: |
    aws cloudfront create-distribution \
    --cli-input-json file://files/{{ ENV }}/cf/distribution.json
  register: result
  changed_when: False

- debug: var=result.stdout_lines
  when: result | success
  tags:
    - always

終わりに

これで、S3バケットをCloudFront経由で表示することが可能になります。
キャッシュを有効利用出来るので、 表示スピードが上がりストレスフリー なページを構築出来ます。

設定用のJsonが少し複雑なので、本記事のJsonをベースに調べながらカスタマイズしてもらえればなと思います。

ただ、このままだとCloudFrontのTTLが効いているのでオブジェクトを更新しても 即時反映 がされません。
また、URLについても、 xxxx.cloudfront.net のようなドメインになっています。

前者については、AWS LambdaによってオブジェクトがPutされたことをトリガーにInvalidationをかけることで解決可能です。(毎回手動でやるのは 運用コスト がかかるので自動化をお勧めします)

後者については、Route53を設定することで独自ドメインによるアクセスが可能になります。

上記の作業についても、aws-cliからの動作確認は済んでいるので時間があるときに記事にまとめようと思っています♪

じゃあの。

続きを読む

AnsibleでAWS操作 S3バケット編

AnsibleでAWS操作シリーズ

  1. aws-cliインストール編
  2. EC2インスタンス編
  3. S3バケット編
  4. CloudFrontディストリビューション編

関連記事

aws-cli コマンド一覧(随時追記)

やりたかったこと

  • S3バケットの作成/削除
  • S3バケットへのオブジェクトの転送/削除
  • S3バケットを静的Webサイトホスティング化
  • GUIを使わずに黒い画面でコマンドを「ッターーン!」してかっこつけたい

やったこと

前提

  • CIサーバー(ansible実行サーバー)構築済み
  • CLIサーバー(aws-cli実行サーバー)構築済み
  • Ansibleインストール済み
  • aws-cliインストール済み
  • 各サーバーへのSSH接続設定済み

${~}は各環境に合わせて値を設定してください。

作業フロー

1. S3バケットを新規作成

command
ansible-playbook -i inventory/production create-aws-s3-bucket.yml

2. S3バケットをWebサイトホスティング化

command
ansible-playbook -i inventory/production setup-aws-s3-bucket.yml

3. オブジェクトを転送

command
ansible-playbook -i inventory/production upload-aws-s3-object.yml

4. オブジェクトの転送確認

command
ansible-playbook -i inventory/production view-aws-s3-object.yml

5. オブジェクトの削除

command
ansible-playbook -i inventory/production delete-aws-s3-object.yml

6. バケットの削除

command
ansible-playbook -i inventory/production delete-aws-s3-bucket.yml

ディレクトリ構成


├── ansible.cfg
├── create-aws-s3-bucket.yml
├── delete-aws-s3-bucket.yml
├── delete-aws-s3-object.yml
├── files
│   └── production
│       └── s3
│           └── sample.txt
├── inventory
│   └── production
│       └── inventory
├── roles
│   ├── create-aws-s3-bucket
│   │   └── tasks
│   │       └── main.yml
│   ├── delete-aws-s3-bucket.yml
│   │   └── tasks
│   │       └── main.yml
│   ├── delete-aws-s3-object.yml
│   │   └── tasks
│   │       └── main.yml
│   ├── setup-aws-s3-bucket.yml
│   │   └── tasks
│   │       └── main.yml
│   ├── upload-aws-s3-bucket.yml
│   │   └── tasks
│   │       └── main.yml
│   └── view-aws-s3-object.yml
│       └── tasks
│           └── main.yml
├── upload-aws-s3-object.yml
├── setup-aws-s3-bucket.yml
├── vars
│   └── all.yml
└── view-aws-s3-object.yml

Ansible構成ファイル

inventory

inventory/production/inventory
[ciservers]
${CIサーバーホスト}

[cliservers]
${CLIサーバーホスト}

[all:vars]
ENV=production

vars

vars/all.yml
AWS:
  S3:
    BUCKET:
      NAME: ${バケット名}

playbook

create-aws-s3-bucket
- hosts: cliservers
  roles:
    - create-aws-s3-bucket
  vars_files:
    - vars/all.yml
delete-aws-s3-bucket
- hosts: cliservers
  roles:
    - delete-aws-s3-bucket
  vars_files:
    - vars/all.yml
delete-aws-s3-object
- hosts: cliservers
  roles:
    - delete-aws-s3-object
  vars_files:
    - vars/all.yml
setup-aws-s3-bucket
- hosts: cliservers
  roles:
    - setup-aws-s3-bucekt
  vars_files:
    - vars/all.yml
upload-aws-s3-object
- hosts: cliservers
  roles:
    - update-aws-s3-bucket
  vars_files:
    - vars/all.yml
view-aws-s3-object
- hosts: cliservers
  roles:
    - view-aws-s3-object
  vars_files:
    - vars/all.yml

tasks

role/create-aws-s3-bucket/tasks/main.yml
- name: Create Bucket
  shell: "aws s3 mb s3://{{ AWS.S3.BUCKET.NAME }}"
  register: result
  changed_when: False

- debug: var=result.stdout_lines
  when: result | success
  tags:
    - always
role/delete-aws-s3-bucket/tasks/main.yml
- name: "Remove Bucket"
  shell: "aws s3 rb s3://{{ AWS.S3.BUCKET.NAME }}"
  register: result
  changed_when: False

- debug: var=result.stdout_lines
  when: result | success
  tags:
    - always
role/delete-aws-s3-bucket/tasks/main.yml
- name: "Delete Bucket"
  shell: "aws s3 rb s3://{{ AWS.S3.BUCKET.NAME }}"
  register: result
  changed_when: False

- debug: var=result.stdout_lines
  when: result | success
  tags:
    - always
role/delete-aws-s3-object/tasks/main.yml
- name: "Delete Object"
  shell: "aws s3 rm s3://{{ AWS.S3.BUCKET.NAME }}/sample.txt"
  register: result
  changed_when: False

- debug: var=result.stdout_lines
  when: result | success
  tags:
    - always
role/setup-aws-s3-bucket/tasks/main.yml
- name: "Setup Bucket"
  shell: |
    aws s3 website s3://{{ AWS.S3.BUCKET.NAME }} \
    --index-document index.html
  register: result
  changed_when: False

- debug: var=result.stdout_lines
  when: result | success
  tags:
    - always
role/upload-aws-s3-object/tasks/main.yml
- name: "Upload Object"
  shell: "aws s3 cp files/{{ ENV }}/s3/sample.txt s3://{{ AWS.S3.BUCKET.NAME }}"
  register: result
  changed_when: False

- debug: var=result.stdout_lines
  when: result | success
  tags:
    - always
role/view-aws-s3-object/tasks/main.yml
- name: "View Objects"
  shell: "aws s3 ls s3://{{ AWS.S3.BUCKET.NAME }}"
  register: result
  changed_when: False

- debug: var=result.stdout_lines
  when: result | success
  tags:
    - always

終わりに

S3のバケット作成やファイル操作も 簡単 に行うことが可能で、 静的サイト として利用する場合のためのindexドキュメントの指定などのオプションもあります。

また、今回は触れませんでしたが転送したファイル単位で パーミッション の設定も出来るので柔軟性もあります。

また、CloudFrontと組み合わせればキャッシュを有効利用してパフォーマンス向上を図ることも可能です。

S3バケットは 静的コンテンツのホスティング簡易ファイルサーバーWordpressの記事をhtml化 したりとかなり汎用性が高く、 料金もかなり良心的 なのでどんどん利用していきましょう♪

じゃあの。

続きを読む

Running Kubernetes Cluster on AWS

Following this guide https://kubernetes.io/docs/getting-started-guides/kops/, set up Kubernetes cluster on AWS using kops.

1. Install kops

According to the guide, kops is:

kops helps you create, destroy, upgrade and maintain production-grade, hig… 続きを読む

ElasticseharchのEC2 Discovery Pluginでクラスタ組むのが捗るはなし

こんばんはー

今回、ElasticsearchのクラスタをAWS上に組む際に便利なブラグインを使ってみたいと思いますー
その名も「EC2 Discovery Plugin」☆彡

調べているとAWS Cloud Pluginの使い方を書いている人は結構いるのですが、5系から提供されているEC2 Discovery Pluginはあんまいないのかなーって思いました。
なので、EC2 Discovery Pluginについてつらつら書きたいと思います( ゚Д゚)ゞビシッ

そもそもEC2 Discovery Pluginは何が便利なん?

Elasticsearchのデフォルトで用意されているZen Discoveryを利用してクラスタを組む際は、Unicastでホスト名やIPアドレス指定で組みます。
ただ、クラウド上の運用を考えるとUnicastはつらい。。
そこで、EC2 Discovery Pluginを使うとSecurityGroupを元にクラスタが組まれるのですー(d゚ω゚d)オゥイェー
Multicast Discovery pluginというのもあったのですが、5系から廃止されました

早速ですが、構成の説明したら実装方法を説明したいと思いますー

インスタンス構成

  • InstanceType:t2.medium
  • OS:Amazon Linux AMI release 2017.03
  • Node: 3台
  • ClusterName: es-cluster

こんな感じの流れで書いちゃいますー

  1. AWS準備

  2. Install Oracle JDK 1.8

  3. Install Elasticsearch

  4. Install Kibana(1台のみ)

  5. Install X-pack Elasticsearch & kibana(1台のみ)

  6. Install EC2 Discovery Plugin

  7. 動作確認

AWS準備

SecurityGroup

ElasticsearchをインストールするインスタンスにアタッチするSecurityGroupです。
ソースは、環境に合わせて設定してください。

Type Protocol Port Source Description
Custom TCP 9200 xx Elasticsearchアクセスポート
Custom TCP 9300 xx ノード間のコミュニケーションポート
Custom TCP 5601 xx KibanaのHTTP用ポート
Custom TCP 22 xx SSH用ポート

IAM Role

以下のPolicyを作成し、インスタンスにアタッチしているIAM RoleにPolicyをアタッチしてください。

{
    "Statement": [
        {
            "Action": [
                "ec2:DescribeInstances"
            ],
            "Effect": "Allow",
            "Resource": [
                "*"
            ]
        }
    ],
    "Version": "2012-10-17"
}

AWS Configure

サーバにログインしたらAWS Configureの実行をします。
Access KeyとSecret keyは利用しないため、何も入力しません。

$ aws configure
AWS Access Key ID [None]:
AWS Secret Access Key [None]:
Default region name [None]: ap-northeast-1
Default output format [None]: json

下準備が終わったので、ここからElasticsearch周りを進めたいと思いますー

Install Oracle JDK 1.8

$ java -version
java version "1.7.0_141"
OpenJDK Runtime Environment (amzn-2.6.10.1.73.amzn1-x86_64 u141-b02)
OpenJDK 64-Bit Server VM (build 24.141-b02, mixed mode)

### Install Java1.8
$ sudo yum -y install java-1.8.0-openjdk-devel

### alternativesコマンドでJavaのバージョンを切り替える
$ sudo alternatives --config java

There are 2 programs which provide 'java'.

  Selection    Command
-----------------------------------------------
*+ 1           /usr/lib/jvm/jre-1.7.0-openjdk.x86_64/bin/java
   2           /usr/lib/jvm/jre-1.8.0-openjdk.x86_64/bin/java

Enter to keep the current selection[+], or type selection number: 2

### バージョン確認
$ java -version
openjdk version "1.8.0_141"
OpenJDK Runtime Environment (build 1.8.0_141-b16)
OpenJDK 64-Bit Server VM (build 25.141-b16, mixed mode)

Install Elasticsearch

Elasticsearch5.5.1を入れちゃいたいと思いますー

Install Elasticsearch

### PGP Keyをダウンロード&インストール
$ sudo rpm --import https://artifacts.elastic.co/GPG-KEY-elasticsearch

### リポジトリの登録
$ sudo vim /etc/yum.repos.d/elastic.repo
[elasticsearch-5.x]
name=Elasticsearch repository for 5.x packages
baseurl=https://artifacts.elastic.co/packages/5.x/yum
gpgcheck=1
gpgkey=https://artifacts.elastic.co/GPG-KEY-elasticsearch
enabled=1
autorefresh=1
type=rpm-md

### Elasticsearchインストール
$ sudo yum -y install elasticsearch

### サービス起動登録
$ sudo chkconfig --add elasticsearch

### 確認
$ sudo chkconfig --list | grep elasticsearch
elasticsearch   0:off   1:off   2:on    3:on    4:on    5:on    6:off

### elasticsearch.yml設定変更
$ sudo vim /etc/elasticsearch/elasticsearch.yml
+ cluster.name: es-cluster
+ network.host: 0.0.0.0
+ http.port: 9200

### サービス起動
$ sudo service elasticsearch start
Starting elasticsearch:                                    [  OK  ]

### 起動確認
$ curl http://localhost:9200
{
  "name" : "fDpNQ4m",
  "cluster_name" : "es",
  "cluster_uuid" : "mayxoDENThSmrUltkXyRWg",
  "version" : {
    "number" : "5.5.1",
    "build_hash" : "19c13d0",
    "build_date" : "2017-07-18T20:44:24.823Z",
    "build_snapshot" : false,
    "lucene_version" : "6.6.0"
  },
  "tagline" : "You Know, for Search"
}

Install Kibana

モニタリングしたいのでKibana5.5.1をインストールします。

### Install Kibana
$ sudo yum install -y kibana
### サービス登録
$ sudo chkconfig --add kibana
$ chkconfig --list | grep kibana
kibana          0:off   1:off   2:on    3:on    4:on    5:on    6:off
### Kibana設定
$ sudo vim /etc/kibana/kibana.yml
+ server.host: 0.0.0.0
+ elasticsearch.url: "http://ES_IP_ADDR"
### サービス停止状態のままにしておきます(X-Pack入れたあとに設定変更後に起動します)
$ service kibana status
kibana is not running

Install X-Pack on Elasticsearch

Elasticsearchのノード状態などをモニタリングしたいため、X-Pack5.5をインストールします。

Install X-Pack on Elasticsearch

$ sudo /usr/share/elasticsearch/bin/elasticsearch-plugin install x-pack
-> Downloading x-pack from elastic
[=================================================] 100%  
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@     WARNING: plugin requires additional permissions     @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
* java.io.FilePermission \.pipe* read,write
* java.lang.RuntimePermission accessClassInPackage.com.sun.activation.registries
* java.lang.RuntimePermission getClassLoader
* java.lang.RuntimePermission setContextClassLoader
* java.lang.RuntimePermission setFactory
* java.security.SecurityPermission createPolicy.JavaPolicy
* java.security.SecurityPermission getPolicy
* java.security.SecurityPermission putProviderProperty.BC
* java.security.SecurityPermission setPolicy
* java.util.PropertyPermission * read,write
* java.util.PropertyPermission sun.nio.ch.bugLevel write
* javax.net.ssl.SSLPermission setHostnameVerifier
See http://docs.oracle.com/javase/8/docs/technotes/guides/security/permissions.html
for descriptions of what these permissions allow and the associated risks.

Continue with installation? [y/N]y
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@        WARNING: plugin forks a native controller        @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
This plugin launches a native controller that is not subject to the Java
security manager nor to system call filters.

Continue with installation? [y/N]y
-> Installed x-pack

動作確認するのですが、Securityが有効になったため、エラーが返ってきます。
なので、今回は、Securityを無効にします。
(モニタリング用途で使いたいだけなので―)

ちなみに、X-Packについては、クラスメソッドさんが詳しく書いてますー
クラスメソッド:Elastic Stack X-Pack

X-Pack Security無効化

無効化することで、レスポンスが返ってきます。

### Security無効化
$ vim /etc/elasticsearch/elasticsearch.yml
+ xpack.security.enabled: false

### 動作確認
$ curl -u elastic http://localhost:9200
{
  "name" : "fDpNQ4m",
  "cluster_name" : "es",
  "cluster_uuid" : "mayxoDENThSmrUltkXyRWg",
  "version" : {
    "number" : "5.5.1",
    "build_hash" : "19c13d0",
    "build_date" : "2017-07-18T20:44:24.823Z",
    "build_snapshot" : false,
    "lucene_version" : "6.6.0"
  },
  "tagline" : "You Know, for Search"
}

### 再起動
$ service kibana restart
kibana stopped.
kibana started

Install X-Pack on Kibana

KibanaのX-Pack5.5もインストールします。

Install X-Pack on Kibana

$ sudo /usr/share/kibana/bin/kibana-plugin install x-pack
Attempting to transfer from x-pack
Attempting to transfer from https://artifacts.elastic.co/downloads/kibana-plugins/x-pack/x-pack-5.5.1.zip
Transferring 119276972 bytes....................
Transfer complete
Retrieving metadata from plugin archive
Extracting plugin archive
Extraction complete

Install EC2 Discovery Plugin

ここからEC2 Discovery Pluginをインストールします。
やっとここまできましたね!

$ sudo /usr/share/elasticsearch/bin/elasticsearch-plugin install discovery-ec2
-> Downloading discovery-ec2 from elastic
[=================================================] 100%  
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@     WARNING: plugin requires additional permissions     @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
* java.lang.RuntimePermission accessDeclaredMembers
* java.lang.RuntimePermission getClassLoader
See http://docs.oracle.com/javase/8/docs/technotes/guides/security/permissions.html
for descriptions of what these permissions allow and the associated risks.

Continue with installation? [y/N]y
-> Installed discovery-ec2

Elasticsearch.ymlに設定します。

$ sudo vim /etc/elasticsearch/elasticsearch.yml
cluster.name: es-cluster
discovery.zen.hosts_provider: ec2
discovery.ec2.groups: "SECURITY_GROUP_NAME" or "SECURITY_GROUP_ID"
discovery.ec2.availability_zones: [ "ap-northeast-1a", "ap-northeast-1c" ]
cloud.aws.region: ap-northeast-1

### サービス再起動
service elasticsearch restart

### クラスタ状態確認
$ curl http://localhost:9200/_cluster/health?pretty
{
  "cluster_name" : "es-cluster",
  "status" : "green",
  "timed_out" : false,
  "number_of_nodes" : 1,
  "number_of_data_nodes" : 1,
  "active_primary_shards" : 0,
  "active_shards" : 0,
  "relocating_shards" : 0,
  "initializing_shards" : 0,
  "unassigned_shards" : 0,
  "delayed_unassigned_shards" : 0,
  "number_of_pending_tasks" : 0,
  "number_of_in_flight_fetch" : 0,
  "task_max_waiting_in_queue_millis" : 0,
  "active_shards_percent_as_number" : 100.0
}

2台目&3台目いくよ!!

2台目も上記と同じで設定を実施しますので割愛しちゃいますm(_ _)m
SecurityGroupは、1台目と同じのを使ってください。
また、ElasticsearchのX-Packは、3台すべてに入れてください。
Kibanaは、インストールしないです。

もろもろ設定して、Elasticsearchを再起動するとログにクラスタが組み込まれたことがわかります。

クラスタに組み込まれているかをログから確認

### Master側のログ
[20xx-xx-xxxx:xx:xx][INFO ][o.e.c.s.ClusterService   ] [fDpNQ4m] added {{gdRR6r1}{gdRR6r17T5iaGPmaO9wgTA}{6l3QNArhSkyBVu08jTfwYQ}{IP_ADDR}{IP_ADDR:9300},}, reason: zen-disco-receive(from master [master {dKgmQfLLg}{IP_ADDR}{IP_ADDR:9300} committed version [15]])

### Node側のログでマスタを検出したことがわかる
$ tail -f /var/log/elasticsearch/es.log
[20xx-xx-xxxx:xx:xx][INFO ][o.e.c.s.ClusterService   ] [gdRR6r1] detected_master {dKgmQfL}{dKgmQfLASM-35x0X0ulSXg}{XE4jgXEzQwqgkSfRypgoLg}{IP_ADDR}{IP_ADDR:9300}, added {{fDpNQ4m}{fDpNQ4mrRR2vN7wiTVjfIg}{aqPbmbhbTkm1X40gH1tylw}{IP_ADDR}{IP_ADDR:9300},{dKgmQfL}{dKgmQfLASM-35x0X0ulSXg}{XE4jgXEzQwqgkSfRypgoLg}{IP_ADDR}{IP_ADDR:9300},}, reason: zen-disco-receive(from master [master {dKgmQfL}{dKgmQfLASM-35x0X0ulSXg}{XE4jgXEzQwqgkSfRypgoLg}{IP_ADDR}{IP_ADDR:9300} committed version [15]])

Kibanaのモニタリングで確認するよ

Kibana [http://KIBANA_IP_ADDR:5601] にアクセスします。

「Monitoring」をクリックし、ElasticsearchにNodeが3台あることがわかりますね。
ステータス:Green

kibana01.png

「Nodes」をクリックし、Nodeの状態を確認できます。

kibana02.png

無事にクラスタを組むことができましたヽ(*゚д゚)ノ

補足#01

AWS Configureの設定とIAM Roleにポリシーをアタッチしないと以下のエラーがでます。
ちなみに、作成したクラスタ名でログが作成されちゃいます。

$ sudo /var/log/elasticsearch/es-cluster.log
[20xx-xx-xxxx:xx:xx][INFO ][o.e.d.e.AwsEc2UnicastHostsProvider] [node_id] Exception while retrieving instance list from AWS API: Unable to execute HTTP request: connect timed out

補足#02

既に存在しているNodeIDのAMIから起動すると、NodeIDがかぶっているためクラスタに組み込むことができないです。。
その際のログが以下です。

### AMIから起動したノード
$ tail -f /var/log/elasticsearch/es.log
[20xx-xx-xxxx:xx:xx][INFO ][o.e.d.z.ZenDiscovery     ] [gdRR6r1] failed to send join request to master [{fDpNQ4m}{fDpNQ4mrRR2vN7wiTVjfIg}{eHfV5HLkRrKo8_FXfgyHDA}{IP_ADDR}{IP_ADDR:9300}{ml.enabled=true}], reason [RemoteTransportException[[fDpNQ4m][IP_ADDR:9300][internal:discovery/zen/join]]; nested: IllegalArgumentException[can't add node {gdRR6r1}{gdRR6r17T5iaGPmaO9wgTA}{hzzXQXB8TM-xQVn9Uj8e2A}{IP_ADDR}{IP_ADDR:9300}{ml.enabled=true}, found existing node {gdRR6r1}{gdRR6r17T5iaGPmaO9wgTA}{9pWjYpL5Tq23yIte7WzMiw}{IP_ADDR}{IP_ADDR:9300}{ml.enabled=true} with the same id but is a different node instance]; ]

さいごに

EC2 Discovery Pluginいかがでしたか?
簡単にクラスタを組むことができたんじゃないかなと思います。

ただ、個人的には、運用を考えると自動復旧できる構成にする必要があると思ってます。
今の状態だとElasticsearchのクラスタ化する時にNodeIDがかぶっちゃうので、AMIからの自動復旧がむずかしい状態です。
なので、Elasticsearchをインストールする前のAMIからプロビジョニングして、クラスタに組み込む必要があるかなと。
(Elasticsearchインストール時にNodeIDが振られる)

うーん。。NodeIDを変更する方法が他にあればいいのですが、誰か知っている人いたら教えてくださいm(_ _)m

てことで、今回は、ElasticsearchのプラグインのEC2 Discovery Pluginについて書かせて頂きました。
ありがとうございましたー

続きを読む

Rails5アプリケーションのAWSによるネットワーク構築 Nginx+Puma+Capistranoな環境とAWS構築(VPC EC2 RDS CloudFlont Route53 etc)

はじめに

数ヶ月前に書いたRails5のAWS構築をもう少し項目を増やしてから公開しようと思っていたのですが、なかなか出来ないのでもう公開します。(ELBとかCloudFlontとかもっとちゃんと書けそう、、)
項目の追加や修正は適宜入れると思います。

Railsは5.0を使っています。(記事を書いたときの最新)

4系は以前書きました↓

構築する環境

  • VPC
  • EC2
    • Nginx + Puma
    • Capistrano
  • (S3)
  • ELB
  • RDS
  • CloudFlont
  • Route53
  • (CloudWatch)

VPC、EC2のインスタンスの作成

AWSのデザインが多少変更はありますが、以下を参考に作成出来ます。

AWS VPCによるネットワーク構築とEC2によるサーバー構築

これに追加でElastic IPの設定もしておきました。

EC2内の環境構築

作成したEC2にログインして環境を構築します。
ec2-userでまずログインしましょう。

ユーザーの作成

デフォルトではec2-userなので新たなユーザーを追加し、そのユーザーでsshログイン出来るように設定します。

$ sudo adduser shizuma #ユーザーの追加 「shizuma」の部分は好きなユーザー名にします。以後「shizuma」の部分は各自のユーザー名になります。
$ sudo passwd shizuma
#ここで、新規ユーザーのパスワードを設定します。
$ sudo visudo
-----------------------------
#vimが起動するので新規ユーザーにroot権限を与える。
root    ALL=(ALL)       ALL
shizuma ALL=(ALL)       ALL #この行を追加
-----------------------------
$ sudo su - shizuma #ユーザー切り替え
#先ほど設定したパスワード

ここでローカルに一旦戻り、鍵を作成します。

$ cd .ssh
$ ssh-keygen -t rsa
-----------------------------
Enter file in which to save the key ():first_aws_rsa #ここでファイルの名前を記述して、エンター
Enter passphrase (empty for no passphrase): #何もせずそのままエンター
Enter same passphrase again: #何もせずそのままエンター
-----------------------------
$ vi config
-----------------------------
# 以下を追記
Host first_aws
  Hostname 54.64.22.197 #自分の設定に合わせて
  Port 22
  User shizuma #先ほどのユーザー名
  IdentityFile ~/.ssh/first_aws_rsa #秘密鍵の設定
-----------------------------

次に、サーバーに戻り作成した 公開鍵 をサーバーに設定します。

$ mkdir .ssh
$ chmod 700 .ssh
$ cd .ssh
$ vi authorized_keys
-----------------------------
#localの「first_aws_rsa.pub」の中身のコピペ。(localで $ cat first_aws_rsa.pubとかすると良い)
ssh-rsa sdfjerijgviodsjcIKJKJSDFJWIRJGIUVSDJFKCNZKXVNJSKDNVMJKNSFUIEJSDFNCJSKDNVJKDSNVJNVJKDSNVJKNXCMXCNMXNVMDSXCKLMKDLSMVKSDLMVKDSLMVKLCA shizuma@shizuma-no-MacBook-Air.local
-----------------------------
$ chmod 600 authorized_keys
$ exit
$ exit

これで設定が完了したので、以降作成したユーザーでアクセスするようにします。

基本ライブラリとrubyの環境構築

$ sudo yum install 
git make gcc-c++ patch 
openssl-devel 
libyaml-devel libffi-devel libicu-devel 
libxml2 libxslt libxml2-devel libxslt-devel 
zlib-devel readline-devel 
mysql mysql-server mysql-devel 
ImageMagick ImageMagick-devel 
epel-release
$ sudo yum install nodejs npm --enablerepo=epel
$ git clone https://github.com/sstephenson/rbenv.git ~/.rbenv
$ echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bash_profile
$ echo 'eval "$(rbenv init -)"' >> ~/.bash_profile
$ source .bash_profile
$ git clone https://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build
$ rbenv rehash
$ rbenv install -v 2.3.1
$ rbenv global 2.3.1
$ rbenv rehash
$ ruby -v

gitの設定

最低限の設定だけしておきます。

$ vi .gitconfig
.gitignore
[user]
  name = your_name #自分の名前
  email = hoge@hoge.com #自分のメアド

[alias] #これはお好きに
  a = add
  b = branch
  ch = checkout
  st = status

[color] #色付け
  ui = true

[url "github:"] #pull、pushのための設定
    InsteadOf = https://github.com/
    InsteadOf = git@github.com:

アプリケーションフォルダの設置

/var/www/rails にアプリケーションフォルダを設置します。

$ cd /
$ sudo mkdir -p /var/www/rails
$ sudo chown -R shizuma var/www

GitHubの接続とアプリケーションのclone

$ cd ~/.ssh
$ ssh-keygen -t rsa
-----------------------------
Enter file in which to save the key ():aws_git_rsa #ここでファイルの名前を記述して、エンター
Enter passphrase (empty for no passphrase): #何もせずそのままエンター
Enter same passphrase again: #何もせずそのままエンター
-----------------------------
$ chmod 744 config
$ vi config
-----------------------------
# 以下を追記
Host github github.com
  Hostname github.com
  User git
  Port 22
  IdentityFile ~/.ssh/aws_git_rsa #秘密鍵の設定
-----------------------------
$ cat aws_git_rsa.pub
-----------------------------
ssh-rsa sdfjerijgviodsjcIKJKJSDFJWIRJGIUVSDJFKCNZKXVNJSKDNVMJKNSFUIEJSDFNCJSKDNVJKDSNVJNVJKDSNVJKNXCMXCNMXNVMDSXCKLMKDLSMVKSDLMVKDSLMVKLCA shizuma@ip-10-0-1-10
-----------------------------

ここで、これをコピペしてgithubに公開鍵を登録する。
githubへの鍵の登録がよくわからない方は以下の記事を参考に。
gitHubでssh接続する手順~公開鍵・秘密鍵の生成から~

そして、git clone する。

$ cd /var/www/rails
$ git clone https://github.com/kuboshizuma/cheerfull # 自分のアプリケーション

RDSの設定

EC2の環境構築に一区切りついたのでRDSの設定を行います。

DBサブネットの登録

RDSで使うサブネットを登録します。
この設定には異なるアベイラビリティゾーンにあるサブネットが最低1つずつ計2つ必要になります。
また、Railsアプリケーションをおいたサブネットはゲートウェイに繋がったpublicなサブネットなので、privateなサブネットを異なるアベイラビリティゾーンに一つずつ作成します。

スクリーンショット_2017-03-04_11_56_01.png

パラメータグループの設定

パラメータグループを設定します。
mysqlを使用していると設定なので、ここではmysqlを選択します。バージョンは適宜選択。
「chara…」で検索すると出て来るcharasetをutf-8に変更。

スクリーンショット_2017-03-04_12_00_57.png

設定が完了した後に、パラメータの編集を押してパラメーター変更。

スクリーンショット_2017-03-04_12_04_29.png

セキュリティグループの作成

VPCでセキュリティグループを作成します。

スクリーンショット_2017-03-04_12_08_19.png

インバウンドルールにMySQLを設定し、MySQLのアクセスのみ許可します。
「送信元」は0.0.0.0/0で設定。特定のRailsアプリケーションサーバーがあるサブネットに限定するほうがよさそう。

スクリーンショット 2017-03-04 12.55.42.png

RDSインスタンスの作成

エンジンの選択

スクリーンショット_2017-03-04_12_12_05.png

本番稼働用?

スクリーンショット_2017-03-04_12_12_45.png

DB詳細設定の指定

無料枠はt2.micro

スクリーンショット_2017-03-04_12_17_35.png

詳細設定の設定

各種作成したもので設定。

スクリーンショット_2017-03-04_12_19_44.png

スクリーンショット_2017-03-04_12_19_53.png

接続確認

hostは各自の作成したRDSインスタンスのエンドポイントをみる。

$ mysql -h hogepoge.ap-northeast-1.rds.amazonaws.com -u shizuma -P 3306 -p

接続出来たら完了!

絵文字の扱い

絵文字も登録出来るようにする場合。
以下を参考に utfmb4を採用する。

ActiveRecordをutf8mb4で動かす

MAMPでは /Applications/MAMP/conf/my.cnfmy.cnf を設置した。

Railsアプリケーションの起動のための準備

puma setting

以下を追記する。

cofig/puma.rb
# add setting for production
_proj_path = "#{File.expand_path("../..", __FILE__)}"
_proj_name = File.basename(_proj_path)
_home = ENV.fetch("HOME") { "/home/#{ENV['PUMA_USER']}" }
pidfile "#{_home}/run/#{_proj_name}.pid"
bind "unix://#{_home}/run/#{_proj_name}.sock"
directory _proj_path
# add end

ENV['PUMA_USER'] にサーバーのユーザー名が入るように環境変数を設定。

database setting

以下のように編集。

config/database.yml
production:
  <<: *default
  database: cheerfull
  username: <%= ENV['DATABASE_USER_NAME_PRODUCTION'] %>
  password: <%= ENV['DATABASE_PASSWORD_PRODUCTION'] %>
  host: <%= ENV['DATABASE_HOST_PRODUCTION'] %>

それぞれ該当の環境変数を設定。

rake secret

以下のsecret keyの箇所に値が入るように環境変数を設定。
rake secret とコマンドを打つと出て来る文字列を設定する。

config/secrets.yml
production:
  secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
$ gem install bundler
$ bundle install
$ rake db:create RAILS_ENV=production
$ rake db:migrate RAILS_ENV=production

PumaとNginxの起動

Pumaの起動

アプリケーションディレクトリにて以下を実行。

$ RAILS_SERVE_STATIC_FILES=true RAILS_ENV=production puma -w 4

「Ctr+c」でプロセス消さなければ生き残る。
プロセス消す必要があれば以下のようにする。

$ ps aux | grep puma # プロセスIDを探す
$ kill -9 (ID)

Nginx の起動

「cheerfull」の部分は自分のアプリケーションディレクトリ名に変更する。

/etc/nginx/conf.d/cheerfull.conf
  # log directory
  error_log  /var/www/rails/cheerfull/log/nginx.error.log;
  access_log /var/www/rails/cheerfull/log/nginx.access.log;
  upstream app_server {
    # for UNIX domain socket setups
    server unix:/home/shizuma/run/cheerfull.sock fail_timeout=0;
  }
  server {
    listen 80;
    server_name 12.134.156.178; # 自分のIP
    # nginx so increasing this is generally safe...
    # path for static files
    root /var/www/rails/cheerfull/public;
    # page cache loading
    try_files $uri/index.html $uri @app_server;
    location / {
      # HTTP headers
      proxy_pass http://app_server;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header Host $http_host;
      proxy_redirect off;
    }
    # Rails error pages
    error_page 500 502 503 504 /500.html;
    location = /500.html {
      root /var/www/rails/cheerfull/public;
    }
    client_max_body_size 4G;
    keepalive_timeout 5;
  }

ユーザーをnginxから自分のユーザー名に変更しておく。

/etc/nginx/nginx.conf
#user nginx;
user shizuma;

nginxを起動します。

$ sudo service nginx restart

これで、IPアドレスでアクセスするとアプリケーションが表示されるようになりました。

Capistranoの設定

諸々動くことが確認出来たのでデプロイが出来るように設定します。
デプロイのためにCapistranoの設定をします。
慣れてきたら、いきなりCapistranoの設定をしていけばいいと思います。

socketの場所もアプリケーションディレクトリに変更するのでnginxの設定もそれに合わせて変更します。

「cheerfull」の部分は自分のアプリケーション名に変更する。

deploy.rb
require 'dotenv'
Dotenv.load

lock "3.7.1"

set :application, "cheerfull"
set :repo_url, "git@github.com:your_gitname/cheerfull.git"
set :branch, 'master'
set :deploy_to, '/var/www/rails/protospacce'
set :linked_files, fetch(:linked_files, []).push('.env')
set :linked_dirs, fetch(:linked_dirs, []).push('log', 'tmp/pids', 'tmp/cache', 'tmp/sockets', 'vendor/bundle', 'public/system')
set :keep_releases, 5
set :rbenv_ruby, '2.3.1'

# puma setting
set :puma_threads,    [4, 16]
set :puma_workers,    0
set :pty,             true
set :use_sudo,        false
set :stage,           :production
set :deploy_via,      :remote_cache
set :deploy_to,       "/var/www/rails/#{fetch(:application)}"
set :puma_bind,       "unix://#{shared_path}/tmp/sockets/#{fetch(:application)}-puma.sock"
set :puma_state,      "#{shared_path}/tmp/pids/puma.state"
set :puma_pid,        "#{shared_path}/tmp/pids/puma.pid"
set :puma_access_log, "#{shared_path}/log/puma.error.log"
set :puma_error_log,  "#{shared_path}/log/puma.access.log"
set :puma_preload_app, true
set :puma_worker_timeout, nil
set :puma_init_active_record, true

namespace :deploy do
  desc 'Create database'
  task :db_create do
    on roles(:db) do |host|
      with rails_env: fetch(:rails_env) do
        within current_path do
          execute :bundle, :exec, :rake, 'db:create'
        end
      end
    end
  end
end

実際にdeployする前に、共有ファイルは用意しておきましょう。この場合、.env

ここでpumaのセッティングをしたので、config/puma.rb はもう不要になります。
また、dotenvを使用する場合は変更を読み込むために config/deploy/templates/puma.rb.erb を用意します。以下を用意。

config/deploy/templates/puma.rb.erb
#!/usr/bin/env puma

directory '<%= current_path %>'
rackup "<%=fetch(:puma_rackup)%>"
environment '<%= fetch(:puma_env) %>'
<% if fetch(:puma_tag) %>
  tag '<%= fetch(:puma_tag)%>'
<% end %>
pidfile "<%=fetch(:puma_pid)%>"
state_path "<%=fetch(:puma_state)%>"
stdout_redirect '<%=fetch(:puma_access_log)%>', '<%=fetch(:puma_error_log)%>', true


threads <%=fetch(:puma_threads).join(',')%>

<%= puma_bind %>
<% if fetch(:puma_control_app) %>
activate_control_app "<%= fetch(:puma_default_control_app) %>"
<% end %>
workers <%= puma_workers %>
<% if fetch(:puma_worker_timeout) %>
worker_timeout <%= fetch(:puma_worker_timeout).to_i %>
<% end %>

<% if puma_preload_app? %>
preload_app!
<% else %>
prune_bundler
<% end %>

on_restart do
  puts 'Refreshing Gemfile'
  ENV["BUNDLE_GEMFILE"] = "<%= fetch(:bundle_gemfile, "#{current_path}/Gemfile") %>"
  ENV.update Dotenv::Environment.new('.env')
end

<% if puma_preload_app? and fetch(:puma_init_active_record) %>
before_fork do
  ActiveRecord::Base.connection_pool.disconnect!
end

on_worker_boot do
  ActiveSupport.on_load(:active_record) do
    ActiveRecord::Base.establish_connection
  end
end
<% end %>
$ bundle exec production cap puma:config 

これでこの設定を本番に反映。

あとはデプロイコマンドを打つ。

$ bundle exec cap production deploy

デプロイdone!

nginxのlogの位置やsocketの位置を変更します。

/etc/nginx/conf.d/cheerfull.conf
  # log directory
  error_log  /var/www/rails/cheerfull/shared/log/nginx.error.log;
  access_log /var/www/rails/cheerfull/shared/nginx.access.log;
  upstream app_server {
    # for UNIX domain socket setups
    server unix:/var/www/rails/cheerfull/shared/tmp/sockets/cheerfull-puma.sock fail_timeout=0;
  }
  server {
    listen 80;
    server_name 12.134.156.178; # 自分のIP
    # nginx so increasing this is generally safe...
    # path for static files
    root /var/www/rails/cheerfull/current/public;
    # page cache loading
    try_files $uri/index.html $uri @app_server;
    location / {
      # HTTP headers
      proxy_pass http://app_server;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header Host $http_host;
      proxy_redirect off;
    }
    # Rails error pages
    error_page 500 502 503 504 /500.html;
    location = /500.html {
      root /var/www/rails/cheerfull/current/public;
    }
    client_max_body_size 4G;
    keepalive_timeout 5;
  }
$ sudo service nginx restart

これで、デプロイ環境が整いました。
以前起動したpumaのプロセスはきっておきましょう。

複数サーバーへのデプロイ

multiconfig を使用します。

Gemfile
gem 'capistrano-multiconfig', '~> 3.1', require: false

Capfileのcapistrano/setupから変更。

Capfile
# require "capistrano/setup"
require 'capistrano/multiconfig'

config/deploy/production.rb から config/deploy/app1/production.rb に変更する。
以下のようにdeploy出来るようになる。

$ bundle exec cap app1:production deploy

違うサーバーにデプロイするときは同じように config/deploy/app2/production.rb を準備すればオーケー。

$ bundle exec cap app2:production deploy

これで違うサーバーにデプロイ出来る。

これまでと同様にec2サーバーをもう一度用意して2箇所にデプロイ出来るようにする。

ロードバランサー ELB

ロードバランサーをhttpsで運用したいときadmin@hoge.comのようなアドレスへのメールを受信可能のしないといけない。

以下でとりあえずzoneファイルのインポートまでする。
Amazon Route53 ネームサーバへの移行手順(お名前.comからの)

その後にこの記事にあるようにお名前.com側の設定をする。
お名前.com + Route53で独自ドメインのメールをGmailに転送する

メールの転送が出来ていることを確認する。
そして、ELBの指示に従って設定。
下記参考。
【初心者向け】ELBにSSL証明書をインストールする

そこまでしたら
Amazon Route53 ネームサーバへの移行手順(お名前.comからの)
の続きでELBとつなげる。

注意点は、ターゲットグループにトラフィックをルーティングするときのプロトコルをHTTPにすること。これでELBへはHTTPSでアクセス出来、それをアプリケーションではHTTPで扱うことが出来る。

また、以下のように nginxのサーバーネームにドメイン名を追加する必要がある。

server_name hogepoge.com 00.000.00.00 sakamichi-app-elb-000000000.ap-northeast-1.elb.amazonaws.com

CloudFront

Amazon CloudFront + ACM 独自ドメインで HTTPS (SSL) 配信設定メモ

CloudWatchとか

必要の応じて加筆。

続きを読む

Amazon Inspectorを使ってみたメモ

Amazon Inspectorを使ってみた

業務で脆弱性診断ツールの選定を任されたので、実際に使ってみた
(使ってみないと何がいいのかわからんよね)

サマリ

  1. IAMロール作成
  2. EC2インスタンスへのタグつけ
  3. AWSエージェントをインストール
  4. 評価ターゲットの定義
  5. 評価テンプレートの定義
  6. 評価の実行

IAMロールを作成

Amazon InspectorがAWS EC2インスタンスのタグ情報を取得できる必要があるため、
下記権限で作成する

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "ec2:DescribeInstances"
            ],
            "Resource": [
                "*"
            ]
        }
    ]
}

EC2インスタンスへのタグつけ

EC2インスタンスに対して適当なタグをつける

AWSエージェントをインストール

脆弱性診断対象のEC2インスタンスに対してSSHし、エージェントをインストールする

[ec2-user@ip-10-0-1-41 ~]$ sudo su -
Last login: Wed Aug  2 22:31:53 JST 2017 on pts/0
[root@ip-10-0-1-41 ~]# cd /usr/src/
[root@ip-10-0-1-41 src]# wget https://d1wk0tztpsntt1.cloudfront.net/linux/latest/install
:
:
install                                            100%[================================================================================================================>]  26.60K  --.-KB/s    in 0s      

2017-08-12 15:40:42 (269 MB/s) - ‘install’ saved [27238/27238]

[root@ip-10-0-1-41 src]# 
[root@ip-10-0-1-41 src]# bash install -u false
Forced update specified as argument is : false
Distribution of the machine is Amazon Linux AMI.
Distribution type of the machine is amzn.
:
:
Current running agent reports version as: 1.0.1041.1
This install script was created to install agent version:1.0.1041.1
In most cases, these version numbers should be the same.
[root@ip-10-0-1-41 src]# 

評価ターゲットの定義

ここで、
1. どのインスタンスに対して診断をするのか(複数可)
2. 対象インスタンスのまとまりに対して名前つけ
を実施する
今回はこんな感じにしてみた
image.png

評価テンプレートの定義

何をもってして評価するのかをここで定義する。具体的には、
1. 評価基準(ルールテンプレートなるもの)
2. 診断時間(15分〜24時間)
3. 上記設定をした評価基準に対しての名前つけ
を実施する。今回はこんな感じにしてみた
image.png

評価の実行

1〜5までの設定が全て完了したら最後は実行するのみ!
image.png
※すでに実行後なため実行ボタンが選択できなくなっています。。。

結果などがでてきたらまた続きをかきたいと思います!

続きを読む

EC2(スポット)インスタンス上でChainerMNのマルチノード分散学習

EC2(スポット)インスタンスでChainerMNを使う(マルチノード分散学習)

概要

  • EC2(スポット)インスタンスでChainerMNのマルチノード分散学習をする方法

    • 環境変数の設定方法
    • sshにStrictHostChecking noを追加
    • セキュリティグループの設定(VPC内からの全アクセスを許可)
  • EC2上でマルチノード分散学習する場合の注意点
    • p2.xlargeを使ってマルチノード分散学習は性能がでない
    • g3.4xlargeを利用すると良い
  • マルチノード学習した際の性能の簡単な評価
    • ImageNetの学習ではp2.8xlargeを使う時と同等かそれ以上のコストパフォーマンス

やりたかったこと

スポットインスタンスの価格が比較的安いGPU1個のインスタンス(p2.xlargeg3.4xlarge)を複数使って、ディープラーニングの学習を高速化させたかった。

学習を高速にする手段としては、マルチノードで分散する以外に、そもそも1台あたりのGPU数を増やす、という選択肢もある。
しかし、GPUを複数個積んでいるEC2のインスタンスはどれも高いし、スポットインスタンスで価格を抑えられないことがある。例えば、p2.8xlargeはオンデマンドインスタンスの場合、\$7.2/hかかる。スポットインスタンスの価格は、ここ1週間くらいはp2.8xlargeが\$2.5/h弱のようだが、ちょっと前は\$72/hに張り付いていた。
あるいは、自前で学習用計算機用意する手もあるが、GPU複数台積むマシンとなるとかなり高くつくことになる。個人の趣味の範囲内だと、電気代を抜いてもAWSを使うより高くなる可能性が高そう。

なので、p2.xlargeなどのスポットインスタンスでの値段が低め(〜\$0.3/h)で抑えられているインスタンスを複数利用して、学習を高速化させるという方針に至った。オンデマンドのp2.8xlargeと比べて、スポットインスタンスのp2.xlargeg3.4xlargeは1GPU当たりの値段で1/3ほどなので、マルチノードの分散学習の複雑さや効率の悪さはGPUの台数で補えるという目論見。

ChainerMNを使った分散学習 in AWS

環境の準備

ChainerMNのインストール

ChainerMNをインストールする方法自体は、もう多数の記事・情報があるので、詳細は省く。自分はここChainerMNのチュートリアルを参考にした。
やったことを列挙すると、以下の通り。

  • CUDA 8.0のインストール
  • cuDNN 6.0のインストール
  • NCCL 1.xのインストール
    • GitHubのページにはno longer maintainedとあるが、まだNCCL2は使えかった
  • OpenMPIのビルド・インストール
  • Chainer、ChainerMNのインストール

この作業はGPUを積んでいる中で安いインスタンス(p2.xlarge)を利用すると良い。

環境変数の設定

sshに非対話モードで入った時に、CPATHLD_LIBRARY_PATHが適切な値(具体的にはcudaのパスを含む)になっていないと学習スクリプトがうまく動かない。
/etc/bash.bashrcを以下のようにした。

/etc/bash.bashrc
export PATH=/usr/local/cuda-8.0/bin:${PATH}
export LD_LIBRARY_PATH=/usr/local/cuda-8.0/lib64:${LD_LIBRARY_PATH}
export CPATH=/usr/local/cuda-8.0/targets/x86_64-linux/include:${CPATH}

以下のコマンドを叩いた時、PATHLD_LIBRARY_PATHが適切に設定されていれば良い。

$ ssh localhost 'env'

sshの設定

マルチノード分散学習をする際、インタラクティブな操作なしに別ノードへsshで接続できる必要がある。したがって、鍵認証の設定をする。また、デフォルトでは最初に接続しようとすると、Are you sure you want to continue connecting (yes/no)?のメッセージが出て、yes/noの入力を求められるので、手間を考えるとこれも対処する必要がある。

まず、鍵認証の設定をする。

$ ssh-keygen #パスフレーズなし、~/.ssh/id_rsaに置く
$ cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys

次に、.ssh/configを以下のとおりにして、yes/no入力をなくす

~/ssh/.config
Host *
    StrictHostKeyChecking no

どちらもセキュリティ上良いとは言えないが、最終的にはAWSのセキュリティグループで外部ネットワークからのインバウンドを遮断して運用すれば許容範囲と思っている。

ENAの有効化

必要なのかはわからないが、UbuntuはデフォルトではENAが有効になっていないようだったので、有効にする。最新の手順はここにあるので、これの通りに行う。
やるべきことは以下の3つ

  1. インスタンス上で、ENAのモジュールを追加
  2. インスタンスを停止
  3. ローカルからaws CLIでENAを有効化

AWS上のリソースの準備

1. VPC、サブネット、プレイスメントグループの準備

それぞれ適当な名前で準備する。VPCとサブネットは一度EC2インスタンスを起動すればついでにできるし、プレイスメントグループは、EC2のコンソールから、ネットワーク&セキュリティ → プレイスメントグループのページに行って作成すれば良い。
なお、プレイスメントグループはいるのかどうか分からないが、ネットワークの帯域幅をフルに出すには必要らしいので自分は作成した。

2. 学習ノード用のセキュリティグループの準備

セキュリティグループの準備も必要。インバウンドルールでは、「すべてのトラフィック・すべてのポート範囲へのVPCからのアクセス」を許可する。本来はもっと絞りこめると思うが、調べるのが面倒だったのでVPC内に全部公開した。
EC2コンソール上では、すべてのトラフィック すべて 0-65535 カスタム <VPCのCIDR>となっていれば良い。

3. (Optional) AMIの作成

必要はないが、ここまで終えた時点でAMIを作っておくと別のことをしたい時に無駄な出費を防げる。
AMIの作成方法は省略。

学習スクリプトなどの準備

最後に、学習用のスクリプト、データセットなどを準備する。

今回、自分はchainermnについているImageNetのサンプルを使った。
git clone --depth 1 https://github.com/chainer/chainermn.gitとして、chainermnのソースを落とすとchainermn/examples/imagenetの下にImageNetのサンプルがあるのでこれを用いる。また、自分の場合、models_v2/nin.pyをchainerのexamples/imagenet/nin.pyに置き換えないと動かなかったので、chainerのソースも落としてきてcpした。

次に、データセットを準備する。データセットの準備方法は、ここここなどが参考になる。

ここまで終えたら、インスタンスを止めてAMIを作成する。

実行方法(1ノード)

テストも兼ねて1ノードで学習を走らせる場合は、インスタンスを起動した後、sshでログインして、

$ mpiexec -n 1 python3 ~/chainermn/examples/imagenet/train_imagenet.py train.txt test.txt

などとすれば良い。ここで、train.txt、test.txtはそれぞれ準備したデータセットのパス

参考: ChainerMN チュートリアル

実行方法(マルチノード)

上で作成した学習スクリプトの入ったAMIを利用し、スポットインスタンスを適当に何個か立ち上げる。この時、VPC、プレイスメントグループ、セキュリティグループは上で準備したものを忘れず利用する。
なお、別にスポットインスタンスでなくてもいいが、費用を抑えて実験してみたいだけならスポットインスタンスの方が適していると思う。ただし、スポットインスタンスが突然中断するリスクを減らすため、高めに価格を設定しておくと安心。

また、多少値段は上がるが、p2.xlargeでなく、g3.4xlargeを使うと良い (理由は”注意点”で後述)。

以下では、2台のg3.4xlargeインスタンスを立ち上げ、それぞれのプライベートIPが172.31.41.13172.31.41.14となったとする。
まず、どちらか1台(以下では172.31.41.13の方とする)にsshでログインする。ログインしたら、以下の内容のホストファイルを~/hostfileに作成する(パスはどこでも良い)。

~/hostfile
172.31.41.13 cpu=1
172.31.41.14 cpu=1

(プライベートIPは、その時立ち上げたスポットインスタンスを見て適宜修正する必要あり。)

次に、以下のコマンドを叩くと、2台のマシンで分散学習される。

$ mpiexec -n 2 --hostfile ~/hostfile python3 ~/chainermn/examples/imagenet/train_imagenet.py train.txt test.txt

参考: ChainerMN チュートリアル

注意点(ネットワークの帯域幅を考慮する必要あり)

GPU付きインスタンスの中ではp2.xlargeが値段は安いのだが、ネットワークの帯域幅が小さく、性能が出なかった。iperfを使ってはかった結果では、1.44Gbps。一方、g3.4xlarge10Gbpsでるというスペックだし、実際iperfではかると10Gbpsでた(情報提供:https://twitter.com/grafi_tt/status/895274632177000449 )。

いくら安く分散学習させたいと言っても、p2.xlargeだと性能向上が見られなかったので、g3.4xlargeを使う方が良いと思う。

性能確認

学習が高速化できるのか確認するため簡単な性能測定をした。なお、どれも1回しか計測してないし、真面目に条件を揃えたわけではないので、数字は参考程度に。

以下のパターンで、ImageNetの学習にかかる時間を測定した。

  1. g3.4xlarge1台で、ChainerMNを利用
  2. g3.4xlarge複数台(2, 4, 6, 8, 10, 12)で、ChainerMNを利用
  3. p2.8xlarge(8GPU)で、ChainerMNを利用

結果

以下の通り。
分散すればちゃんと高速化されるし、p2.8xlargeと比べても安いまたは同等程度の値段でほぼ同じ性能を出せている。ただ、この辺は学習させるネットワークやデータセットによって色々異なるんだろうな。

表1: 1エポック当たりの時間

条件 1エポックあたりの平均時間 (sec)
g3.4xlarge*1 34.4
g3.4xlarge*2 21.8
g3.4xlarge*4 12.5
g3.4xlarge*6 9.2
g3.4xlarge*8 7.9
g3.4xlarge*10 6.3
g3.4xlarge*12 5.2
p2.8xlarge 7.9

ちゃんと分散するにつれて短い時間で学習できている。


表2: 値段 – 総実行時間

条件 値段 (\$/h) 総実行時間 (sec)
g3.4xlarge*1 0.3 344.3
g3.4xlarge*2 0.6 217.8
g3.4xlarge*4 1.2 125.2
g3.4xlarge*6 1.8 92.4
g3.4xlarge*8 2.4 79.2
g3.4xlarge*10 3.0 63.0
g3.4xlarge*12 3.6 51.7
p2.8xlarge 7.2(オンデマンド) / 2.5(スポットインスタンス利用時) 79.1

備考:g3.4xlargeのスポットインスタンスの値段は\$0.3/hとして計算

p2.8xlargeをオンデマンドで利用する場合に比べると、より安く高速な学習ができる。p2.8xlargeがスポットインスタンスの場合と比べても、ほぼ同等の性能が今回の例では出た。


グラフ1: epoch – elapsed_time
graph1.png


グラフ2: epoch-validation/main/accuracy
graph2.png

epochが少なすぎてわかりやすいデータにならなかったが、分散させるほど同エポックでの精度は悪化する傾向にあるらしい。直感的にもそんな気はする。とはいえ、マルチノードの場合とp2.8xlargeでノード内で分散した場合では大きな精度の差は見つけられない。分散学習するなら、エポックを大きめに設定する必要があるようだが、それはマルチノード分散学習の問題というより、現在のChainerMN全体の問題の可能性が高い。


その他備考
分散学習では、最初の1回のmpiexecは時間がかかるらしい。上記計測は、2回目のmpiexecで行っている。原因は、ノード間の接続を確立する時間が追加されているからではないかと思うが、詳細は不明。ただし、学習時間が長くなるにつれて、その時間は無視できるものになると思われる。

まとめとか

少なくともImageNetでは、マルチノードの分散学習でも相当の学習時間の短縮が見込める。また、8/7からChainerMNを初めて5日でここまでできたので、非常に難しい作業が必要というわけでもない。
そのため、AWS上でのディープラーニング学習を高速化させたい時、選択肢に入れる価値はあると思う。最初に書いたような、複数GPUを積んだスポットインスタンスが高い時にも使えるし、あるいはp2.8xlargeを複数使ってさらに高速化する、という使い方もマルチノードの分散学習はできるはず。

一方で、データセットが増えた時どうなるのか、モデルが複雑になった時どうなるのか、などは調べてない。実際に使ってみるとたいして高速化されなかった、みたいなケースはありそう。

要改善点

とりあえずテストするだけなら上記手順でもできたが、実際にディープラーニングを利用するプロジェクトに組み込むとなると以下の点を改善しないといけない。

学習スクリプトの実行方法

本来は、aws CLIとかSDKからスポットインスタンスを立ち上げて、自動で学習を回したい(ここみたいに)。
そのためには、UserDataのスクリプトで学習スクリプトを実行する必要があるが、以下の点に注意が必要。

  1. mpiexecをするインスタンスの決定方法
  2. ホストファイルの作成方法
  3. すべてのインスタンスが立ち上がるまでの待ち合わせ処理

1については、特定のタグを全インスタンスに付けておき、aws ec2 describe-instancesで全インスタンスのプライベートIPを取得、辞書順最小のインスタンスでmpiexecすれば解決しそう。
2は、describe-instancesした時に全部のプライベートIPがわかるんだからホストファイルもついでに生成できる。
3は、ポーリングなりなんなりでやればできるはず。この時、ついでに学習パラメータの環境変数への展開やS3からデータセットのダウンロードも待ち合わせ処理すると色々便利そう。

中断時の対処

スポットインスタンスなので、たまに強制終了させられることがある。

  1. 定期的なS3へのスナップショットアップロード(systemd-timer)
  2. 1台でも終了したら全台終了して無駄な出費の削減
  3. 学習開始時にスナップショットがあればそれを読み込み

の3つの対処が必要。

続きを読む

CodeStarでAPIを公開する

AWS CodeStarを用いて簡単にAPIを公開する方法を記載します。
CodeStarを利用すると、1から設定するのは結構大変な以下サービスなどをテンプレートより一気に作成できます。

  • AWS CodeCommit(レポジトリ)
  • AWS CodeBuild(自動ビルド)
  • AWS CodePipeline(継続的デリバリ)
  • AWS CodeDeploy(自動デプロイ)
  • AWS CloudFormation(インフラ構成管理)

前提

  • CodeCommitの認証セットアップ

http://docs.aws.amazon.com/ja_jp/codecommit/latest/userguide/setting-up-ssh-unixes.html

手順

CodeStar選択

2017/8/10時点では、東京リージョンでは利用できないので、CodeStar選択の後、任意のリージョンへ。

スクリーンショット 2017-07-27 時刻 19.39.17.png

プロジェクト作成

スクリーンショット 2017-07-27 時刻 19.50.29.png

テンプレート選択

AWS Lambdaをチェックして、任意の言語を選択する
※以下はnode+Expressを選択した例

スクリーンショット 2017-07-27 時刻 19.55.03.png

プロジェクト名設定

スクリーンショット 2017-07-27 時刻 19.55.55.png

IDEの設定

必要ないので、Command line tools選択

スクリーンショット 2017-07-27 時刻 19.58.40.png

レポジトリとの接続方法を教えてくれます。
確認の後、Skipします。

スクリーンショット 2017-07-27 時刻 19.58.53.png

自動ビルド

以上で設定が完了です。
→テンプレートモジュールの自動ビルドが走ります。

スクリーンショット 2017-07-27 時刻 20.02.08.png

API更新

自動ビルドが完了すると、管理コンソール右上部の「Application Endpoint」をクリックすると、APIのEndpointへ接続できます。
プログラムを修正後、CodeCommitへpushすると、更新バージョンが自動でデプロイされます。
※CodeCommitへは管理コンソール左上部より遷移できます。

スクリーンショット 2017-08-10 時刻 17.03.13.png

スクリーンショット 2017-07-27 時刻 20.22.19.png

注意

AWS 無料利用枠を超えると、APIコール回数(Lambda実行時間)で課金されるので、サービス公開前は念のため、使用量プランで呼び出し回数を制限しておきましょう。

https://aws.amazon.com/jp/lambda/pricing/
http://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/api-gateway-api-usage-plans.html

続きを読む

SORACOM BeamでバイナリデータをAPIGateway経由でS3にアップロードする

はじめに

あるお客様からLTEモジュールからS3に画像ファイルをアップロードしたとの要望があったので、いろいろな方法を模索したのですが、AWS-CLIなんて積めないし、HTTPリクエストのヘッダーに必要なAWS認証情報を作成できないし、といった感じでどうやって実現しようかと悩んでいました。(AWS-CLIやAWS-SDKのすばらしさに感動したw)
そんなとき「SORACOM Beamを使ってAPIGateway経由で何とかならんかね?バイナリデータのサポートもされたことだし」という神のお告げが降ってきたので実装してみた

どんな感じ?

apigateway_post.PNG

SORACOM Beamって?

下記公式ページから抜粋

SORACOM Beam(以下、Beam)は、IoT デバイスにかかる暗号化等の高負荷処理や接続先の設定を、クラウドにオフロードできるサービスです。
Beam を利用することによって、クラウドを介していつでも、どこからでも、簡単に IoT デバイスを管理することができます。
大量のデバイスを直接設定する必要はありません。

(参考:https://soracom.jp/services/beam/)

S3バケットの作成

まず画像ファイルをアップロードするバケットを作成しましょう
詳しい手順は割愛しますが、バケットポリシーを設定しないとPutObjectできないのでこんな感じで設定しておきます

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "AddPerm",
            "Effect": "Allow",
            "Principal": "*",
            "Action": [
                "s3:PutObject"
            ],
            "Resource": "arn:aws:s3:::[Bucket Name]/*"
        }
    ]
}

APIGatewayの作成

次にAPIGatewayを作成します

  1. APIGatewayのコンソール画面を開き、「Create API」をクリックします
  2. 「API name」に任意の文字列を入力し「Create API」で作成します
  3. リソースを作成します。「Actions」から「Create Resource」を選択し「upload」というリソースを作成します
  4. 「/upload」の子階層に「/{key}」といリソースを作成します。「/{key}」はURLパスパラメータとなり、今回はS3にPutする際のKey情報となります
  5. 「/{key}」を選択した状態で「Actions」から「Create method」を選択し「POST」メソッドを作成します。画面はこのような感じになっていると思います。この時点で「/upload/{key}」に対してPOSTメソッドを受け付けるように設定されています
    apigateway_post_test.PNG
  6. 「Integration type:HTTP」「HTTP method:PUT」「Endpoint URL:S3のエンドポイント(ex:https://s3-ap-northeast-1.amazonaws.com/[Bucket Name]/{key})」を選択・入力し「save」をクリックします。そうするとPOSTメソッドが作成されます
    apigateway_post_method.PNG
    「Method Request」を選択して「API Key Required」を「ture」に変更します
  7. 「Actions」から「Deploy API」を選択し、APIをデプロイします。初めての場合は[New Stage]を選択して新しいステージを作成してください。「Invoke URL」を控えておいてください
  8. バイナリデータを受け取る設定をします。「Binary Support」を選択し、「Edit」をクリックして「application/octet-stream」と「image/png」を追加します
  9. 次にSORACOM BeamからAPIGatewayを利用するにあたってAPIキーが必要になるので、APIキーを作成してAPIGatewayと紐付けます。
  10. 「API Keys」を選択し、「Actions」から「Create API Key」をクリック。「Name」に任意の文字列を入力し、「Save」をクリックします。これでAPIキーが作成されました。「API Key」の文字列をどこかに控えておいてください
  11. 「Usage Plans」を選択し、「Create」をクリック。各必須項目に任意の値を入力して「next」をクリック
  12. 「Add API Stage」をクリックして、先ほどデプロイしたAPIGatewayを選択し「Next」をクリック
  13. 「Add API Key to Usage Plan」を選択し、先ほど作成したAPIキーを入力し「Done」をクリック
  14. これで、APIGatewayとAPIキーが結びつきました

SORACOM Beamの設定

最後にSORACOM Beamの設定をします
1. SORACOMのコンソール画面を開きグループを作成します
2. 作成したグループのBeam設定を開き、プラスのアイコンから「HTTPエントリポイント」を選択します
3. 下記内容を入力・設定していきます

設定名:任意の文字列
エントリポイント
  パス:/
転送先
  ホスト名:APIGatewayのInvoke URL
  パス:/upload/test.png
ヘッダ操作
  IMSIヘッダ付与:ON
  署名ヘッダ付与:ON
  事前共有鍵:作成しておいたものを選択。新たに作成する場合は後ほど記述します
カスタムヘッダ:「アクション:置換」「ヘッダ名:X-API-KEY」「値:APIキーの文字列」

※事前共有鍵の作成
「認証情報ID」と「事前共有鍵」に任意の文字列を入力し「登録」をクリックしてください
soracom_key.PNG

これで「http://beam.soracom.io:8888/ 」に対してリクエストを送信すると、APIGatewayにPOSTメソッドが送信されます

送信テスト

SIMのグループをBeamが設定されているグループに変更し、デバイスにSSHでログインします
テスト送信用のpngファイル(下記コマンドでは「test.png」)があるディレクトリに移動し、下記コマンドを入力しS3に画像がアップロードされているかを確認してください

curl -X POST --data-binary "@test.png" -H  "Content-Type: application/octet-stream" http://beam.soracom.io:8888/

成功していれば、S3の対象バケット内に「test.png」がアップロードされていると思います

まとめ

最近よくIoT案件に関わらせていただいているのですが、そもそもセキュアな通信を前提としていないデバイスをクラウドなどと接続したいという要望が増えてきているように感じてきました
SORACOMさんのようなSIMに認証情報を持たせてセキュアな通信を担保してくれるようなサービスを最大限に活用してそういった要望にこたえていきたいと思う今日この頃でした
ただクラウド側での処理が複雑になればなるほどどこかで破綻するんじゃないかなーとも感じていたりいなかったり

あと、SORACOMBeamでAPIGatewayのパスを変数的に持たせることってできないんだろうか
APIGateway側でURLパスパラメータを使ってS3へアップロードするキー名を決めているので、今回のように固定にしてしまうと、アップロードされる画像がずっと上書きされてしまうので、あまりよろしくない・・・
S3のイベントで画像がアップロードされたタイミングでLambdaキックしてリネームしてアップロードしなおすという手もあるけど・・・
Beamの仕様を漁るか・・・
もし、何かよい方法があればコメント等で教えてください!

ではまた!

続きを読む