keisyuのブログ

最近めっきりエンジニアリングしなくなった厄年のおじさんがIT技術をリハビリするブログ。

AWSを使ってみる. ( ELB 編 )

ELB*1aws の構成の中で一般的にロードバランサ(負荷分散装置)と呼ばれるものを提供する機能です。

  • ELB配下のロジックサーバ(インスタンス)に対して適切にトラフィックを分散して流すことができる
  • 配下のサーバのヘルスチェックを行うことでサーバ障害等の発生時に自動的に障害サーバを切り離すことができる
  • AZを分散して配下のサーバを配置した場合はまずAZ単位で分散した後にAZ内のサーバに分散することが可能。AZレベルの障害も回避可能。
  • ELB自体の耐障害性を高めるためにRoute53 (DNSラウンドロビン) も併用するとさらによし。
  • VPCと併用することでユーザのアクセスするサーバをELBとしてVPC内のサーバーを隠ぺいすることが可能。

ELBの構成手順

前回構成したVPCの中にプライベートIPのみを持つ複数のHTTPDサーバに対して外部からELB経由でアクセスしてみます。

User <====> ELB ( Public Subnet ) <----> EC2 ( Private Subnet ) x2

上のような構成をしてみます。ただしこの構成を実現する場合はプライベートサブネットをパブリックサブネットと同一のAZにする必要があります.
f:id:keisyu:20140303190826j:plain:w300

設定はEC2のマネージメントコンソールの左カラムのNETWORK & SSECURITY 内の Load Balancers が ELB の設定です。
f:id:keisyu:20140303173033j:plain:w150

まずはELBの管理上の名称を決めるのと、どのプロトコルの通信を転送するかを決定します。VPC内に設置するのでそれを選択します。
f:id:keisyu:20140303192146j:plain:w300

HTTPSをサポートする場合はこの間にSSL証明書関連の設定が挟まりますが、今回はHTTPでしたので次の項目であるヘルスチェック関連の設定を行います。今回はすべてデフォルトとしときます。
f:id:keisyu:20140303192233j:plain:w300

次にサブネットを指定します。配下にするEC2の属するサブネットではなくELBの属するパブリックサブネットを指定します。ELBは外部からのアクセスに利用する場合(一般的)はパブリックなセグメントに配置する必要があります。
f:id:keisyu:20140303192352j:plain:w300

次にELBに対するセキュリティグループを定義します。HTTPを外部から接続なのでデフォルトのままでOKとします
f:id:keisyu:20140303192738j:plain:w300

ELBに割り当てるインスタンスを指定します。ここでプライベートセグメントに属するインスタンスが選択肢に現れない場合はELBが配置されたAZと異なるパブリックセグメントでプライベートサブネットを定義している可能性が高いです。
同一AZという条件に沿っていればアサインするインスタンスはプライベートなセグメントに配置してあるものでもOKになります。
f:id:keisyu:20140303193044j:plain:w300

最後確認画面が出ますのでCreateでELBの作成完了です。

挙動確認

ELBに割り当てたインスタンスhttpdをインストールして適当な /index.html を配置します。access_log を tail しているとELBのヘルスチェックアクセスがきて200応答をしていることがわかります

[root@ip-10-0-1-190 httpd]# tail -f access_log 
10.0.0.87 - - [03/Mar/2014:10:36:22 +0000] "GET /index.html HTTP/1.1" 200 85 "-" "ELB-HealthChecker/1.0"
10.0.0.87 - - [03/Mar/2014:10:36:52 +0000] "GET /index.html HTTP/1.1" 200 85 "-" "ELB-HealthChecker/1.0"
..

ELBのマネージメントコンソールからヘルスチェックステータスを確認することが可能です。
f:id:keisyu:20140303194453j:plain:w300

ELBのDNS Nameをしらべて http でブラウザでアクセスしたらアクセスできると思います。
f:id:keisyu:20140303194649j:plain:w300

リロードするとアクセス元のインスタンスが切り替わっているのも確認できます。

便利な世の中になったもんだと実感しました。

AWSを使ってみる. ( VPC 編 )

VPC

今回はAWSVPC*1を使ってみます。VPCを利用するとEC2などの仮想サーバを仮想的なプライベートネットワークで起動することが可能です。最近ではユーザがアクセスするサーバとシステムの中核となるサーバをネットワーク的に分離し、後者をプライベートネットワーク内に隠ぺいするシステム構成が多く見られます。
また、SSH等を用いてサーバにログインする際にパブリックIPが割り当てられている少数の踏み台のサーバを経由し、それ以外のサーバプライベートなネットワークでシステム管理するようなセキュリティを確保するためにも利用されます。

AWSコンソールからVPCを選択し、VPC Dashboard から 「Get started creating a VPC」を選択します。
するとどのような形態のVPCを作成するかを選択するメニューが表示されます。

f:id:keisyu:20140228122820j:plain:w300

  • VPC with a Single Public Subnet Only

