awsでruby on railsとapacheとの連携構築方法

すべての手順

必要なミドルウェアのインストール

$ sudo yum update
$ sudo yum install curl-devel ncurses-devel gdbm-devel readline-devel sqlite-devel ruby-devel
$ sudo yum install gcc gcc-c++ openssl-devel zlib-devel make patch git gettext perl rpm-build libxml2

rbenvのインストール

$ 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
$ env | grep RBENV
RBENV_SHELL=bash
$ rbenv --version
rbenv 1.1.0-2-g4f8925a

ruby2.4.0 のインストール

$ git clone https://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build
$ rbenv install -l
.....
2.3.0-dev
  2.3.0-preview1
  2.3.0-preview2
  2.3.0
  2.3.1
  2.3.2
  2.3.3
  2.4.0-dev
  2.4.0-preview1
  2.4.0-preview2
  2.4.0-preview3
  2.4.0-rc1
  2.4.0
  2.5.0-dev
  jruby-1.5.6
  jruby-1.6.3
  jruby-1.6.4
  jruby-1.6.5
  jruby-1.6.5.1
...
$ rbenv install -v 2.4.0
$ rbenv rehash
$ rbenv global 2.4.0
$ ruby -v
 ruby 2.4.0p0 (2016-12-24 revision 57164) [x86_64-linux]

Railsのインストール

$ gem update --system
$ gem install bundler --no-rdoc --no-ri
$ gem install --no-ri --no-rdoc rails
$ rbenv rehash    
$ rails -v
Rails 5.0.2
$ gem install bundler

sqlite3のインストール

$ wget https://www.sqlite.org/2017/sqlite-autoconf-3170000.tar.gz
$ tar xvzf sqlite-autoconf-3170000.tar.gz
$ cd ./sqlite-autoconf-3170000
$ ./configure
$ make
$ make install
$ source ~/.bash_profile
$ rm -rf sqlite-autoconf-317000*
$ sqlite3 --version
; 3.17.0 2017-02-13.....

Hello World アプリ作成

$ cd ~
$ rails new hello_world
$ cd hello_world
$ vim Gemfile
$ bundle install
- # gem 'therubyracer', platforms: :ruby
+ gem 'therubyracer', platforms: :ruby
$ bundle exec rails s -e production
=> Booting Puma
=> Rails 5.0.2 application starting in production on http://0.0.0.0:3000
=> Run `rails server -h` for more startup options
Puma starting in single mode...
* Version 3.8.2 (ruby 2.4.0-p0), codename: Sassy Salamander
* Min threads: 5, max threads: 5
* Environment: production
* Listening on tcp://0.0.0.0:3000
Use Ctrl-C to stop

apache2.4のインストール

$ sudo yum install httpd24 httpd24-devel

Passengerのインストール

$ gem install passenger
$ rbenv rehash
$ sudo dd if=/dev/zero of=/swap bs=1M count=1024
$ sudo mkswap /swap
$ sudo swapon /swap
$ passenger-install-apache2-module
.....
linking shared-object passenger_native_support.so

--------------------------------------------
Almost there!

Please edit your Apache configuration file, and add these lines:

   LoadModule passenger_module /home/ec2-user/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/passenger-5.1.2/buildout/apache2/mod_passenger.so
   <IfModule mod_passenger.c>
     PassengerRoot /home/ec2-user/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/passenger-5.1.2
     PassengerDefaultRuby /home/ec2-user/.rbenv/versions/2.4.0/bin/ruby
   </IfModule>

After you restart Apache, you are ready to deploy any number of web
applications on Apache, with a minimum amount of configuration!
....

Apacheの設定

$ sudo chown -R apache. ~/hello_world

$ sudo vim /etc/httpd/conf.d/passenger.conf
+   LoadModule passenger_module /home/ec2-user/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/passenger-5.1.2/buildout/apache2/mod_passenger.so
+   <IfModule mod_passenger.c>
+     PassengerRoot /home/ec2-user/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/passenger-5.1.2
+     PassengerDefaultRuby /home/ec2-user/.rbenv/versions/2.4.0/bin/ruby
+   </IfModule>
$ sudo vim /etc/httpd/conf.d/apps.conf
 <VirtualHost *:80>
    ServerAlias ip address
    ServerName ip address

    <Directory "/home/ec2-user/hello_world/public/">
        PassengerAppRoot /home/ec2-user/hello_world
        RailsEnv production        
        Options -MultiViews
        Options Indexes FollowSymLinks
        Require all granted
    </Directory>
 </VirtualHost>

Apacheの起動

$ sudo service httpd start
$ sudo service httpd status

続きを読む

DockerHubのAmazonLinuxオフィシャルイメージの中身が空っぽ過ぎたのでこのyumコマンドで本物と同等にした

yum install -y acl acpid alsa-lib at attr audit audit-libs authconfig autogen-libopts aws-amitools-ec2 aws-apitools-as aws-apitools-common aws-apitools-ec2 aws-apitools-elb aws-apitools-mon aws-cfn-bootstrap aws-cli basesystem bash bc bind-libs bind-ut… 続きを読む

MySQL 8.0 DMRを試してみた

MySQL5.7の次のメジャーバージョン8を使ってみた。8の詳細については、こちらの記事が良いと思う。

インストール

公式のyumリポジトリを使用する

sudo yum install https://dev.mysql.com/get/mysql57-community-release-el6-9.noarch.rpm

Amazon Linux(バージョン 2016.09)で検証を進めるため、RHEL6系のリポジトリを使用します。他のディストリビューションは、こちらから探してください。

リポジトリの修正

このままでは5.7が入るので、5.8がインストールされるように変更する。--enablerepoとか--disablerepoでもできます。

sudo vim /etc/yum.repos.d/mysql-community.repo

[mysql57-community]
name=MySQL 5.7 Community Server
baseurl=http://repo.mysql.com/yum/mysql-5.7-community/el/6/$basearch/
- enabled=1
+ enabled=0
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-mysql

[mysql80-community]
name=MySQL 8.0 Community Server
baseurl=http://repo.mysql.com/yum/mysql-8.0-community/el/6/$basearch/
- enabled=0
+ enabled=1
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-mysql

yumインストール

sudo yum install mysql-community-server

起動

sudo /etc/init.d/mysqld start
MySQL データベースを初期化中:                              [  OK  ]
Installing validate password plugin:                       [  OK  ]
mysqld を起動中:                                           [  OK  ]

パスワードを調べる

sudo cat /var/log/mysqld.log | grep 'password is generated';
> 2017-02-27T04:47:14.134975Z 4 [Note] A temporary password is generated for root@localhost: XXXXXXXXXXXX