これはパブリックなサブネットを1つ作成する選択肢です。パブリックなサブネット内で起動したインスタンスグローバルIPを付与することで外部のインターネットと接続することが可能です。

  • VPC with Public and Private Subnets

これはパブリックなサブネットとプライベートなサブネットを1つづつ作成する選択肢です。プライベートなサブネット内で起動したインスタンスはパブリックなネットワーク内に設置したNATインスタンスを経由してインターネットに接続します。

VPCの作成

今回は「VPC with Public and Private Subnets」を作成してみます。次の画面で構成するネットワーク等の情報が出ますので必要に応じて変更します。
f:id:keisyu:20140228125107j:plain:w300
デフォルトでは以下の構成になります。

  • 10.0.0.0/16 のネットワークでVPCを構成します。
  • 10.0.0.0/24 のサブネットでパブリックなネットワークを構成します
  • 10.0.1.0/24 のサブネットでプライベートなネットワークを構成します
  • NATインスタンス(EC2)をm1.small構成でElasticIP付きでパブリックなネットワークに設置します

作成が完了すると EC2 Dashbord で EC2がひとつ起動し ElasticIPがひとつ割り当てられています。作られた2つのサブネットを確認してみます。

  • パブリックサブネット: Routeのテーブルで一般IPの向き先はインターネットゲートウエイになっている。

f:id:keisyu:20140228134800j:plain:w300

  • ブライベートサブネット: Routeのテーブルで一般IPの向き先はNATインスタンスになっている。

f:id:keisyu:20140228134808j:plain:w300

VPC内のサブネットにインスタンスを作成

まずはパブリックサブネットにEC2を立ち上げます。Step3 の Configure Instance Detail で属するネットワークを選択します。また、このサーバを踏み台に外部からSSH接続をするのでPublic IPを割り当てます。
f:id:keisyu:20140228143913j:plain:w300
普通にSSH接続できますし、外部へのアクセスも可能です

[ec2-user@ip-10-0-0-30 ~]$ ifconfig
eth0      Link encap:Ethernet  HWaddr 06:52:CF:61:65:2D  
          inet addr:10.0.0.30  Bcast:10.0.0.255  Mask:255.255.255.0
....
[ec2-user@ip-10-0-0-30 ~]$ curl http://www.yahoo.co.jp/

次にプライベートサブネットにインスタンスを立ち上げます。ネットワークの選択でプライベート側を選択します。
IPがプライベートIPになって起動していることがわかります。
f:id:keisyu:20140228144958j:plain:w300
先ほどパブリックに起動したインスタンスを経由してプライベートなインスタンスに接続します