5.7.6以降からこうなってるみたいです。 ( http://www.hack-log.net/entry/2015/10/26/100000 )
最近もっぱらRDS Auroraばっかりだったから知らなかった。

検証用にはうざいので、validate_passwordを殺す

mysql -u root -p 
mysql> uninstall plugin validate_password;

初期設定

とりあえず、 mysql_secure_installation を実行。

使ってみる

バージョンの確認!

mysql> SELECT @@version;
+-----------+
| @@version |
+-----------+
| 8.0.0-dmr |
+-----------+
1 row in set (0.00 sec)

8がインストールされてる!

ちなみに5.6の場合

mysql> SELECT @@version;
+-----------+
| @@version |
+-----------+
| 5.6.31    |
+-----------+
1 row in set (0.00 sec)

MyISAMが撲滅されたらしいので、確認してみる

mysql> SELECT DISTINCT(engine) FROM INFORMATION_SCHEMA.TABLES  WHERE table_schema = 'information_schema';
+--------+
| ENGINE |
+--------+
| NULL   |
+--------+
1 row in set (0.00 sec)

NULLになってる。

ちなみに5.6だと

mysql> SELECT DISTINCT(engine) FROM INFORMATION_SCHEMA.TABLES  WHERE table_schema = 'information_schema';
+--------+
| engine |
+--------+
| MEMORY |
| MyISAM |
+--------+
2 rows in set (0.04 sec)

ちなみにちなみにAuroraだと

mysql> SELECT @@version;
+-----------+
| @@version |
+-----------+
| 5.6.10    |
+-----------+
1 row in set (0.04 sec)

mysql> SELECT @@aurora_version;
+------------------+
| @@aurora_version |
+------------------+
| 1.10.1           |
+------------------+
1 row in set (0.04 sec)

mysql> SELECT DISTINCT(engine) FROM INFORMATION_SCHEMA.TABLES  WHERE table_schema = 'information_schema';
+--------+
| engine |
+--------+
| MEMORY |
| MyISAM |
+--------+
2 rows in set (0.04 sec)

ロールを使ってみる

// 適当にDB作る
mysql> CREATE DATABASE alice_database DEFAULT CHARSET = utf8;
Query OK, 1 row affected (0.00 sec)

mysql> CREATE DATABASE bob_database DEFAULT CHARSET = utf8;
Query OK, 1 row affected (0.02 sec)

// それっぽいroleを作る
mysql> CREATE ROLE 'queen';
Query OK, 0 rows affected (0.01 sec)

mysql> GRANT ALL ON alice_database.* TO 'queen';
Query OK, 0 rows affected (0.00 sec)

mysql> GRANT ALL ON bob_database.* TO 'queen';
Query OK, 0 rows affected (0.00 sec)

mysql> CREATE USER elizabeth@'%' IDENTIFIED BY 'test1234';
Query OK, 0 rows affected (0.00 sec)

mysql> GRANT 'queen' TO elizabeth@'%';
Query OK, 0 rows affected (0.00 sec)


// つくったユーザーで入り直す
$ mysql -u elizabeth -p
// まだ何もない
mysql> select current_role();
+----------------+
| current_role() |
+----------------+
| NONE           |
+----------------+
1 row in set (0.00 sec)

// 怒られる
mysql> use alice_database;
ERROR 1044 (42000): Access denied for user 'elizabeth'@'%' to database 'alice_database'

// ロールをセット
mysql> set role 'queen';
Query OK, 0 rows affected (0.00 sec)

mysql> select current_role();
+----------------+
| current_role() |
+----------------+
| `queen`@`%`    |
+----------------+
1 row in set (0.00 sec)

// つかえる
mysql> use alice_database;
Database changed

// こっちも使える
mysql> use bob_database;
Database changed

複数のDBを同じサーバーにまとめることがあるので、これは便利そう。

気が向いたら、他にも検証をしてみる。

続きを読む

Lambda + Apex + API Gateway Slackで緊急連絡先を表示するslashコマンドを作ろう

概要

  • Slackで/tel_listと打ったら緊急連絡先が出るようにする
  • serverless実装

構成図

スクリーンショット 2017-02-20 13.59.25.png

  • slackでtel_list打つ
  • api gatewayがPOSTリクエスト受け取る
  • 指定したlambdaを実行。そこでKMSでslackのtoken認証する
  • lambdaでpython実行し、slackに結果を送る

内容

①apexでlambda関数を管理する

  • ここではKMSのtokenがまだできてないので、コードは書きません
  • apexの使い方やインストール等はこちらをご覧ください
$ mkdir apex_slack_tel_list && cd apex_slack_tel_list
$ apex init
Project name: apex_slack_tel_list
Project description: apex_slack_tel_list is able to show TEL list when anyone was in trouble

### 一度deployする
$ cd apex_slack_tel_list
$ apex deploy

②IAM(KMS)の設定

  • IAM->暗号化キー->リージョンを選んでください->キーの作成

    • エイリアス:apex_lambda_slack_tel_list
    • キーマテリアルオリジン:KMS
    • 次へ
      • タグの追加はなしで次のステップへ

        • apex_slack_tel_list_lambda_function
        • キーの管理者が削除できるようにをチェック入れる
        • 次のステップへ
          • apex_slack_tel_list_lambda_function
          • 次のステップへ

スクリーンショット 2017-02-20 14.21.27.png

  • 完了したらここのARN部分を控えてください。
  • arn:aws:kms:リージョン名:数字:key/key_idとなってます。

③IAMのroleにARNに対する権限追加

  • IAM->ロール->apex_slack_tel_list_lambda_function->ポリシー名->編集
        {
            "Effect": "Allow",
            "Action": [
                "kms:Decrypt"
            ],
            "Resource": [
                "arn:aws:kms:ap-northeast-1:xxxxxxxxxx:key/xxxxxxxxxxxxx"
            ]
        }

④SlackでSlashコマンドの登録

  • slackのapp->Apps & Integration->Slashで検索->Add Configuration->/tel_list で登録

スクリーンショット 2017-02-20 14.24.58.png

  • 登録が終わったらtokenを控えてください

⑤Slackのtokenを暗号化

$ aws kms encrypt --key-id arnに書いてるkey_id --plaintext="slackのtoken"
{
    "KeyId": "arn:aws:kms:ap-northeast-1:xxxxxxxx:key/xxxxxxxxxxxx",
    "CiphertextBlob": "暗号化されたtoken"

⑥apexでコードを書いてdeployする

$ vim functions/hello/main.py
# -*- coding: utf-8 -*-
import boto3
from base64 import b64decode
from urlparse import parse_qs
import logging

ENCRYPTED_EXPECTED_TOKEN = "暗号化されたtoken"

kms = boto3.client('kms')
expected_token = kms.decrypt(CiphertextBlob = b64decode(ENCRYPTED_EXPECTED_TOKEN))['Plaintext']

logger = logging.getLogger()
logger.setLevel(logging.INFO)

def handle(event, context):
    req_body = event['body']
    params = parse_qs(req_body)
    token = params['token'][0]
    if token != expected_token:
        logger.error("Request token (%s) does not match exptected", token)
        raise Exception("Invalid request token")
    tel_list = [
        'sion:000-0000-0000',
        'cojp:111-1111-1111',
    ]

    format_tel_list = 'n'.join(tel_list)
    return { "text": "```%s```" % (format_tel_list) }
### いらないファイルを削除し、最終的にはこんなディレクトリになります
$ tree
├── functions
│   └── hello
│       └── main.py
└── project.json

### deploy
$ cd apex_slack_tel_list
$ apex deploy
  • lambdaからテストしてみましょう。成功するはずです。

⑦API GatewayでAPIを作成

  • API GATEWAY->API->APIの作成

    • 新しいAPI
    • API名:apex_slack_tel_list
    • 説明:lambda経由でslackで緊急連絡先を表示させてます
    • 作成を押す

スクリーンショット 2017-02-17 15.51.10.png

⑧リソースを作成する

  • apex_slack_tel_list->アクション->リソースの作成

    • リソース名:slack
    • リソースパス:slack
    • リソースの作成

スクリーンショット 2017-02-20 14.38.34.png

⑨メソッドを作成する

  • POST
  • apex_slack_tel_list->アクション->メソッドの作成
    • Lambda関数
    • region: 選んでください
    • Lambda関数: apex_slack_tel_list_hello
    • 保存を押す
  • slack/->出来上がったPOSTをクリック->統合リクエスト->本文マッピングテンプレート
    • application/x-www-form-urlencoded
    • テンプレートに書きを登録し保存。(今回は不要ですが、urlDecodeで日本語をPOSTしてもエラーが起きないようにしてます)
{ "body": $util.urlDecode($input.json("$")) }

⑩API-Gatewayのdeployする

  • リソース->アクション->APIのdeploy

    • デプロイされるステージにprod
    • デプロイ
  • 作成が終わったら、ステージ->prod->URLの呼び出し のurlを保存。
  • これを先ほどのslackに登録してください。

⑪実際にslackで打ってみよう

/tel_list
  • すると下記のように自分自身だけに緊急連絡先が表示されるかと思います。

スクリーンショット 2017-02-20 14.49.35.png

最後に

  • Hubot用にサーバを立てなくて良いし、serverless便利

続きを読む

初心者がAWS(EC2)にアプリをデプロイする際に対峙したエラーとその対応

はじめに

デプロイの際に遭遇したエラーたちとの奮闘記です。
まとまってませんが、皆さんのエラー解決のヒントになれば幸いです。

Version等

Ruby: 2.3.1
Rails: 5.0.1
AWS(EC2)・Nginx・Unicorn・Mysqlの組み合わせ。

① rails コマンドが使えない

terminal
[ec2-user@ip-XXX-XX-XX-XXX teatapp]$ rails secret
-bash: rails: コマンドが見つかりません

解決策

exitで一回ec2を出て入り直すとコマンドが使えるようになる。
なんか変なこと起きたらとりあえず再起動しよう。ド初歩 …。

② Mysql2::Error: Can’t connect to local MySQL server through socket ‘/tmp/mysql.sock’ (2)

「’/tmp/mysql.sock’のソケットじゃ、MySQLと通信できません」とのエラー。アプリケーションが指定しているソケットと、データベース側のソケットが食い違っているために通信ができない模様。
ソケットに関しては以下でざっくり掴めると思います。
IT用語辞典e-Words
「分かりそう」で「分からない」でも「分かった」気になれるIT用語辞典

terminal
$ mysql_config --socket

このコマンドでmysql側のソケットタイプが調べられるので調べてみると、

terminal
[ec2-user@ip-XXX-XX-XX-XXX ~]$ mysql_config --socket
/var/lib/mysql/mysql.sock

/var/lib/mysql/mysql.sockこういうタイプのソケットらしい。
一方、アプリ側のソケットを調べてみると、

database.yml
default: &default
  adapter: mysql2
  encoding: utf8
  pool: 5
  username: root
  password:
  socket: /tmp/mysql.sock

/tmp/mysql.sockと、Mysql側のソケットとタイプが異なる。

解決策

ということでアプリ側のソケットの記述を以下のように修正。

database.yml
default: &default
  adapter: mysql2
  encoding: utf8
  pool: 5
  username: root
  password:
  socket: /tmp/mysql.sock

 (中略)

production:
  <<: *default
  database: testapp_production
  username: testapp
  password: <%= ENV['TESTAPP_DATABASE_PASSWORD'] %>
  socket: /var/lib/mysql/mysql.sock

default部分の記述は残したままで、productionの方に加筆すればOK。
これで、ソケットタイプが合致してアプリとデータベースが通信できるようになりました。

と、思いきや

③ ERROR — : Access denied for user ‘testapp’@’localhost’ (using password: NO) (Mysql2::Error)

terminal
[ec2-user@ip-XXX-XX-XX-XXX testapp]$ unicorn_rails -c config/unicorn.rb -E production

のコマンドでunicornを作動しても、うまく立ち上がらず。

何が起こっているのか、エラーの詳細を確認するために、以下のコマンドを実行。

terminal
[ec2-user@ip-XXX-XX-XX-XXX testapp]$ less log/unicorn.stderr.log

log/unicorn.stderr.logのログを辿ると、以下のような記述が。

log/unicorn.stderr.log
ERROR -- : Access denied for user 'testapp'@'localhost' (using password: NO) (Mysql2::Error)

using password: NOということで、どうやら環境変数で設定したパスワードが読み込まれていない模様。
①アプリ側の環境変数を読み込んでいるところの記載
②EC2側の環境変数を書き込んでいるところの記載
をチェックしてみると…

database.yml
production:
  <<: *default
  database: testapp_production
  username: testapp
  password: <%= ENV['TESTAPP_DATABASE_PASSWORD'] %>
  socket: /var/lib/mysql/mysql.sock

アプリ側は、password: <%= ENV['TESTAPP_DATABASE_PASSWORD'] %>との記述。
一方EC2側はの記載を以下のコマンドでチェックしてみると、

terminal
$ sudo vim /etc/environment
etc/environment
DATABASE_PASSWORD='XXXXXXXXXXXXXXX'
SECRET_KEY_BASE='XXXXXXXXXXXXXXX'

DATABASE_PASSWORD='XXXXXXXXXXXXXXX'との記述。

変数がアプリ側とEC2側で食い違ってました。

解決策

ということで、アプリ側の記述を以下のように変更。

database.yml
production:
  <<: *default
  database: testapp_production
  username: testapp
  password: <%= ENV['DATABASE_PASSWORD'] %>   # 「TESTAPP_」を削除
  socket: /var/lib/mysql/mysql.sock

これで環境変数が正しく読み込まれるようになりました。
ちなみにですが、
① Mysqlのroot user passwordの先頭が「0」で始まる場合、環境変数の読み込みがうまくいかない場合がある
② 環境変数名に「-」(ハイフン)を含めると、環境変数の読み込みがうまくいかない場合がある
みたいです。

環境変数を

etc/environment
NEW-APP_DATABASE_PASSWORD='01234567'
SECRET_KEY_BASE='XXXXXXXXXXXXXXX'

のように変数名にハイフンを含めており、パスワードの先頭を0で始めていて、大っ変痛い目に会いました。

ちなみのちなみに、環境変数がちゃんと読み込まれているか確認するには、

terminal
[ec2-user@ip-XXX-XX-XX-XXX testapp]$ rails c

でコンソールを開いて、

terminal
irb(main):001:0> ENV['DATABASE_PASSWORD']
=> "XXXXXXXX"

と環境変数を打ってあげればちゃんと読み込めているか確認できます。

さて、これでunicornが使えるかと思いきやまたMysqlのエラー。

④ ERROR — : Access denied for user ‘testapp’@’localhost’ (using password: YES) (Mysql2::Error)

ソケットも合わせて、
環境変数も読み込めるようにして、
でもエラー。エラー文を見てみると、なんかユーザーのところが「testapp」になっている。ここが原因。

database.yml
default: &default
  adapter: mysql2
  encoding: utf8
  pool: 5
  username: root
  password:
  socket: /tmp/mysql.sock

 (中略)

production:
  <<: *default
  database: testapp_production
  password: <%= ENV['TESTAPP_DATABASE_PASSWORD'] %>
                                                                                   # productionのuser部分を削除。
  socket: /var/lib/mysql/mysql.sock

解決策

productionのuser部分を削除(するとdefaultのusername、つまりrootがproductionでも読み込まれるようになる)。

これで、

terminal
$ unicorn_rails -c config/unicorn.rb -E production

をしてあげるとunicornが立ち上がりました。Mysqlとの通信もうまくいっている模様。でも、

⑤ Unicornは動いているのにページが表示されない

http:// ~Elastic IP~ :3000/でページにアクセスしても、ロードが続くだけで表示されず。

スクリーンショット 2017-02-16 18.28.12.png

と出てきてしまう。ううう…。

これは原因は単純でポートの開け忘れでした。

スクリーンショット 2017-02-16 18.29.40.png

AWSでポート3000を開けてあげて、

ページが表示されました。

参考サイト

IT用語辞典e-Words
「分かりそう」で「分からない」でも「分かった」気になれるIT用語辞典

続きを読む

zabbixを使ってRDS for Auroraの内部ステータスを取得してみました。

目的

Aurora のステータス情報をzabbixで取得したい

手法

Auroraををzabbixで直接参照することはできない(RDSにzabbix-Agentは入らないので)
代替えのインスタンスを起動して、EC2のAgentを使ってAWS Auroraから値を取得する。

検証環境

・Aurora 5.6.10a
・Auroraエンドポイント用のEC2インスタンス(zabbixサーバへ登録済み)
・zabbixサーバ

詳しい手順

Auroraへzabbix監視ユーザの登録

grant process on *.* to 'zabbix'@'%' identified by 'zbxpass';

EC2インスタンスに.my.cnfの作成

$ sudo mkdir /var/lib/zabbix
$ sudo vim /var/lib/zabbix/.my.cnf
$ sudo chmod 400 /var/lib/zabbix/.my.cnf
$ sudo chown -R zabbix:zabbix /var/lib/zabbix

.my.cnfの中身

[client]
user = zabbix
password= zbxpass

/etc/zabbix/zabbix_agentd.d/userparameter_mysql.confの編集

変更前
UserParameter=mysql.status[*],echo "show global status where Variable_name='$1';" | HOME=/var/lib/zabbix mysql -N | awk '{print $$2}'
変更後
UserParameter=mysql.status[*],echo "show global status where Variable_name='$1';" | HOME=/var/lib/zabbix mysql -hxxxxxxx.cluster-xxxxxxx.ap-northeast-1.rds.amazonaws.com -N | awk '{print $$2}'
変更前
UserParameter=mysql.size[*],bash -c 'echo "select sum($(case "$3" in both|"") echo "data_length+index_length";; data|index) echo "$3_length";; free) echo "data_free";; esac)) from information_schema.tables$([[ "$1" = "all" || ! "$1" ]] || echo " where table_schema="$1"")$([[ "$2" = "all" || ! "$2" ]] || echo "and table_name="$2"");" | HOME=/var/lib/zabbix mysql -N'
変更後
UserParameter=mysql.size[*],bash -c 'echo "select sum($(case "$3" in both|"") echo "data_length+index_length";; data|index) echo "$3_length";; free) echo "data_free";; esac)) from information_schema.tables$([[ "$1" = "all" || ! "$1" ]] || echo " where table_schema="$1"")$([[ "$2" = "all" || ! "$2" ]] || echo "and table_name="$2"");" | HOME=/var/lib/zabbix mysql -hxxxxxxx.cluster-xxxxxxx.ap-northeast-1.rds.amazonaws.com -N'
変更前
serParameter=mysql.ping,HOME=/var/lib/zabbix mysqladmin ping | grep -c alive
変更後
serParameter=mysql.ping,HOME=/var/lib/zabbix mysqladmin -hxxxxxxx.cluster-xxxxxxx.ap-northeast-1.rds.amazonaws.com ping | grep -c alive

これで疑似的にEC2上で動くMySQLとして認識されるのでRDSでTemplate App MySQLを利用することができる。

クラスタエンドポイントや、読み込みエンドポイントをそれぞれ指定すれば
両方のステータス値が取得できる。

デメリットとして、RDSが複数ある場合、その台数分の代替えインスタンスが必要になるが
zabbix-agentを動かすだけなら、t2.nanoでも十分かも。

続きを読む

AWS EC2において管理をしやすくするためにChef-zeroを使えるようにする

正直、サーバの管理方法をいい加減確立しないといけないと思うので、覚書。

構成として、EC2にはCentOS7ないしRHEL7を使う

Chef Development Kitインストール

chefの構成要素を一通り含まれているChef Development Kit(以降chefDK)をインストール。
chefDKをインストールすればchef-zero(Chef Client Local Mode)を使用することが可能。
※Chef soloとChef zeroの違いは、スタンドアロン専用のsoloとは異なり、フルバージョンのサーバ/クライアントが動いているので、展開が容易なところ。
chefDKを公開しているサイト( https://downloads.chef.io/ )からrpmを取得してインストール。

rpm -Uvh https://packages.chef.io/files/stable/chefdk/1.2.22/el/7/chefdk-1.2.22-1.el7.x86_64.rpm

knife-zeroインストール

kinfe-zeroはchefDKに含まれていないのでchefコマンドでインストール。
※気を付けないといけないのが、先にknife-zeroをgemでインストールしようとすると通常のchefが入ってしまうので、chefDKのインストールが完了していることを確認する。

chef gem install knife-zero --no-document

Chefのリポジトリ作成する

リポジトリを作成したいディレクトリでコマンドを実行。
※他のパッケージ管理ツールと名称やパスが被らないように注意する。
特に、yumのレポジトリとコマンドが似ているので、うわのそらで実行して失敗したりしないように注意

chef generate repo chef-repo

kinfe-zeroを使用するための設定

ローカルモードを有効とする設定ファイルを作成。

cd chef-repo
mkdir .chef
vim .chef/knife.rb

knife.rbには、動かすだけであれば
local_mode true
だけ、記述すればいいが、正直それだとBootstrapした場合、ノード情報が多すぎるので、ホワイトリストを利用して必要な情報だけ取得するようにする。

knife.rb
local_mode true
knife[:automatic_attribute_whitelist] = [
  "fqdn/",
  "os/",
  "os_version/",
  "hostname",
  "ipaddress/",
  "roles/",
  "recipes/",
  "ipaddress/",
  "platform/",
  "platform_version/",
  "platform_version/",
  "cloud/",
  "cloud_v2/",
  "ec2/ami_id/",
  "ec2/instance_id/",
  "ec2/instance_type/",
  "ec2/placement_availability_zone/",
  "chef_packages/"
]

正直、これでも多いけど、これより減らすと管理が面倒になるのでこのくらいが妥当

リモートサーバに対してbootstrap実行

リモートサーバをノードに追加。
追加されたノードをnode listコマンドで確認可能。

knife zero bootstrap infra -x ec2-user --hint ec2 --sudo
knife zero bootstrap {サーバー名 or IP} -x ec2-user -i {秘密鍵のパス} --hint ec2 -N {サーバーの名称}
knife node list

bootstrapが完了するとnodeディレクトリ以下にjsonファイルが生成される。
ノード追加時に-Nオプションをつけるとノード名を指定することが可能。

クックブックの作成

サーバを構築するためのクックブックを作成。

knife cookbook create {クックブック名}

完了するとcookbook以下にクックブック名のディレクトリが作成される。
デフォルトはcookbook/recipes/

レシピの作成

例としてApache+PHP7のレシピを作成。
apacheはphpの依存でインストールされるのでインストールは特に記述していない。
cookbook/recipes/default.rbのファイルを次のように修正する。

default.rb

package 'git' do
  action :install
end

package 'epel-release.noarch' do
  action :install
end

remote_file "#{Chef::Config[:file_cache_path]}/remi-release-7.rpm" do
    source "http://rpms.famillecollet.com/enterprise/remi-release-7.rpm"
    not_if "rpm -qa | grep -q '^remi-release'"
    action :create
    notifies :install, "rpm_package[remi-release]", :immediately
end

rpm_package "remi-release" do
    source "#{Chef::Config[:file_cache_path]}/remi-release-7.rpm"
    action :nothing
end

package 'php' do
  flush_cache [:before]
  action :install
  options "--enablerepo=remi --enablerepo=remi-php70"
end

%w(php-openssl php-common php-mbstring php-xml).each do |pkg|
  package pkg do
    action :install
    options "--enablerepo=remi --enablerepo=remi-php70"
  end
end

bash 'install_composer' do
  not_if { File.exists?("/usr/local/bin/composer") }
  user 'root'
  cwd '/tmp'
  code <<-EOH
    curl -sS https://getcomposer.org/installer | php -- --install-dir=/tmp
    mv /tmp/composer.phar /usr/local/bin/composer
  EOH
end

packages = %w(unzip fontconfig-devel)
packages.each do |pkg|
  package pkg do
    action :install
  end
end

ノードにクックブックを登録

レシピを実行するためにノードにクックブックを登録する。

knife node run_list add {ノード名} {クックブック名}

レシピの実行

レシピを実行する。

knife zero converge '{ノード名}' --attribute ec2.local_ipv4 -x ec2-user --sudo -i {鍵のパス}

正常終了するとリモート先のサーバにApache+PHP7がインストールされる。

続きを読む

Apache Sparkによる大規模データの分散処理による機械学習(回帰分析) by Amazon EMR

EMRでApache Sparkを使用するに当たって、必要なデータを処理するためのコードと入力データを用意するだけで、面倒な環境構築を行わず、わずかばかりの設定を加えるだけですぐに使用することができます。

ここでは、最初にScalaによるEMR上でのSpark処理、次に他のサービスのトリガーをきっかけに動くJavaによるLambdaでEMRを呼び出す手順を注意点も含めて詳細なメモを書きます。

Scalaによる回帰分析を行う実行ファイルの作成

環境構築

scalaをインストール。

$ cd /tmp
$ curl -O http://downloads.typesafe.com/scala/2.11.8/scala-2.11.8.tgz
$ tar xzf scala-2.11.8.tgz
$ mkdir -p /usr/local/src
$ mv scala-2.11.8 /usr/local/src/scala

$ export PATH=$PATH:/usr/local/src/scala/bin
$ export SCALA_HOME=/usr/local/src/scala

$ which scala

Scalaのパッケージマネージャであるsbtをインストール。

$ brew install sbt
$ which sbt

プロジェクトの作成

プロジェクトを置くディレクトリを作成。

mkdir SparkExampleApp

ビルドの設定ファイルの作成。

vim build.sbt

以下のように記述する。

name := "Spark Sample Project"
version := "1.0"
scalaVersion := "2.11.8"

libraryDependencies ++= Seq(
  "org.apache.spark" %% "spark-core" % "2.0.0",
  "org.apache.spark" %% "spark-mllib" % "2.0.0",
  "org.apache.spark" %% "spark-sql" % "2.0.0",
  "com.databricks" %% "spark-csv" % "1.4.0"
)

実行ファイルの作成。

mkdir -p src/main/scala
vim src/main/scala/SparkExampleApp.scala

SparkExampleApp.scalaは以下のものを使用。

import org.apache.spark.sql.SparkSession
import org.apache.spark.ml.{Pipeline, PipelineModel}
import org.apache.spark.ml.feature._
import org.apache.spark.ml.regression.LinearRegression

object SparkExampleApp {

  def main(args: Array[String]) {
    val spark = SparkSession
      .builder()
      .appName("Spark Sample App")
      .getOrCreate()

    import spark.implicits._

    spark.sparkContext.hadoopConfiguration.set("fs.s3n.awsAccessKeyId", "AKIA****************")
    spark.sparkContext.hadoopConfiguration.set("fs.s3n.awsSecretAccessKey", "****************************************")

    val filePath = args(0)
    val df = spark.sqlContext.read
        .format("com.databricks.spark.csv")
        .option("header", "true")
        .option("inferSchema", "true")
        .load(filePath)

    val assembler = new VectorAssembler()
      .setInputCols(Array("x"))
      .setOutputCol("features")

    val polynomialExpansion = new PolynomialExpansion()
      .setInputCol(assembler.getOutputCol)
      .setOutputCol("polyFeatures")
      .setDegree(4)

    val linearRegression = new LinearRegression()
      .setLabelCol("y")
      .setFeaturesCol(polynomialExpansion.getOutputCol)
      .setMaxIter(100)
      .setRegParam(0.0)

    val pipeline = new Pipeline()
      .setStages(Array(assembler, polynomialExpansion, linearRegression))


    val Array(trainingData, testData) = df.randomSplit(Array(0.7, 0.3))
    val model = pipeline.fit(trainingData)

    val outputFilePath = args(1)
    model.transform(testData)
      .select("x", "prediction")
      .write
      .format("com.databricks.spark.csv")
      .option("header", "false")
      .save(outputFilePath)

  }
}

jarファイルの作成のために以下のコマンドを実行。

sbt package

生成されたtarget/scala-2.11/spark-sample-project_2.11-1.0.jarをS3のsample-bucket/sample-emr/srcにアップロードしておく。

Apache Sparkの設定

http://spark.apache.org/downloads.html
上のURL上で以下のように入力してApache Sparkのデータをダウンロードしてくる。

  1. Choose a Spark release: 2.0.1(Oct 03 2016)
  2. Choose a package type: Pre-build for Hadoop 2.7 and later
  3. Choose a download type: Direct download
  4. Download Spark: spark-2.0.1-bin-hadoop2.7.tgz

ダウンロードしたファイルに移動する。

cd ~/Downloads/spark-2.0.1-bin-hadoop2.7

conf以下にspark-defaults.confというファイルを以下の内容で作成する。

vim conf/spark-defaults.conf
spark-defaults.conf
spark.jars.packages  com.amazonaws:aws-java-sdk:1.7.4,org.apache.hadoop:hadoop-aws:2.7.1

実験用データの作成

ruby -e 'puts "x,y";Range.new(0,5).step(0.05).each {|i| puts "#{i},#{Math.sin(i)+Random.rand(-0.3..0.3)}"}' > data.csv

sample.jpg

グラフの表示

brew install gnuplot
gnuplot
gnuplot> set datafile separator ","
gnuplot> set terminal jpeg
gnuplot> set output "sample.jpg"
gnuplot> plot "data.csv" every ::1
qlmanage -p sample.jpg

実行

S3にバケットを作成しておき、以下のようなバケットポリシーを作成しておく。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "GetObject",
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::sample-bucket/*"
        },
        {
            "Sid": "ListBucket",
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:ListBucket",
            "Resource": "arn:aws:s3:::sample-bucket"
        }
    ]
}

Sparkの実行

./bin/spark-submit --class SparkExampleApp --master local ~/workspace/SparkExampleApp/target/scala-2.11/spark-sample-project_2.11-1.0.jar s3n://sample-bucket/sample-emr/data.csv s3n://sample-bucket/sample-emr/output.csv

実行後の結果は以下のようになる。

sample.jpg

EMRの実行

1.マネジメントコンソールからEMRを選択。
2.[クラスターを作成]を選択して以下のように記述する。

クラスター名: SampleCluster
ログ記録: チェック
S3フォルダー: s3://sample-bucket/sample-emr/logs
起動モード: ステップ実行

ステップタイプ: Spark アプリケーション

[設定]を選択し、ステップを追加の設定を行う。

3.ステップを追加で以下のように入力して、[追加]を選択。

名前: SparkApplication
デプロイモード: クラスター
Spark-submitオプション: –class SparkExampleApp
アプリケーションの場所: s3://sample-bucket/sample-emr/src/spark-sample-project_2.11-1.0.jar
引数: s3n://sample-bucket/sample-emr/data.csv s3n://sample-bucket/sample-emr/output/output.csv
失敗時の操作: クラスターを終了。

ベンダー: Amazon
リリース: emr-5.0.3
アプリケーション: Hadoop 2.7.3, Spark 2.0.1

インスタンスタイプ: m3.xlarge
インスタンス数: 2

アクセス権限: デフォルト
EMRロール: EMR_DefaultRole
EC2 インスタンスプロファイル: EMR_EC2_DefaultRole

4.[クラスターを作成]を選択。

5.デプロイモードはクライアントとクラスターのうちのクラスターを選択。
6.失敗時の操作は、次へ、キャンセルして待機、クラスターを終了からクラスターを終了を選択。

7.起動モードはクラスター、ステップ実行のうちのステップ実行を選択。
8.ステップタイプはストリーミングプログラミング、Hiveプログラム、Pigプログラム、Spark アプリケーション、カスタムJARからSpark アプリケーションを選択。

しばらくすると、EMRのクラスターが終了し、出力先にファイルが生成されていることが確認できる。

JavaによるLambdaからのEMR呼び出し

Eclipseでプロジェクト作成

Apache Mavenを利用してプロジェクト管理する場合。

1.File > New > Other…からMaven > Maven Project
デフォルト設定のまま進めるので、3回[Next]を選択。

Group Id: com.sample.lambda
Artifact Id: com.sapmle.lambda
Version: 0.0.1-SNAPSHOT
Package: com.sample.lambda

[Finish]を選択。

2.pom.xmlを選択してOverviewタブ中の以下を編集。
Name: lambda-java

3.Dependenciesタブを選択。
[Add…]を選択し、以下のように記述して、aws-lambda-java-core: 1.0.0aws-lambda-java-eventsaws-java-sdk-coreaws-java-sdk-emrを追加する。

Group Id: com.amazonaws
Artifact Id: aws-lambda-java-core
Version: 1.0.0
Group Id: com.amazonaws
Artifact Id: aws-lambda-java-events
Version: 1.0.0
Group Id: com.amazonaws
Artifact Id: aws-java-sdk-core
Version: 1.11.49
Group Id: com.amazonaws
Artifact Id: aws-java-sdk-emr
Version: 1.11.49

次に重要なのが

pom.xmlを右クリックして、Maven > Add Plugin を選択し、以下のように入力する。

Group Id: org.apache.maven.plugins
Artifact Id: maven-shade-plugin
バージョン: 2.3

プロジェクトをビルドするために、Package Exploreからcom.sample.lambdaを右クリックして、Run As > Maven Build を実行する。

作成したpom.xmlは以下のようになる。

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>com.sample.lambda</groupId>
  <artifactId>com.sample.lambda</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>jar</packaging>

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

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>com.amazonaws</groupId>
        <artifactId>aws-lambda-java-core</artifactId>
        <version>1.0.0</version>
    </dependency>
    <dependency>
        <groupId>com.amazonaws</groupId>
        <artifactId>aws-lambda-java-events</artifactId>
        <version>1.0.0</version>
    </dependency>
    <dependency>
        <groupId>com.amazonaws</groupId>
        <artifactId>aws-java-sdk-emr</artifactId>
        <version>1.11.49</version>
    </dependency>
    <dependency>
        <groupId>com.amazonaws</groupId>
        <artifactId>aws-java-sdk-core</artifactId>
        <version>1.11.49</version>
    </dependency>
  </dependencies>
  <name>lambda-java</name>
  <build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-shade-plugin</artifactId>
            <version>2.3</version>
        </plugin>
    </plugins>
  </build>
</project>

4.src/main/java中のcom.sample.lambdaのSampleCreateEMRClusterHandler.javaを以下のように作成する。

SampleCreateEMRClusterHandler.java
package com.sample.lambda;

import java.util.ArrayList;
import java.util.List;

import com.amazonaws.auth.AWSCredentialsProvider;
import com.amazonaws.auth.EnvironmentVariableCredentialsProvider;
import com.amazonaws.regions.Region;
import com.amazonaws.regions.Regions;

import com.amazonaws.services.elasticmapreduce.AmazonElasticMapReduceClient;
import com.amazonaws.services.elasticmapreduce.model.ActionOnFailure;
import com.amazonaws.services.elasticmapreduce.model.Application;
import com.amazonaws.services.elasticmapreduce.model.HadoopJarStepConfig;
import com.amazonaws.services.elasticmapreduce.model.InstanceGroupConfig;
import com.amazonaws.services.elasticmapreduce.model.InstanceRoleType;
import com.amazonaws.services.elasticmapreduce.model.JobFlowInstancesConfig;
import com.amazonaws.services.elasticmapreduce.model.RunJobFlowRequest;
import com.amazonaws.services.elasticmapreduce.model.RunJobFlowResult;
import com.amazonaws.services.elasticmapreduce.model.StepConfig;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;

public class SampleCreateEMRClusterHandler implements RequestHandler<Object, String> {

    public String handleRequest(Object event, Context context) {
        AWSCredentialsProvider cp = new EnvironmentVariableCredentialsProvider();
        AmazonElasticMapReduceClient emr = new AmazonElasticMapReduceClient(cp);
        emr.setRegion(Region.getRegion(Regions.US_EAST_1));

        JobFlowInstancesConfig instanceConfig = new JobFlowInstancesConfig()
                .withKeepJobFlowAliveWhenNoSteps(false)
                .withInstanceGroups(buildInstanceGroupConfigs());

        RunJobFlowRequest request = new RunJobFlowRequest()
                .withName("SampleCluster")
                .withReleaseLabel("emr-5.0.3")
                .withLogUri("s3://sample-bucket/sample-emr/logs/")
                .withServiceRole("EMR_DefaultRole")
                .withJobFlowRole("EMR_EC2_DefaultRole")
                .withVisibleToAllUsers(true)
                .withInstances(instanceConfig)
                .withApplications(buildApplications())
                .withSteps(buildStepConfigs());

        RunJobFlowResult result = emr.runJobFlow(request);
        return "Process complete.";
    }

    private List<Application> buildApplications() {
        List<Application> apps = new ArrayList<Application>();
        apps.add(new Application().withName("Hadoop"));
        apps.add(new Application().withName("Spark"));
        return apps;
    }

    private List<InstanceGroupConfig> buildInstanceGroupConfigs() {
        List<InstanceGroupConfig> result = new ArrayList<InstanceGroupConfig>();
        InstanceGroupConfig masterInstance = new InstanceGroupConfig()
                .withName("MasterNode")
                .withInstanceRole(InstanceRoleType.MASTER)
                .withInstanceCount(1)
                .withInstanceType("m3.xlarge");
        result.add(masterInstance);

        InstanceGroupConfig coreInsetance = new InstanceGroupConfig()
                .withName("CoreNode")
                .withInstanceRole(InstanceRoleType.CORE)
                .withInstanceCount(1)
                .withInstanceType("m3.xlarge");
        result.add(coreInsetance);

        return result;
    }

    private List<StepConfig> buildStepConfigs() {
        List<StepConfig> result = new ArrayList<StepConfig>();

        final String[] args = {
                "spark-submit",
                "--deploy-mode", "cluster",
                "--class", "SparkExampleApp",
                "s3://sample-bucket/sample-emr/src/spark-sample-project_2.11-1.0.jar", 
                "s3n://sample-bucket/sample-emr/data.csv",
                "s3n://sample-bucket/sample-emr/output/output.csv"
        };

        final StepConfig sparkStep = new StepConfig()
                .withName("SparkProcess")
                .withActionOnFailure(ActionOnFailure.TERMINATE_CLUSTER)
                .withHadoopJarStep(new HadoopJarStepConfig()
                        .withJar("command-runner.jar")
                        .withArgs(args));
        result.add(sparkStep);

        return result;
    }

}

以上実行コードの完成。プロジェクトを再度ビルドする。

(追記) TerminalからのMavenによるビルド方法

Mavenをインストールしていない場合は以下の手順でインストールする。

Mavenを以下のURLからダウンロードしてくる。
http://maven.apache.org/download.cgi

mv apache-maven-3.3.9 /usr/local

.zshrcに以下を追加

export M3_HOME=/usr/local/apache-maven-3.3.9
M3=$M3_HOME/bin
export PATH=$M3:$PATH
source ~/.zshrc

プロジェクト直下にterminalで移動し、以下を実行する。

bashを使用している場合は、~/.zshrc~/.bashrcに置き換えて考える。

mvn package

target以下にcom.sapmle.lambda-0.0.1-SNAPSHOT.jarが生成される。こちらがLambdaにアップロードするものとなる。

IAMロールの作成

マネジメントコンソールからIdentity and Access Management
[ロール]から[新しいロールの作成]を選択。

手順 1: ロール名の設定

以下のように入力して[次のステップ]を選択。
ロール名: sampleRole

手順 2: ロールタイプの選択

AWS Lambdaを選択。

手順 3: 信頼性の確立

信頼関係はLambdaに設定されて飛ばされる。

手順 4: ポリシーのアタッチ

AmazonElasticMapReduceRoleを選択して、[次のステップ]を選択。

手順 5: 確認

内容を確認したら、[ロールの作成]を選択。

Lambdaの作成

マネジメントコンソールからLamdaを選択。
[Create a Lambda function]を選択。

Select blueprint

Blank functionを選択。

Configure triggers

ここでは何も設定しないが、必要に応じてLambdaを呼び出す元となるサービスを指定する。
[Next]を選択。

Configure function

Name: SampleLambda
Runtime*: Java8

Code entry type: Upload a .ZIP or JAR file
Function package: [先ほど作成したJARファイルを指定]

Handler*: com.sample.lambda.SampleCreateEMRClusterHandler::handleRequest
Role*: Choose an existing role
Existing role: sampleRole

[Next]を選択。

Review

最後に設定内容を確認して、実際にTestを実行してみてエラーが出ないか確認してみる。

SNSによるLambdaのトリガー処理

SNSから先程作成したLambdaをトリガーするTopicを生成する。
ここでは、Node.jsから呼び出す例を取り上げます。

node.js
var AWS = require('aws-sdk');
AWS.config.update({
        accessKeyId: 'AKIAXXXXXXXXXXXXXXXX',
        secretAccessKey: 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
        region: 'us-east-1'
});

publishSNSTopic();

const publishSNSTopic = () => {
    var sns = new AWS.SNS({
        apiVersion: "2010-03-31",
        region: "us-east-1"
    });

    sns.publish({
        Message: 'Start Job by triggering lambda for EMR',
        Subject: "Trigger Lambda for EMR",
        TopicArn: "arn:aws:sns:us-east-1:xxxxxxxxxxxx:<トピック名>"
    }, function(err, data) {
        if (err) console.log("Failure to publish topic by SNS");
    });
}
$ node index.js

注意点

  • URI schemeはs3ではなくs3nを指定。
    s3: S3ブロックファイルシステム
    s3n: S3ネイティブファイルシステム

以前はs3nを使っていたが、今はs3を推奨。
サービス提供者のドキュメントを読んでそれに合わせることが大事。

  • EMRでログ記録はチェックするようにする
    エラーが出た時のデバッグの根拠になるから。

  • EMR: 出力先にファイルがあるとエラーになる。(output.csv/)

  • com/amazonaws/auth/AWSCredentialsProvider: class java.lang.NoClassDefFoundError

maven-shade-pluginを忘れずに追加することが重要。
https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/java-create-jar-pkg-maven-and-eclipse.html
https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/java-create-jar-pkg-maven-no-ide.html

  • java.lang.ClassNotFoundException: com.amazonaws.ClientConfigurationFactory

aws-java-sdk-coreaws-java-sdk-emrのバージョンを1.11.49に統一

http://stackoverflow.com/questions/36796268/java-lang-classnotfoundexception-com-amazonaws-clientconfigurationfactory

  • spark-submitコマンド実行時の注意点
spark-submit --deploy-mode cluster --class SparkExampleApp s3n://sample-bucket/sample-emr/src/spark-sample-project_2.11-1.0.jar s3n://sample-bucket/sample-emr/data.csv s3n://sample-bucket/sample-emr/output/output.csv

じゃなくて

spark-submit --deploy-mode cluster --class SparkExampleApp s3://sample-bucket/sample-emr/src/spark-sample-project_2.11-1.0.jar s3n://sample-bucket/sample-emr/data.csv s3n://sample-bucket/sample-emr/output/output.csv

原因は、EMRでS3を呼び出すときに、昔はs3nを使っており、今はs3を推奨しており、ScalaのAWSをライブラリはs3nで認識するのに対して、EMRはs3じゃないと受け付けないことが原因。

参考

Scalaをsbtでビルド
http://qiita.com/kanuma1984/items/6f599c815cc8f9232228

EMRでSpark
http://www.atmarkit.co.jp/ait/articles/1609/27/news018.html

JavaによるLambda作成
http://docs.aws.amazon.com/ja_jp/ElasticMapReduce/latest/DeveloperGuide/calling-emr-with-java-sdk.html

Lambda+Java
http://qiita.com/Keisuke69/items/fc39a2f464d14480a432

Lambda+EMR
http://qiita.com/yskazuma/items/b67b1f3f8c39a7a19051
http://qiita.com/RyujiKawazoe/items/534f4b069ebea2f7d26c

続きを読む

DarkforestをUbuntu14.04で動かしてみた

Darkforestを動かしていくまでを記録してみましたので、参考になればと思います
今回はAmazon EC2のg2.2xlergeを使っていきます
こちらは有料なので、使われる場合は金額をご確認ください
インスタンスの使用数に制限があり、デフォルトではg2.2xlargeは0(!)になっているかと思いますので、制限緩和のリクエストをしなければならないので、先にリクエストしておきましょう

OSはUbuntu14.04LTSを選択しました
DarkforestのGithubはこちら
https://github.com/facebookresearch/darkforestGo

gitインストール
$sudo apt-get install git

vimインストール
$sudo apt-get install vim

$makeインストール

$sudo apt-get install -y build-essential cmake

パスワード設定
$sudo passwd (username)

パッケージのアップデート
$sudo aptitude update

$sudo aptitude -y full-upgrade

torchインストール
$git clone https://github.com/torch/distro.git ~/torch –recursive

$cd ~/torch; bash install-deps;

$./install.sh

$source ~/.bashrc

デフォルトのグラフィックドライバを停止
$sudo vim /etc/modprobe.d/blacklist-nouveau.conf
以下入力
blacklist nouveau
blacklist lbm-nouveau
options nouveau modeset=0
alias nouveau off
alias lbm-nouveau off
sudo vim /etc/modprobe.d/nouveau-kms.conf
options nouveau modeset=0

$sudo update-initramfs -u

$sudo reboot

CUDAインストール
NVIDIA公式サイトにてCUDAをダウンロードする(最新版で良いと思います)
$sudo dpkg -i cuda-repo-ubuntu1404_8.0.44-1_amd64.deb

$sudo apt-get update

$sudo apt-get install cuda(長い)

$echo “export PATH=/usr/local/cuda/bin/:$PATH; export

$LD_LIBRARY_PATH=/usr/local/cuda/lib64/:$LD_LIBRARY_PATH; ” >>~/.bashrc && source ~/.bashrc

cuDNNインストール
公式サイトよりcudaのverに合わせてダウンロードする(こちらも最新版で良いと思います)
$tar -xvf cudnn-8.0-linux-x64-v5.1.tgz

$sudo cp cuda/include/*.h /usr/local/cuda/include

$sudo cp cuda/lib64/.so /usr/local/cuda/lib64

パッケージのインストール
githubでは以下だけだが、cunnも必要なようです(現段階では入れなくてもよい)
$sudo apt-get install luarocks

$luarocks install class

$luarocks install image

$luarocks install tds

$luarocks install cudnn

darkforestをインストール
$git clone https://github.com/facebookresearch/darkforestGo.git ~/darkforest –recursive

$cd ~/darkforest

$sh ./compile.sh

モデルファイルを作る
$mkdir ~/darkforest/models

全部モデルファイルに入れておく
$cd models/

$wget https://www.dropbox.com/sh/6nm8g8z163omb9f/AAAOsp9WzSQrITY9veSmOA1pa/df1.bin?dl=0 -O df1.bin

$wget https://www.dropbox.com/sh/6nm8g8z163omb9f/AACZwAeb0OOCReoeIWPRgILta/df2.bin?dl=0 -O df2.bin

$wget https://www.dropbox.com/sh/6nm8g8z163omb9f/AABrO3wRZ5hLOk70gmu3rK7Ja/LICENSE?dl=0 -O LICENSE

$wget https://www.dropbox.com/sh/6nm8g8z163omb9f/AABcYJKMOl6-Uol98boGa7n5a/playout-model.bin?dl=0 -O playout-model.bin

pipeファイル用ディレクトリ
$mkdir ~/df_pipe

動かす
$cd ~/darkforest/local_evaluator

$sh cnn_evaluator.sh 1 ~/df_pipe

(output path = /~/df_pipe
数字)で処理が止まってしまった場合は、df_pipeの中身を確認
私の場合はcnn_eval-1.logに(cunn)が見つからないといわれていたので、luarocks install cunnで入れました

$cd ~/darkforest/cnnPlayerV2

$th cnnPlayerMCTSV2.lua –pipe_path ~/df_pipe (–option)

これでGTPコマンドでの操作が可能です(公式では以下)
clear_board (盤面の初期化)
genmove b (Darkforestに黒番で次の一手を打たせる)
play w Q4 (自分が白番で指定の座標に打つ) ← genmove w Q4 をお勧めします
quit (終了)

genmove b
qiita_genmove.PNG

goguiでの遊び方
javaのインストール
$sudo apt install default-jre

goguiをインスト―ル
$wget http://downloads.sourceforge.net/project/gogui/gogui/1.4.9/gogui-1.4.9.zip

解凍
$unzip gogui-1.4.9.zip

仮想環境の場合以下から、vncserverなどで行ったほうが良いだろう
$cd gogui-1.4.9/bin/

$./gogui

プログラムの登録方法
~/darkforest でpipeを通すところまで行う

Program/NewProgram…をクリック
コマンド(Command)
th cnnPlayerMCTSV2.lua ―pipe_path ~/df_pipe -num_gpu 1 -time_limit 5
time_limitは変更可能

Workingdirectry
~/darkforest/cnnPlayerV2
パスはフルパスで入力

Program/Attachで先ほど登録したものを選択

Game/New Gameをクリックし、対局が始められる

デフォルトではComputerが白番だが、以下から
Game/Computer Colorで手番を決められる

やってみた
gogui.PNG

棋譜は載せませんが、軽く対局してみた感じですと、有段者レベル(1~3d)はあるかなぁという感じでした
悪手と守る手が多くて… ( ´∀` )

学習につきましては、試行錯誤してみましたが、どうもうまくいかないようでした

続きを読む

[Rails]本番環境のデプロイで思わぬところで躓いた

環境

Ruby 2.3.1
Rails 5.0.0
unicorn
AWS/EC2

鍵の設定やらMySQLのインストールやらでちょこちょこ躓きながらも、
ようやくアセットファイルのコンパイルにたどり着きました。

rails assets:precompileとコマンドを打つことでpublic/assets以下にファイルができるのですが、
下記の通りCSSが読み込まれないという現象が。

スクリーンショット 2017-02-12 15.27.16.png

一見するとCSSだけが読み込まれていないように見えますが、
chromeのデベロッパーツールを見るともはやHTMLから違う、、、

EC2サーバーには最新のコードをクローンしているし、vimで中身を見ても問題ない。
とりあえずCSSが効いていないと踏んで検索するとconfig.assets.compileをtrueにしたりキャッシュを消したり色々出てきました。

あれこれ試しましたが、結果は変わらず。
var/wwwからディレクトリを作り直したり、ブラウザのキャッシュを消したりしてもダメ。

しかし結果的にはgemのhaml-railsが開発環境でしかインストールされておらず、
CSSはおろかHTMLも読み込まれていないということでした。

後日検証

 gem haml-railsをしっかりと本番環境にインストールし、再度デプロイ。
案の定CSSは反映されていませんが、前日までとは違い、ちゃんとコード通りのHTMLが表示されている。

スクリーンショット 2017-02-12 17.14.41.png

私の場合は

bundle exec rake assets:precompile RAILS_ENV=production

とするだけで無事読み込まれました。

そしてついにデプロイ。

スクリーンショット 2017-02-12 19.47.52.png

アセットコンパイルが怪しい決め込んでいたことが、泥沼にはまった原因です。猛省します。

続きを読む