[ec2-user@ip-10-0-0-30 ~]$ ssh -i .ssh/***.pem ec2-user@ip-10-0-1-223.ap-northeast-1.compute.internal

SSH鍵をあらかじめパブリック側にSCPする必要がありますが問題なく接続できます。

[ec2-user@ip-10-0-1-223 ~]$ ifconfig
eth0      Link encap:Ethernet  HWaddr 0A:34:16:FB:8B:9F  
          inet addr:10.0.1.223  Bcast:10.0.1.255  Mask:255.255.255.0
...
[ec2-user@ip-10-0-0-30 ~]$ curl http://www.yahoo.co.jp/

ただし、現在の状態ではプライベートセグメントから外部へのアクセスはまだできません。NATサーバのセキュリティ設定がされておらず、全部閉鎖状態になっているからです。
http://docs.aws.amazon.com/ja_jp/AmazonVPC/latest/UserGuide/VPC_NAT_Instance.html#NATSG
を参考に今回は設定してNATインスタンスに割り当てました。これでプライベートネットワークからHTTP/HTTPSの通信は外部に可能となります。

※NATサーバといっても通常のAmazonLinuxなので踏み台のサーバと兼用するとわざわざ踏み台サーバを準備することもないともいえそう。

AWSを使ってみる. ( EC2 : EBS, AMI 編 )

前回はAWSでEC2インスタンスを単純に起動するところまででしたが今回はEBS,AMIについて調べてみます。

EBS

EC2で利用できる永続的ストレージです。これを用いるとEC2にディスクデバイスを追加することが可能です。

  • 容量の拡縮が数分で完了。
  • AZ内で自動的にレプリケート
  • 通常のボリュームの他に「プロビジョンド IOPS ボリューム」というものも選択できる。高速なIOPS。ストライピングも可能で更に超高速に。
  • S3にスナップショットを保存可能
  • スナップショットは共有可能
  • EBSを保持している間は課金発生。デバイスアクセス毎に課金発生。

作成してみます。まずはManagement Console の EBS - Volumues を選択して「Create Volume」で作成。容量等を選択してCreateします。
f:id:keisyu:20140224180306j:plain:w300
作成したEBSがリストに追加されます。
f:id:keisyu:20140224180419j:plain:w300
これをEC2にアタッチするには右クリックしてAttach Volumeを選択しアタッチしたいEC2のIDを選択します。
f:id:keisyu:20140224180426j:plain:w300
アタッチが完了するとEC2上の /dev 以下にストレージデバイスが追加されます

$ ls -l /dev/xvd*
brw-rw---- 1 root disk 202,  1  224 08:26 2014 /dev/xvda1
brw-rw---- 1 root disk 202, 80  224 08:59 2014 /dev/xvdf
$ ls -l /dev/sd*
lrwxrwxrwx 1 root root 5  224 08:26 2014 /dev/sda1 -> xvda1
lrwxrwxrwx 1 root root 4  224 08:59 2014 /dev/sdf -> xvdf

※この ln の関係の理由は EBSではデバイス名は、/dev/sd[f-p] の指定しかできず、最近のLinuxカーネルのデバイス名は、/dev/xvd[f-p]だという経緯らしい。
新規ののボリュームを作成した場合はファイルシステムを作成した後にマウントをすれば利用できます

$ sudo mkfs -t ext3 /dev/xvdf 
$ sudo mount /dev/xvdf /ebs_mount1
$ df
Filesystem     1K-blocks    Used Available Use% Mounted on
/dev/xvda1       8256952 1222752   6950316  15% /
tmpfs             304144       0    304144   0% /dev/shm
/dev/xvdf       10321208  154232   9642688   2% /ebs_mount1

再起動したらmountが外れないようにするには /etc/fstab に以下のように追記する

/dev/xvdf   /ebs_mount1 ext3    defaults        0   0

EC2をTerminateするときにはEBSボリュームは Delete on Termination の付いてないから残り続けるよと警告でます。
Detachして他のEC2を立ち上げて付け替えても当然うまく行きます。

SnapShot

EBSボリュームはスナップショットをS3にバックアップすることができます。EBSのボリュームを選択して Create Snapshotを選択する(もしくはSnapshotセクションでCreateしてEBSを選択する)だけです。スナップショットを作成しておけばEC2インスタンス作成時にそれを指定してEBSを作成することができます。Snapshotから新しいEBSを作成するので同一Snapshotのボリュームを複数のEC2が利用できることになります。
f:id:keisyu:20140224192111j:plain:w300

AMI

EBSは追加ストレージに関する機能でしたがAMIは起動ディスクに対してMachine Imageを作成することができます。EC2を起動するときに最初に選択する今までOS等を選んでいたものもパブリックなAMIです。このAMIから個人でカスタマイズしたEC2インスタンスに関する起動イメージをAMIとして作成することができます。作成されたAMIから新しいEC2インスタンスを起動することでカスタマイズした同一の構成のEC2インスタンスを簡単に作成することができます。

AMIの作成

AMI化したいEC2インスタンスを構築しStopで停止します。EC2のマネージメント小ソールから該当のインスタンスを選択して「Create Image」 を選択します。
f:id:keisyu:20140224222820j:plain:w300
AMIsの項目に作成したAMIがリストされます。しばらくするとステータスがavailableになって利用可能になります。
f:id:keisyu:20140225012643j:plain:w300
これで、EC2のインスタンス作成時にMyAMIsから選択でき自分で作った構成のマシンが起動可能になります。
f:id:keisyu:20140225012828j:plain:w300

AWSを使ってみる. ( EC2 編 )

AWS*1 を利用するとインターネットシステムを構成するさまざまな物理的リソースを手軽に入手することができます。昔はレンタルサーバを借りて...とかしているとそんなに贅沢なリソースは確保できなしロードバランサーとかネットワークストレージとかDNSとかは個人レベルで構築するのって大変でしたが、そんな悩みをAWSは全て解決してくれます。個人でも(月額利用料さえはらえば)大規模なウエブサービスのシステムを簡単に構築できるなんて便利な世の中ですね。

EC2

今回は初めてということでAWSの基本となるサービスである仮想サーバ EC2 *2 を使ってみようと思います。AWSのアカウントを作成して事前準備(Paymentの設定など)をしてAWS Management Console を開きます。こんな画面です。

f:id:keisyu:20140223100138j:plain:w300

EC2を選択します。AWSはWorld-Wideにデータセンタがあり仮想サーバを使用する際にどのリージョン(データセンターのロケーション)を利用するかを選択します。今回はTokyoを選びます。(AWSはリージョンおよびインスタンスタイプ(仮想サーバのスペック)によって時間単位の利用料金が異なりますので注意)
f:id:keisyu:20140223100704j:plain:w200

「Launch Instance」というボタンをクリックして仮想サーバを取得してみます。

  • Step 1: Choose an Amazon Machine Image (AMI)

という画面がまず出ます。あなたの欲しいサーバ(Machine Image)は何ですか(Linuxですか?Windowsですか?64/32bitOSどちら?とか聞かれます)。AWSが提供する初期構成のものがデフォルトの選択肢で表示されますが、それ以外にもすでに特定のパッケージ(ソフトウエア)インストール済みのイメージも選択できます(物によっては追加時間費用がかかります)。また、自分であらかじめ作成したイメージや他の人が作成して公開したイメージとかも選択できます。自分で作成したイメージを選択するとサーバ増強などの場合に同じ構成のインスタンスを速攻で作成できます。

今回はAWSが提供するAmazon Linux 64bit を選択します (Amazon Linux はほぼ CentOS と同等+AWS Toolsのようです)
f:id:keisyu:20140223102622j:plain:w300

  • Step 2: Choose an Instance Type

次に仮想サーバのマシンスペックについて選択します。とりあえずお試しの超低スペックなものから化け物級の超高性能のマシンまであります。当然時間単位の利用料金が異なりますのでそれを踏まえて選択します。選択の表でスペックが確認できますので用途に応じて(かつ値段に応じて)選択します。今回は最低スペックの t1.micro を選択します。
f:id:keisyu:20140223103422j:plain:w300

  • Step 3: Configure Instance Details

Step 2:の段階で起動をすることもできますが、Detailの設定をすることもできます。ここでは Network (あらかじめ構築してあるプライベートネットワーク(VPCに属するように立ち上げるか) や、AZ(Availability Zone: リージョン内でも複数の物理的に離れたデータセンターゾーンがある。同一リージョン内でも対障害性を高めるために分散したりする)を選択できたりします。今回は項目を確認するだけで全てデフォルトで次に行きます。

  • Step 4: Add Storage

ルートディスクサイズや拡張ストレージのボリュームを追加できます。これも今回は触らないで次に行きます。

  • Step 5: Tag Instance

仮想サーバに対してTagがKey/Valueが追加できます。AWSツールなどでこの仮想サーバを特定したり,サーバを用途ごとにグルーピングして管理しやすいような名称をつけれます。用途は各自。

  • Step 6: Configure Security Group

セキュリティポリシーを選択できます。このサーバに対して外部からの接続Port(SSH,HTTP,etc)の開放状況を指定します。
SSHがデフォルトでAnyになっているので MyIPを選択します(そうすると自動で自分が接続している外部IPを調べてセットしてくれます).
HTTP/80をAnyで追加してみます。
f:id:keisyu:20140223105329j:plain:w500

  • Step 7: Review Instance Launch

最後に構成の確認をして「Launch」をクリックすれば仮想サーバが起動します。が、最初の起動直前にKeyPair の選択をします。これはSSH等で接続するための公開鍵認証のセキュリティキーを指定してください。ということです、既存に作成済みのKeyPairを選択するか新しいKeyPairを作成します。新規作成した場合は秘密鍵(*.pemファイル)もここでダウンロードできますのでココで保存しときます(再ダウンロードできないので注意)。

LaunchするとManagement Console で戻ってきたら立ち上げたインスタンスが表示されます。Selectすると下部のパネルに情報が表示されます。こんな感じに。
f:id:keisyu:20140223140614j:plain:w300

注意すべきは立ち上がった状態はもちろんのこと Stopした状態でも課金は発生します。使い終わったら(不要になったら)Terminateの状態にしましょう。右グリックでActionパネル開いてTerminateを選択します。
f:id:keisyu:20140223140832j:plain
ただし、Terminateとは仮想サーバ上のデータも含めて全て消去(捨てる)して利用を終了するという意味なのでその辺も注意。

SSHでログイン

ログインするにはコンソールからSSH接続します

$ ssh -i xxxxxxx.pem ec2-user@ec2-54-238-115-217.ap-northeast-1.compute.amazonaws.com                                                                                                                               
The authenticity of host 'ec2-54-238-115-217.ap-northeast-1.compute.amazonaws.com (54.238.115.217)' can't be established.
RSA key fingerprint is 6d:07:4a:aa:30:e7:71:35:88:18:d4:42:41:7e:34:f8.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added 'ec2-54-238-115-217.ap-northeast-1.compute.amazonaws.com,54.238.115.217' (RSA) to the list of known hosts.

       __|  __|_  )
       _|  (     /   Amazon Linux AMI
      ___|\___|___|

https://aws.amazon.com/amazon-linux-ami/2013.09-release-notes/
9 package(s) needed for security, out of 30 available
Run "sudo yum update" to apply all updates.
[ec2-user@ip-10-134-152-93 ~]$ 
  • 起動時に指定したSSH秘密鍵を指定(パーミッションを400にしておく)
  • 接続ユーザは ec2-user
  • 接続先はManagement Consoleで表示された Public DNS のホスト名

つながりました。 ec2-user ってなんか嫌だな。セキュリティ的にもこのまま使うのもイマイチなのでこれを変える場合は

# useradd keisyu
# password keisyu
# vi /etc/sudoers  # sudo 設定
# mkdir /home/keisyu/.ssh
# cp /home/ec2-user/.ssh/authorized_keys
# chown -R keisyu:keisyu /home/keisyu/.ssh

これで自分のアカウントでログインできますし ec2-userは userdel ec2-user で消せます。とりあえずログイン時に言われたとおり

$ sudo yum update

するとよいですね。

ec2へHTTPアクセス

せっかくなのでapacheでもインストールして公開設定したHTTP/80を外部から確認できるかやってみます。apacheをインストールします

$ sudo yum install httpd
$ sudo /sbin/service httpd start

これでapacheが立ち上がりました。

# cat /var/www/html/index.html 
<html><body>
Hello! AWS World! by keisyu
</body></html>

簡単にhtmlを作成してブラウザでアクセスしたら閲覧できました
f:id:keisyu:20140223144317j:plain:w300

インスタンスの停止

  • マネージメントコンソールで仮想サーバを選択してStop
  • shutdown -h now

前にも書きましたがこのまま放置は課金されます。必要なければ Terminate しましょう。再び起動するなら右クリックでStartです。
再スタートの場合 Public DNS, IPは前回の起動と異なる場合があります(というか大抵は異なる)。

nginxとリバースプロキシでモダンなシステム構成

nginx

今日は今どき?(といっても世間では既に主流な構成な感はありますが)nginx&リバースプロキシを用いたシステム構成を実験で作ってみたいと思います。
nginx はイベント駆動型のウエブーサーバでありApacheでいうところのMPM Prefork/workerとは異なる構成なのでメモリ使用量が少なく動作することと一つの処理が低コストな場合において同時接続数が多くても安定して動作することが期待できるウエブサーバです

Apacheをフロントに設置してシステムを構成する場合、一昔前はなどと3層のシステム構成(もしくはAPIを外した2層)にすることが主流でした。A

User <====> Apache <----> API <----> DB

C10K問題*1に直面するケースは少ないにしろ比較的大規模なサービスにおいて同時接続をサポートするとフロント(ユーザがアクセスするサーバ)を並列に並べる必要がでてきます.この問題を解決するためにフロントエンドにnginxを配置してユーザの接続を受け付け処理はバックエンドのサーバに流す(リバースプロキシ)の構成が流行っているようです。

User <====> nginx <----> WebServer <----> DB
  • 静的コンテンツはnginx 側で配信
  • 動的なコンテンツはリバースプロキシでバックエンドに配置されたウエブサーバに振り分ける
  • nginx自体がロードバランサの役目も持っている

インストール

epel のリポジトリに少々古いですが(1.0.15-5.el6) パッケージがあるようなので yum でインストールしてみます。

$ sudo yum install nginx
$ /usr/sbin/nginx -V
nginx version: nginx/1.0.15
built by gcc 4.4.7 20120313 (Red Hat 4.4.7-3) (GCC) 
TLS SNI support enabled
configure arguments: --prefix=/usr/share/nginx --sbin-path=...

nginxはapacheのDSOのような動的なモジュールの組み込みができず、コンパイルやり直しが必要らしいです。今の時点でのモジュールの組み込み具合は -V のオプションを付けて起動すると確認できます。とりあえずは何も考えずに起動 ( $ sudo /sbin/services nginx start )すると以下のような画面が立ち上がります。
f:id:keisyu:20140221143438j:plain:w300
詳しく見ていきます。設定ファイルは /etc/nginx/nginx.conf と、そこからincludeされる /etc/nginx/conf.d/*.confが主な設定ファイルです

$ ls  /etc/nginx/conf.d/
default.conf  ssl.conf  virtual.conf

記載されている nginx.conf で設定のフォーマットとしては以下のように記述するようです。

coreモジュールの設定
events {
    eventモジュールの設定
}
http {
    httpモジュールの設定
}
server {
    virtual serverの設定 (conf.d/default.conf or virtial.conf)
}

主要な設定

worker_processes core nginxのworkerプロセスの数, CPUの個数と同じにするのが効果的(それ以上にしても意味がない)
worker_connections event 一つのworkerプロセスが同時に処理できる最大コネクション数を設定

リバースプロキシの設定

せっかくなのでリバースプロキシの設定をやってみます。nginxを起動したサーバがゲストOSのCentOSでありバックエンドに相当するウエブサーバを
Amon2を使ったアプリ開発 (その2) - keisyuのブログ でつくったシンプルな掲示板と想定しますすなわち以下のようなややこしい感じです。

User(ホストPC) <----> nginx (CentOS:VirtualBox) <---> Server (ホストPC)

以下のようにconf.d/default.conf を編集します

upstream bbs_server {
    ip_hash;
    server 192.168.56.1:5000;
}
server {
    ...
    ...
    location / {
        proxy_pass http://bbs_server/;
    }
    ...

これで一応はリバースプロキシとして動作します。nginxのPort80 "/" にアクセスすると 192.168.56.1:5000 で起動する plackup されたWebサーバにアクアクセスしてコンテンツが応答されます。その他リバースプロキシの設定はたくさんあるので必要に応じて調べてみる(X-Forwarded-For とか)

最新版の利用

最新の安定版は1.4.5らしいです。これを利用するために本家からダウンロードしてコンパイルしてみます。configure で epel でインストールした版の -V で確認したオプションと同じものを定義してみることにします。

$ sudo rpm -e nginx  # yum で入れたのを消しとく
$ curl -LO http://nginx.org/download/nginx-1.4.5.tar.gz
$ tar zxvf nginx-1.4.5.tar.gz
$ cd nginx-1.4.5
$ ./configure --prefix=/usr/share/nginx ... #(epel版の -V で出てきたのをそのまま使う)
$ sudo make install

configure で色々モジュールが足りないと言われて結局いかのものをyumでインストールした

pcre-devel, openssl-devel, libxslt-devel, gd-devel, perl-ExtUtils-Embed, GeoIP-devel

インストール完了すると

$ /usr/sbin/nginx -V
nginx version: nginx/1.4.5
built by gcc 4.4.7 20120313 (Red Hat 4.4.7-4) (GCC) 
TLS SNI support enabled
configure arguments: --prefix=/usr/share/nginx --sbin-path=...

最新安定バージョンで動作します。/etc/init.d/nginx がrpm -e で消えて install で生成されないけどググれば見つかる。
f:id:keisyu:20140221162823j:plain:w300

Elasticsearch 1.0 を使ってみた2。( perl からの利用と日本語全文検索 )

前回 Elasticsearch を利用してみて (Elasticsearch 1.0 を使ってみた。 - keisyuのブログ)検索に関する仕事を5年以上やってた経験があるだけに非常に便利な世の中になったもんだと感動しました。今回はPerlから利用して見る方法と日本語全文検索はどんな感じかを試してみたいと思います。

elasticsearch.pm

Elasticsearchのリファレンスに記載の通り (Elasticsearch.pm) cpanのモジュールがすでにあるようです。

$ cat cpanfile
requires 'Elasticsearch';
$ carton install
...
Successfully installed Elasticsearch-1.03
59 distributions installed

とりあえずリファレンス通りにやってみる

use strict;
use warnings;
use utf8;
use Elasticsearch;
use Data::Dumper;

my $e = Elasticsearch->new(
    nodes => '192.168.56.101:9200',
);

print Dumper $e->index (
    index   => 'myindex',
    type    => 'mytype',
    id      => 'doc3',
    body    => {
        'name' => 'Suzuki Ichiro',
        'comment' => 'this document was inserted by perl module!'
    }
);

print Dumper $e->get(
    index   => 'myindex',
    type    => 'mytype',
    id      => 'doc1'
);

print Dumper $e->search(
    index   => 'myindex',
    body    => {
        query => {
            match => { 'comment' => 'perl' }
        }
    }
);

実行すると期待通りの結果が応答されます。

$ carton exec -- perl es.pl
$VAR1 = {
          '_version' => 4,
          '_id' => 'doc3',
          '_type' => 'mytype',
          '_index' => 'myindex',
          'created' => bless( do{\(my $o = 0)}, 'JSON::PP::Boolean' )
        };
$VAR1 = {
          '_version' => 7,
          '_id' => 'doc1',
          '_source' => {
                         'comment' => 'Hello! Elasticsearch World!',
                         'age' => 40,
                         'name' => 'Yamada Taro'
                       },
          '_type' => 'mytype',
          '_index' => 'myindex',
          'found' => bless( do{\(my $o = 1)}, 'JSON::PP::Boolean' )
        };
$VAR1 = {
          'hits' => {
                      'total' => 1,
                      'hits' => [
                                  {
                                    '_source' => {
                                                   'comment' => 'this document was inserted by perl module!',
                                                   'name' => 'Suzuki Ichiro'
                                                 },
                                    '_id' => 'doc3',
                                    '_type' => 'mytype',
                                    '_score' => '0.11506981',
                                    '_index' => 'myindex'
                                  }
                                ],
                      'max_score' => '0.11506981'
                    },
          'timed_out' => bless( do{\(my $o = 0)}, 'JSON::PP::Boolean' ),
          '_shards' => {
                         'total' => 5,
                         'failed' => 0,
                         'successful' => 5
                       },
          'took' => 4
        };

日本語全文検索

それではとりあえず何も考えずに上のサンプルで日本語のドキュメントを投入して日本語検索してみましたがうまくいきませんでした。
このindexを定義するときにmappingを指定せず登録されたデータから自動的に生成しましたので日本語の形態素解析をするanalyzerとして定義がフィールドにされなかったからだと思います。これを明示的に指定してindexを生成して日本語で検索ができるようにしてみます。

今回は comment という要素に対して NGram のインデックスをmappingにて定義してみます。
Elasticsearchが標準で搭載するNgramのアナライザに関してはこの辺が参考になります。NGram Tokenizer

以下の要領でindexを再定義します。

use strict;
use warnings;
use utf8;
use Elasticsearch;
use Data::Dumper;

my $e = Elasticsearch->new(
    nodes => '192.168.56.101:9200',
);

print Dumper $e->indices->delete(
    index => 'myindex',

);

print Dumper $e->indices->create(
    index => 'myindex',
    body => {
        settings => {
            analysis => {
                analyzer => {
                    my_ngram_analyzer => {
                        tokenizer => 'my_ngram_tokenizer'
                    }
                },
                tokenizer => {
                    my_ngram_tokenizer => {
                        type => 'nGram',
                        min_gram => 2,
                        max_gram => 3,
                        token_chars => [ "letter", "digit" ]
                    }
                }
            }
        },
        mappings => {
            mytype => {
                properties => {
                    name    => { type => 'string'},
                    comment => { type => 'string', analyzer => 'my_ngram_analyzer'},
                    age     => { type => 'integer'}
                }
            }
        }
    }
);

データを投入して検索してみると正しく結果が応答されるはずです

print Dumper $e->index (
    index   => 'myindex',
    type    => 'mytype',
    id      => 'doc4',
    body    => {
        'name' => 'Takahashi Daisuke',
        'comment' => '日本語の検索が正しく動作するかの確認です。'
    }
);
# 投入した直後はヒットしない可能性があるので注意。
print Dumper $e->search(
    index   => 'myindex',
    body    => {
        query => {
            match => { 'comment' => '京都' }
        }
    }
);  # ヒットしない
print Dumper $e->search(
    index   => 'myindex',
    body    => {
        query => {
            match => { 'comment' => '検索' }
        }
    }
);   # ヒットする

HQの管理ツールからも検索が直接試せるのでここからでも確認できます。すごい便利!
f:id:keisyu:20140220005315j:plain:w300

Ngram ではなく 形態素解析にて検索したい場合はこの辺を参考にする。

以下の日本語形態素解析のpluginをインストールします。
elasticsearch/elasticsearch-analysis-kuromoji · GitHub

$ cd /usr/share/elasticsearch
$ sudo bin/plugin -install elasticsearch/elasticsearch-analysis-kuromoji/2.0.0.RC1
$ sudo /sbin/service elasticsearch restart  # /_nodes/plugin?pretty にアクセスしてpluginインストール確認

疲れたのでまた今度にする。

Elasticsearch 1.0 を使ってみた。

Elasticsearch (Elasticsearch.org Open Source Distributed Real Time Search & Analytics | Elasticsearch) がVer1.0となったらしいので使ってみました。
オープンソースのSearch Systemといえば solr (Apache Lucene - Apache Solr)かこれの2択みたいですが、最近はElasticsearchの記事が目立つような気がする。
1.0になったといってもそれ以前のバージョンも使ったことなかったのではじめて使ってみる状態。まあやってみます。

インストール

CentOSにインストールします。ここ (Elasticsearch.org Download ELK | Elasticsearch)からRPM版をダウンロードしてインストールします。
serviceに登録しましょう、と新設に教えてくれるのでそのとおりに登録します。

$ curl -LO https://download.elasticsearch.org/elasticsearch/elasticsearch/elasticsearch-1.0.0.noarch.rpm
$ sudo rpm -ihv elasticsearch-1.0.0.noarch.rpm 
$ sudo /sbin/chkconfig --add elasticsearch

JAVAが必要なのでそれもインストールします。JREをインストールしJAVA_HOMEの環境変数をセットします。
Java SE Runtime Environment 7 - Downloads | Oracle Technology Network | Oracle

$ sudo rpm -ihv jre-7u51-linux-x64.rpm 
$ export JAVA_HOME=/usr/java/jre1.7.0_51

動作確認

起動は簡単

$ sudo /sbin/service elasticsearch start
Starting elasticsearch:                                    [  OK  ]

特に設定を変更せずデフォルトでプロセスを確認するとこんな感じです

/usr/bin/java -Xms256m -Xmx1g -Xss256k -Djava.awt.headless=true -XX:+UseParNewGC -XX:+UseConcMarkSweepGC 
-XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly -XX:+HeapDumpOnOutOfMemoryError 
-Delasticsearch -Des.pidfile=/var/run/elasticsearch/elasticsearch.pid -Des.path.home=/usr/share/elasticsearch 
-cp :/usr/share/elasticsearch/lib/elasticsearch-1.0.0.jar:/usr/share/elasticsearch/lib/*:/usr/share/elasticsearch/lib/sigar/* 
-Des.default.path.home=/usr/share/elasticsearch -Des.default.path.logs=/var/log/elasticsearch 
-Des.default.path.data=/var/lib/elasticsearch -Des.default.path.work=/tmp/elasticsearch 
-Des.default.path.conf=/etc/elasticsearch org.elasticsearch.bootstrap.Elasticsearch

ドキュメントを見ても用語が混乱するので整理。

cluster 一つの検索のシステム全体を示す
node 複数のサーバ(Elasticsearchのプロセス)で分散して検索システムを構成することができる。その際の各々を指すもの。定義されているcluster名が同一ネットワーク内にあればそのclusterに自動で組み込まれる

この辺の応答のJSONをみてみる。

$ curl -XGET 'http://localhost:9200/_cluster/state'
$ curl -XGET 'http://localhost:9200/_cluster/stats'
$ curl -XGET 'http://localhost:9200/_cluster/health'
$ curl -XGET 'http://localhost:9200/_nodes'
$ curl -XGET 'http://localhost:9200/_nodes/stats'

が、そんなことはせずともElasticsearchのモニタリングをする便利なプラグインがあるようです。

$ cd /usr/share/elasticsearch
$ sudo ./bin/plugin --install royrusso/elasticsearch-HQ

これだけで http://localhost:9200/_plugin/HQ にアクセスすると以下のようなカッコイイ&多機能を予感させる画面が出てきた!!
f:id:keisyu:20140218232912j:plain:w300

なんか入れて検索してみる

とりあえずここでも概念的に頭を整理しないと混乱する

index 一つの検索システムで複数のtypeを構成できる
type 検索対象の郡(集合)を表す
mapping ドキュメントの各要素(検索Key)の属性等の定義
document 個々の検索データ

データの投入(index, typeは特に事前定義しなくとも勝手に作成されるのがデフォルトの挙動らしい), mapping も document のから自動的に識別されるらしい(頭がいい..が間違ってたり矯正したい場合は定義ファイルに書くみたい)

データの投入

PUTにて /[index_name]/[type_name]/[document_id] で JSON 形式でドキュメントを送信する.

$ curl -XPUT 'http://localhost:9200/myindex/mytype/doc1' -d '{ "name" : "Yamada Taro", "comment": "Hello! Elasticsearch World!"}'
{"_index":"myindex","_type":"mytype","_id":"doc1","_version":1,"created":true}
$ curl -XPUT 'http://localhost:9200/myindex/mytype/doc2' -d '{ "name" : "Tanaka Hanako", "comment": "Today is Febyrary 19. I like World Trip!"}'
{"_index":"myindex","_type":"mytype","_id":"doc2","_version":1,"created":true}
$ curl -XPUT 'http://localhost:9200/myindex/mytype/doc1' -d '{ "name" : "Yamada Taro", "comment": "Hello! Elasticsearch World!", "age":40}'
{"_index":"myindex","_type":"mytype","_id":"doc1","_version":4,"created":false}

データを更新したい場合は同じdocument_idで上書きする感じ (レスポンスの createdがfalseになって_versionが加算される)

データの取得(個別ドキュメント)

$ curl -XGET 'http://localhost:9200/myindex/mytype/doc1'
{
  "_index":"myindex",
  "_type":"mytype",
  "_id":"doc1",
  "_version":4,
  "found":true,
  "_source" : { 
     "name" : "Yamada Taro", 
     "comment": "Hello! Elasticsearch World!", 
     "age":40
  }
}

検索する

ドキュメント単位, タイプ単位で検索できるようです

$ curl -XGET 'http://localhost:9200/myindex/mytype/_search?q=Hello'   # 1件ヒット
{
  "took":3,
  "timed_out":false,
  "_shards":{
    "total":5,
    "successful":5,
    "failed":0
  },
  "hits":{
    "total":1,
    "max_score":0.13424811,
    "hits":[
      {
        "_index":"myindex","_type":"mytype","_id":"doc1","_score":0.13424811, 
        "_source" : { 
          "name" : "Yamada Taro", "comment": "Hello! Elasticsearch World!", "age": 40
        }
      }
    ]
  }
}

$ curl -XGET 'http://localhost:9200/myindex/mytype/_search?q=world'    # 2件ヒット
{
  "took":3,
  "timed_out":false,
  "_shards":{
    "total":5,
    "successful":5,
    "failed":0
  },
  "hits":{
    "total":2,
    "max_score":0.13424811,
    "hits":[
      {
        "_index":"myindex","_type":"mytype","_id":"doc1","_score":0.13424811,
        "_source" : { 
          "name" : "Yamada Taro", "comment": "Hello! Elasticsearch World!", "age": 40
        }
      },{
        "_index":"myindex","_type":"mytype","_id":"doc2","_score":0.095891505,
        "_source" : { 
          "name" : "Tanaka Hanako", "comment": "Today is Febyrary 19. I like World Trip!"
        }
      }
    ]
  }
}

この状態でHQ確認すると確かにmyindexというインデックスが定義されています
f:id:keisyu:20140219002609j:plain:w300
検索の確認もHQ内でできる。すごく使いやすい。早くも神ツールの予感。
f:id:keisyu:20140219002708j:plain:w300


その他の詳しいことはリファレンスを読んでみることにする。
Elasticsearch - Reference [1.x]

今日はここまで。