Elasticsearch の概要を調べてみた

今日から Elasticsearch について調べたことのメモ書きとして、当ブログにまとめていきます。

Elasticsearch とは

Elasticsearch は全文検索エンジンであり、多種多様なクエリ、集計、スケーラブル、高可用性、ユーザーフレンドリなAPI、拡張性を特徴としています。競合ツールとしては Apache Solr があげられると思います。

多種多様なクエリ

全文検索エンジンと聞いて、構文解析形態素解析を思い浮かべる方も多いかと思います。Elasticsearch でももちろん構文解析形態素解析ができます。ただそれだけではありません。

単純なところでは数値や日時の比較ができます。RDBMS でもできるので、Elasticsearch の特徴にはなりませんが、例えば、20歳以上、30歳未満といった客層データの検索、2016/1/1以降、1/31以前といった一定期間の売り上げデータの検索が可能です。

珍しいところで言えば、地理情報です。緯度経度情報を扱うことで、ある地点から、ある地点への距離を検索(算出)することができます。例えば、複数のお店の地理情報を入れておき、現在地から近い順にソートして検索することができます。

集計

Aggregation という機能でデータを集計することができます。例えば、年齢が20台、30台などといった一定のスパンでの購入件数、合計購入金額を検索でき、それにより客層の購入分析を行うことができます。例えば、1日の1時間毎の購入件数、合計購入金額を出すことでどの時間帯の売り上げが良いのか知ることができます。

スケーラブル

Elasticsearch は複数ノードでクラスタを組むことができ、データが入ってからもノードの追加・削除が可能です。運用が始まってからもパフォーマンスが不足しているようであれば、ノードを足すことができますし、パフォーマンスが余剰であれば、ノードを減らすことができます。

高可用性

クラスタで得られる恩恵はパフォーマンスの調整だけではありません。ノードでデータのレプリカを持つことで、可用性を高めることができます。全てのデータのレプリカを異なるノードに配置することで、一つのノードが障害でダウンしても、サービスの損失を防ぐことができます。データ量は倍となりますが、本番環境ではレプリカを持たせることは必須でしょう。

ユーザーフレンドリなAPI

Elasticsearch は RESTfull な API を提供しています。HTTP/HTTPS リクエストが送信できる環境であれば、誰でも利用することができます。RESTfull API だけでなく、各種言語による SDK も提供していますので、プログラムに組み込む時は SDK を利用することでエラー処理や、共通処理を実装することなく利用することができます。

拡張性

Elasticsearch はプラグインを提供しています。Elastic 社が提供するプラグインから、コミュニティ、個人が提供するプラグインなど色々あります。

Elasticsearch 関連プロダクト

Elastic社は Elasticsearch だけでなく、関連プロダクトを提供しています。Elasticsearch 5.0 から Elasticsearch を含む主要 4つのプロダクトを Elastic Stack として提供しています。4つとも OSS で Elastic社が持つ Github リポジトリで管理されています。

  • Elasticsearch
  • Kibana
  • Logstash
  • Beats

Kibana

Kibana は Elasticsearch に入っているデータを可視化するツールです。円グラフ、線グラフ、棒グラフ、地図など各種グラフにて Elasticsearch の検索、集計を利用して可視化することができます。また各種グラフを一つの画面に並べることでダッシュボードを作成することも可能です。例えば、ECサイトマーケティング担当向けにダッシュボードを作成するとしたら、アクセス数、購入金額、商品別の購入数、時間帯別の購入数と商品品目、時間帯別の購入数と購入年齢層・性別などをいい感じに可視化することができます。

また X-Pack というプラグインなどと組み合わせることで、Elasticsearchクラスタの監視、Elasticsearchへのアクセス制御なども可能となっています。

Logstash

Logstash は Elasticsearch にデータを入れる仕組みを提供するツールです。Elasticsearch は RESTfull APISDK などを利用してデータを入れることができますが定期的に入れる場合、スクリプト、プログラムの実装が必要となります。Logstash は入れたいデータの元、データを入れるElasticsearchの情報を設定ファイルに指定するだけで Elasticsearch にデータを入れることができます。例えば、Apacheaccess_log を Elasticsearch に入れる場合、ファイルプラグインaccess_log の差分を定期的に取得し、Grokプラグインaccess_log のメッセージを解析・変換し、Elasticsearch にデータを入れてくれます。それを Kibana で可視化することでアクセス解析、パフォーマンス解析することが可能です。

Logstash はプラグインの組み合わせで処理パイプラインを形成します。プラグインは大きく、インプット、フィルター、アウトプットに分けられます。インプットは取得するデータソースを表し、ファイル、JDBC、標準入力、AWS S3など多数あります。フィルターはインプットで取得したデータの絞り込み、変換を表し、文字列→日付変換、文字列をデータパターンから構造化データへの変換など多数あります。アウトプットはデータの出力先を表し、Elasticsearch だけではなく、ファイル、JDBC、標準出力など多数あります。

Beats

Beats は役割は Logstash と同じく Elasticsearch にデータを入れる仕組みを提供するツールです。Logstash との違いは、機能が限定されているが、軽量・高パフォーマンスだという点です。Logstash のような多種多様なプラグインはなく、データ取得元毎にプロダクトが別れています。データ出力先も Elasticsearch、Logstash、Apache Kafkaなどと限定的です。

現在提供されている Beats プロダクトは以下となります。

  • Packetbeat
  • Metricbeat
  • Filebeat
  • Winlogbeat

まとめ

Elasticsearch は単純な検索エンジンだけではなく、関連プロダクトと組み合わせることでユースケースが非常に広がりそうだと思っています。

次回は Elasticsearch クラスタのインストール、立ち上げ方を解説します。

AWS CloudFormation

AWSにサインアップした時、既にVPC(デフォルトVPC)やサブネット(デフォルトサブネット)が用意されています。
私はVPCから用意したかったのでデフォルトは削除し、VPC作成から行いました。
ただデフォルトVPCは唯一の存在であり、
自分で作成したVPCをデフォルトVPCには設定できません。
削除したデフォルトVPCを復元したい場合、
AWSサポートに問い合わせる必要があります。
昨日、AWSサポート(BASICサポートで対応いただけます)で復元を依頼したところ、
わずか、15分で対応いただけました。
過去、色々な有償プロダクトのサポート問い合わせを行ってきましたが、
無償サポートでこの対応の早さは素晴らしいです。

さて、今回はCloudFormationです。
私は単調作業が嫌いです。
単調作業があればどうすれば自動化できるかを考えます。

AWSはそんな私にピッタリな自動化のソリューションをいくつか用意されています。

まだ違いがよくわかっていませんが、以下のようなソリューションがあります。

  • CloudFormation
  • Elastic Beanstalk
  • OpsWorks
  • CodeDeploy

CloudFormationはAWSリソースをTemplateというJSON形式で表すことにより、
AWS ManagementConsoleからポチポチするクリック動作を自動で行ってくれる素晴らしい機能です。

それ以外のソリューションはまた別の機会で。

まずはもっとも簡単な例としてEC2インスタンスを一つ作成します。

サンプル
前提条件

  AMI IDはLaunch InstanceのAMI選択画面からコミュニティAMI画面を開き、
  AMIの名前の右にami-xxxxxxxx形式で記載されています。

  • キーペアにtest1を作成済み
  • サブネットを作成済み(ID指定)
  • セキュリティグループを作成済み(ID指定)
{
  "Resources": {
    "Ec2Instance": {
      "Type": "AWS::EC2::Instance",
      "Properties": {
        "KeyName": "test1",
        "ImageId": "ami-cbf90ecb",
        "InstanceType": "t2.micro",
        "NetworkInterfaces": [
          {
            "AssociatePublicIpAddress": "true",
            "SubnetId": "subnet-2d359d5a",
            "DeviceIndex": "0",
            "GroupSet": [
              "sg-5fae323a"
            ]
          }
        ]
      }
    }
  }
}

このテンプレートからスタックを作成すると、
EC2インスタンスが一つ作成されます。

一度、EC2を作成したことがある方はGUIで指定した覚えがある項目ばかりですよね。
そういうことです。
GUIの設定項目をテキストで外出ししておくだけです。

でも、それだけだとありがたみないは少ないですよね。
それどころか手入力なので逆に時間かかるだけですよね。
CloudFormationが生きるところは、

  • リソースの連携
  • リージョン間の複製
  • 特にBlue-Green Deployment

にあるように感じました。

リソースの連携

サンプルでは一つのリソースしか作成していませんが、
例えば前提条件が何もなかったとします。
その場合、EC2インスタンスを作成する前に
サブネット、キーペア、セキュリティグループを作成する必要があります。
これらもCloudFormationのリソースで作成することが可能です。
Resourcesの中にそれぞれ指定することで作成でき、
また他のリソースから参照指定することでIDを記載せずとも紐づけることが可能です。
GUIで作成ウィザードの中で表示されるプルダウンに表示されるのはIDだけで選択を誤ったことがあるのは私だけではないはず。

リージョン間の複製

複数リージョンに同一構成を作成するケースがあります。
そういうケースではテンプレートを使いまわすことで同一構成が作成できます。
勘がよい方はAMI IDが異なることを気になったはず。
そういう違いを吸収する仕組みがCloudFormationではJSON形式で記載できる組み込み関数を用意してくれています。
例えば、AMI IDのケースではMappings定義とFn::FindInMap関数を組み合わせることで、
リージョンに応じたAMI IDの紐付けを行うことができます。

Blue-Green Deployment

DevOpsで話題のリリース方式です。詳細はググってください。
Blue-Green Deploymentは同一構成をリリースの度に構築するため、
一つテンプレートを作っておけば、
それを元にスタックを作成するだけで同一構成を用意できます。
またスタックを削除するとテンプレートによって作成されたリソースが合わせて削除されます。
削除忘れにより無駄なコスト削減も実現できます。

いかがでしょうか。

CloudFormation使いたくなってきましたよね。
今回の私のサンプルは使い物にならないものですが、
AWSが利用用途に応じたサンプルをいくつか用意してくれています。
そちらを参考にすれば学習コスト少なく、扱うことができます。

ただいくつか不満があります。

  • デバッグができない
    • 予約語や変数の結果からどういった値が得られるのか分からない
    • 事前チェックがなく、処理が依存関係に応じて順に行われるので、後半にエラーが発生するため息が出る

この辺は経験で吸収するしかないのでしょうか。。
とりあえず色々と触ってみます。

AWS S3

今回はS3。
AWS複数あるストレージサービスの一つです。
S3はファイルをアップロード、ダウンロードできるWebサービスです。
Webサーバとしても動作します。
特徴は、
容量制限がなく(5GB/file)、
非常に高い耐久性(99.999999999%)、
低価格です。

AWSは他に以下の様なストレージサービスを提供しています。

EBSはEC2のディスクとしてアタッチするサービス。
Glacierはアーカイブストレージサービス。
Storage Gatewayはオンプレミスのストレージに接続するサービス。
EFSはNASサービス。

といったようにそれぞれ機能が違うので目的に応じたサービスをチョイスしましょう。

EC2でWebAP構築
 ↓
RDSでWebAP-DB構築
 ↓
AMIでEC2のイメージ化、複製
 ↓
ELBで冗長化
 ↓
ElastiCacheでセッション管理
 ↓

S3にアクセスログ保存 (今回はここ)

 ↓
(できれば)SQSでWebAP連携

S3は汎用的なストレージサービスであるため、本当に様々な用途があります。
今回はログの集約用途に利用します。
Webサーバやアプリケーションサーバ冗長化するとその分ログが各OSのファイルシステムに分散されてしまい、いざログを調査したい時に手間です。
またトラブル対応の調査する時にこのメッセージって過去にもあるんだろうか、など過去のログから比較をしたいけどそこまで古いログないや、ってことも多々あります。

ログの置き場所や保持世代数って運用設計でよく悩みます。

そんな時はS3に飛ばすことで、ログデータの集約、蓄積を実現できます。

今回はWebサーバのhttpdアクセスログをFluentdでS3へ飛ばします。

それではいってみよう。

Simple Storage Service

  1. S3画面へ遷移
    AWSのメニューからS3を選択します。

  2. バケットの作成
    ファイル保存先のトップディレクトリ(FQDN)となるバケットを作成します。
    [バケットの作成]ボタンを押下します。

  3. バケット名とリージョンの選択
    各項目を入力して、[作成]ボタンを押下します。
    バケット名:任意の名前を入力します。FQDNに含まれます。
    リージョン:バケットを作成するリージョンを選択します。

  4. フォルダ作成
    作成されたバケットが一覧に表示されます。
    バケットのリンクを選択します。
    バケットの中に入りました。
    作成直後は何もデータが無いため、[バケット「<バケット名>」は空です。]と表示されます。

    とりあえず今回はアクセスログを置くので、
    置き場となるフォルダを作成します。
    [フォルダの作成]ボタンを押下します。
    名前を入力します。

  5. Fluentd設定
    Fluentdのインストールは以下の記事を参考にしてください。sfujimoto.hatenablog.com

    設定は以下になります。
    input pluginはtailでaccess_logを監視します。
    output pluginはs3で対象となるAWSのキー情報とバケット名、パスを指定します。

    以下、サンプル

    # cat /etc/td-agent/td-agent.conf
    <source>
      type tail
      format apache
      path /var/log/httpd/access_log
      tag td.apache.access
    </source>
    
    <match td.apache.access>
      type s3
    
      aws_key_id **************
      aws_sec_key ***********************
      s3_bucket <bucket name>
      s3_region ap-northeast-1
      path <folder name>/
      buffer_path /var/log/td-agent/buffer
    
      # デバッグ用(デフォルトは1h)
      flush_interval 1s
    </match>

    今回は試験用でflush_intervalを1秒毎にしています。
    デフォルト設定では1時間単位にまとまったgzファイルが生成されます。
    # ファイル容量が8MBにならない限りは。


  6. 動作確認
    とりあえず、httpdにアクセスして、access_logに書き込みます。

    S3のManagementConsoleから作成したフォルダの中を確認します。
    httpdにアクセスした数だけgzファイルが存在することでしょう。

    中を覗いてみましょう。
    アップロードしたファイルのデフォルトアクセス権はAWSアカウントにしかないため、
    アクセス権の設定を行います。
    被付与者に「全員」を選択し、「開く/ダウンロード」を選択して、
    「保存」ボタンを押下します。

    オブジェクトの「リンク」からURLを確認します。

    gzファイルの中を確認してみると、
    アクセスログの情報がJSON形式に変換されて格納されています。

    # curl -s https://s3-ap-northeast-1.amazonaws.com/<bucket name>/<folder name>/2015062113_3.gz |gunzip
    2015-06-21T13:46:34Z	td.apache.access	{"host":"127.0.0.1","user":"-","method":"GET","path":"/","code":"200","size":"16","referer":"-","agent":"curl/7.40.0"}

    これで集約を実現できました。

    ただこの方法では単純な読み出しや解析が不便なので、
    ログ集約が目的ならばElasticSearch+Kibanaで可視化する方が有用です笑

    次回はちょっと逸れてCloudFormationを試してみたいと思います。


AWS ElasticCache

先日、クリエーションラインさんが開催したHashiCorp社プロダクトの勉強会に参加しました。
Vagrantが有名で、その他プロダクトも注目されていることは、
色々な媒体で知っていましたが、
なかなか時間を確保出来ず、触ることができませんでした。

勉強会を通して何がいいな、って、
初めて社外の勉強会に参加しましたが、
個人で一から調べれば一週間かかるような知識を2時間という短時間で得ることができます。
今まで引きこもって勉強してきましたが、
これからはもっと外の世界に目を向けて勉強していきたい。
connpassやDoorkeeper等、今は手を伸ばせばこういう機会を得ることができます。

さて、本題。
前回はAMIを利用してELBを利用して、
複数のEC2インスタンスへのバランシングを利用することができました。

ロードバランサーのバランシングルールにRoundRobinやLeastConnectionを採用するに直面する課題があります。
セッション維持です。
アプリケーションサーバでクライアントのキャッシュ情報を保持することで、
クライアントはそのアプリケーションサーバのみアクセスするようにしないといけないこととなります。

この対策として有名な実装方式として、以下があります。

前者はL4LBではソースIPによる振り分けやL7LBではクッキーパーシステンスによる振り分けを利用することで実装可能です。
L4では振り分けが均等にならない、L7は高価だし、負荷が気になります。

ということで、今回はElasticCacheを利用した後者を実装します。

EC2でWebAP構築
 ↓
RDSでWebAP-DB構築
 ↓
AMIでEC2のイメージ化、複製
 ↓
ELBで冗長化
 ↓

ElastiCacheでセッション管理 (今回はここ)

 ↓
S3にアクセスログ保存
 ↓
(できれば)SQSでWebAP連携

今回はElasticCacheを起動して、
どちらのWebサーバにアクセスしてもセッション情報を返すような構成を組みます。
ElasticCacheでやることは少ないです。。

ElasticCahceは実装方式として、Memcached、Redisがあります。
両方共、オンメモリタイプのKVSです。
私のイメージは安定的なMemcached、高機能なRedisです。
Memcachedはメモリにデータの参照、追加/追記/変更/削除、加減算しかできないが、シンプルな分、安定動作が期待できる。
Redisはパターン一致やList,Hash型の取り扱いや永続化等の機能を実装している。

今回はセッション情報の扱いたいだけなのでMemcachedを利用します。

環境はずっと利用しているDjangoを利用したWebアプリケーション構成を引き続き利用します。
実はDjangoは初期設定でセッション情報をデータベースに出力するようになっています。
つまり、既にセッション管理不要な状況です笑
で、でもパフォーマンス面を考えれば、ElasticCacheの方がよいよね。そうだよね。

それではいってみよう。

Elastic Cache

  1. Elastic Cache画面へ遷移
    AWSのメニューからElastic Cacheを選択します。

  2. Cache Subnet Group作成
    Elastic Cacheを作成する前にElastic Cache用にSubnet Groupを作成する必要があります。

    覚えている方もいると思いますが、RDS作成時にもSubnet Groupを作成しました。
    考え方は同じです。
    Elastic CacheはCluster構成とすることが一般的です。
    またCluster構成はAvailabilityZone(AZ)を跨いだ分散配置が可能です。

    APサーバがAZを跨いでいるのでそれぞれのAPサーバは同じAZのElastic Cacheを参照したいです。
    その願い叶えましょう、AWSが。
    Elastic Cache作成前は確認できないですが、作成済みだと思ってください。

    AP#1(AZ#1)で作成されたElastic CacheのEndpointホスト名を名前解決してください。

    [root@ip-192-168-0-54 ~]# dig test-cache.g0y8q8.cfg.apne1.cache.amazonaws.com
    
    ; <<>> DiG 9.8.2rc1-RedHat-9.8.2-0.30.rc1.36.amzn1 <<>> test-cache.g0y8q8.cfg.apne1.cache.amazonaws.com
    ;; global options: +cmd
    ;; Got answer:
    ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 33761
    ;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
    
    ;; QUESTION SECTION:
    ;test-cache.g0y8q8.cfg.apne1.cache.amazonaws.com. IN A
    
    ;; ANSWER SECTION:
    test-cache.g0y8q8.cfg.apne1.cache.amazonaws.com. 60 IN A 192.168.1.13
    
    ;; Query time: 84 msec
    ;; SERVER: 192.168.0.2#53(192.168.0.2)
    ;; WHEN: Sun May 31 03:05:35 2015
    ;; MSG SIZE  rcvd: 81

    次はAP#2(AZ#2)。

    [root@ip-192-168-100-35 ~]# dig test-cache.g0y8q8.cfg.apne1.cache.amazonaws.com
    
    ; <<>> DiG 9.8.2rc1-RedHat-9.8.2-0.30.rc1.36.amzn1 <<>> test-cache.g0y8q8.cfg.apne1.cache.amazonaws.com
    ;; global options: +cmd
    ;; Got answer:
    ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 17159
    ;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
    
    ;; QUESTION SECTION:
    ;test-cache.g0y8q8.cfg.apne1.cache.amazonaws.com. IN A
    
    ;; ANSWER SECTION:
    test-cache.g0y8q8.cfg.apne1.cache.amazonaws.com. 60 IN A 192.168.101.230
    
    ;; Query time: 2 msec
    ;; SERVER: 192.168.0.2#53(192.168.0.2)
    ;; WHEN: Sun May 31 03:05:46 2015
    ;; MSG SIZE  rcvd: 81

    結果が違います。
    ちな、192.168.1.0/24はAZ#1のPrivateSubnet、192.168.101.0/24はAZ#2のPrivateSubnet。
    それぞれCache Subnet Groupに割り当てています。
    つまり、そういうことです。

    順を追って一応説明します。

    AP#1/AP#2は同じIPアドレス(192.168.0.2)のDNSサーバを参照しています。
    なのに、返ってきたIPアドレスが違います。
    ということは同じIPアドレスですが、それぞれ違うDNSサーバを参照していることが想像できます。

    これによってそれぞれのAPサーバは通信範囲を自身が配置されたAZ内だけとすることができます。

    設定に戻ります。

    左メニューからCache Subent Groupsを選択し、[Create Cache Subnet Group]ボタンを押します。

        • -

    Name: 任意の名前
    Description: 任意の説明
    VPC ID: Elastic Cacheを設置するVPC
    Availability Zone: 追加するSubnetが存在するAZ
    Subnet ID: 追加するSubnet

        • -

    Subnet IDを選択して[Add]ボタンを押すことで追加されます。
    追加したいSubnetの数だけAvailability Zone、Subnet IDを選択、[Add]を繰り返します。

    Subnet IDがIDというところがイケてないです。
    プログラミングする上では一意となるIDが嬉しいですが、GUIでIDって言われても覚えていません。
    一度[Add]しないとそれらの情報は表示されません。
    任意で入力できる名前やCIDR Blockをプルダウン内に表示してほしいところです。


  3. Elastic Cache用Security Group作成
    Security Groupを作成します。
    Memcachedを利用する場合、デフォルト接続ポートは11211です。
    ルールはInboundでAPサーバのセキュリティグループからの通信に対して11211のみを開放します。

    ちな、Redisのデフォルト接続ポートは6379です。


  4. Elastic Cache Cluster作成
    Elastic Cacheを作成します。
    左メニューからElastic Cache Clustersを選択し、[Get Started]ボタンを押します。


  5. Select Engine
    実装方式を選択します。
    先の説明にあったように今回はMemcachedを選択します。


  6. Specify Cluster Details
    Memcachedの一般設定情報を入力します。

    Engine Version: 利用可能なバージョンからプルダウンで選択可能
    Port: デフォルトは11211
    Parameter Group: 詳細設定のパラメータを選択可能
         事前にParameter Groupで詳細設定情報を定義しておくことでMemcachedに設定を投入できます。
    Cluster Name: 任意の名前。
         Endpointにも利用されます。
    Node Type: スペックを選択可能
    Number of Nodes: Clusterのノード数(MultiAZ構成とする場合、最低2つ)


  7. Configure Advanced Settings
    Cache Subnet Group: 2で作成したSubnet Groupを選択
    Availability Zone(s): どういうAZ構成にするか選択可能
         [No Preference]、[Spread Nodes Cross Zones]、[Specify Zones]から選択可能
         分散配置したい場合、[Spread Nodes Cross Zones]を選択
    VPC Security Group(s): 3で作成したSecurity Groupを選択
    Maintenance Window: 自動メンテナンス(パッチ適用)とするか、する場合、可能な日にち、曜日、時間等で指定可能。
         [Select Window]、[No Preference]から選択可能
    Topic for SNS Notification: 自動メンテナンス時の通知先(SNSのARNで指定)


  8. 作成
    確認画面で設定内容を確認し、作成を開始します。
    5分ほどで出来ます。


  9. AP側の準備
    LoginURLにユーザ名、パスワードを渡すと、RDSのUserテーブルからユーザ名を元にデータを取得し、パスワードを照合できると、ログイン成功とします。
    セッションにユーザ名を登録します。
    HelloURLにアクセスすると、セッション情報からユーザ名を取得し、Hello, ユーザ名、を返します。
    セッション情報からユーザ名を取得できなければ、ログインしろよ、を返します。

    またそれぞれアクセス先がわかるようにレスポンスにAPサーバのホスト名も含めます。

    それではササッと作ります。

    あー、時間かかった。
    無駄にロギング、エラーハンドリング入れすぎた。

    AWS関係ないのでメモ程度

    Elastic Cache設定追加、CSRF対策解除

    vi /var/lib/project/project/settings.py
    -----
    
    CACHES = {
        'default': {
            'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
            'LOCATION': 'test-cache.g0y8q8.cfg.apne1.cache.amazonaws.com:11211',
        }
    }
    
    #    'django.middleware.csrf.CsrfViewMiddleware',

    Login、Hello URL追加

    vi /var/lib/project/project/urls.py
    -----
    
        url(r'^login/', views.login, name='login'),
        url(r'^hello/', views.hello, name='hello'),

    model設定追加

    vi /var/lib/project/app/models.py
    -----
    
    class User(models.Model):
      name = models.CharField(max_length=32)
      password = models.CharField(max_length=32, null=True)
      created = models.DateField()

    ※ 今回、passwordカラムだけを追加したのですが、
      dbmigrations、migrateでテーブルにカラムが追加されます。
      既にデータがある場合、not null成約を外さないとエラーとなります。

    views設定追加(エラー処理削除)

    vi /var/lib/project/app/views.py
    -----
    
    def login(request):
      res = {"server": gethostname(), "message":""}
      body = request.body
      user_dict = json.loads(body)
      users = User.objects.filter(name=user_dict["user"])
      user = users[0]
      request.session["user"] = user.name
    
      res["message"] = "ok"
      return HttpResponse(json.dumps(res))
    
    
    def hello(request):
      res = {"server": gethostname(), "message":""}
      username = request.session.get("user")
      if not username:
        res["message"] = "You are not logined"
        return HttpResponse(json.dumps(res))
      res["message"] = "Hello, " + username
      return HttpResponse(json.dumps(res))


  10. 動作確認
    ELBをエンドポイントにアカウント情報を引き連れて、LoginURLへアクセスします。

    $ curl -d '{"user":"shinji","pass":"password"}' -X POST http://lb-*********.ap-northeast-1.elb.amazonaws.com/app/login/
    {"message": "ok", "server": "ip-192-168-100-35"}

    AP#2からレスポンスが返ってきました。

    もう一回。

    $ curl -d '{"user":"shinji","pass":"password"}' -X POST http://lb-*********.ap-northeast-1.elb.amazonaws.com/app/login/
    {"message": "ok", "server": "ip-192-168-0-54"}

    AP#1からレスポンスが返ってきました。

    次にHelloURLへCookieを渡さずアクセスします。

    $ curl http://lb-*********.ap-northeast-1.elb.amazonaws.com/app/hello/
    {"message": "You are not logined", "server": "ip-192-168-100-35"}

    ログインしてませんよー、よしよし。

    次はCookieを取得して、Cookieを渡してHelloを受け取ります。

    $ curl -c cookie.txt -d '{"user":"shinji","pass":"password"}' -X POST http://lb-*********.ap-northeast-1.elb.amazonaws.com/app/login/
    {"message": "ok", "server": "ip-192-168-100-35"}
    
    $ curl -b cookie.txt http://lb-***********.ap-northeast-1.elb.amazonaws.com/app/hello/
    {"message": "Hello, shinji", "server": "ip-192-168-0-54"}

    AP#2にログインして、AP#1からHelloを受け取れました。


いかがでしたでしょうか?

これである程度の可用性、スケーラビリティを確保したWebアプリケーションシステムを組むことが出来ました。

クラウドIaaSを利用すると、なんといってもお手軽です。

私が入社した時は検証環境なんて一つの検証サーバをOSレベル、アプリケーションレベルで何人か共有していたり、本番環境はOSが分かれているところ同居させていたりが当たり前で、検証環境なのに気を使ったり、本番環境と違う構成だから本番環境にデプロイすると想定外のエラーがあったりと本当に苦労しました。(ブツブツ

次回は運用面です。

AWS ELB

4ヶ月ぶりにダーツ部が活動しました。
ぜんっぜんダメでした。
3時間投げても、4ヶ月前の投げ方がわかりませんでした。
1年ぐらい前に三ヶ月間ほど週二で通って身につけてフォームもブランク空くと分からなくなるもんです。
ブログはそうならないように頑張ります。

さて、本題。
前回はAMIを利用してEC2インスタンスを複製し、
同一構成のEC2インスタンスを2つのAZに分けて配置しました。

ただし、これだけでは2つのWebサーバがあるだけで、
負荷分散も冗長化も構成されていません。
入口となるグローバルIPアドレスDNSがバラバラだからです。

一般的な負荷分散方式としてDNSラウンドロビンを使ったDNSによる負荷分散、
ロードバランサーを入口に置くことで振り分けてもらうことによる負荷分散があります。
前者は負荷分散としては意味がありますが、冗長化の観点では役に立たないため、あまり採用されません。

ロードバランサーの過負荷となる場合にロードバランサー複数並べてDNSラウンドロビンして負荷分散することは可能です。
ただし、この場合も各ロードバランサー冗長化しないと、意味を成し得ません。
またGSLBのようにDNSによって死活監視を行うことでDNSレコードをダイナミックに切り替えるシステムを使うことでロードバランサーの負荷も、そもそもロードバランサーを置く必要がなくなる仕組みもあります。
AWSのRoute53を使えば簡単に組むことができます。

、、、今調べたらELBは負荷に応じてスケーリングするようです。
AWSさん、さすがです。

今回はELBを起動して、
2つのWebサーバを負荷分散、冗長化できる構成を組みます。

EC2でWebAP構築
 ↓
RDSでWebAP-DB構築
 ↓
AMIでEC2のイメージ化、複製
 ↓

ELBで冗長化 (今回はここ)

 ↓
ElastiCacheでセッション管理
 ↓
S3にアクセスログ保存
 ↓
(できれば)SQSでWebAP連携

ELB (Elastic Load Balancing)

ELBはロードバランサーです。
通信を振り分けてくれることにより負荷分散してくれたり、
負荷分散対象を監視し、ダウンした対象を切り離すことで冗長化構成としてくれます。

ELB作成

  1. EC2画面へ遷移
    AWSのメニューからEC2を選択します。
    ELBはEC2画面より作成します。

    AWSのポータルが日本語対応しました。
    アマゾンジャパンの方、本当にありがとうございます。

  2. ELB作成
    左のメニューからロードバランサーを選択します。
    ロードバランサーの作成」ボタンを押下します。

  3. ロードバランサーの定義
    基本情報を入力します。
    ロードバランサー
      : 名前です。DNS名にも利用されます。
      今回はlbとします。
    内部向け LB の作成
      : 作成する対象のVPCを指定します。
    内部向けロードバランサーの作成
      : インターネットに開放せず、内部からのアクセスのみの場合、
      こちらをチェックしてください。
    ロードバランサープロトコル/ロードバランサーのポート
      : アクセス元が指定するプロトコル/ポート番号です。
      今回は80番ポート(http)を利用します。
    インスタンスプロトコル/インスタンスのポート
      : ロードバランサーインスタンスにアクセスするプロトコル/ポート番号です。
      今回は80番ポート(http)を利用します。
    サブネットの選択
      : バランシン対象が存在するサブネットを選択します。
      今回は2つのazにあるpublic networkを選択します。
      EC2インスタンスに設定したサブネットです。

  4. セキュリティグループの割り当て
    80番ポートが空いていればいいのでEC2インスタンスに割り当てたセキュリティグループを選択します。

  5. セキュリティ設定の構成
    httpだと注意されます。。

  6. ヘルスチェックの設定
    ELBがEC2インスタンスを監視する方式を指定します。

    pingプロトコル
      : HTTP、HTTPSSSLTCPから選択します。
      今回はHTTPを利用します。
    pingポート
      : そのポート番号です。
      今回は80番ポートを利用します。
    pingパス
      : ヘルスチェック先パスを指定します。デフォルト値は「/index.html」です。
      SSLTCPの場合、指定不要です。
    応答タイムアウト(秒)
      : ヘルスチェックの応答待ち時間を指定します。デフォルト値は「5」です。
      ここで指定した時間以内に応答がない場合、NGと判断します。
    ヘルスチェック間隔(秒)
      : ヘルスチェックを行う間隔を指定します。デフォルト値は「30」です。
    非正常のしきい値
      : EC2インスタンスがダウンしたと判断するヘルスチェックNG連続回数を指定します。
      デフォルト値は「2」です。
    正常のしきい値
      : EC2インスタンスがアップしたと判断するヘルスチェックOK連続回数を指定します。
      デフォルト値は「10」です。

    デフォルト設定の場合、30秒ごとに監視対象サーバに向けて、
    http://<対象サーバ>/index.html
    のリクエストを発行し、5秒間応答がない、を2連続検知すると、
    監視対象サーバがダウンしたと判断します。

  7. EC2 インスタンスの追加
    振り分け対象のサーバを指定します。
    今回は作成したWebサーバ2台を選択します。

    クロスゾーン負荷分散の有効化
      : LBが存在するEC2インスタンスにしか振り分けない場合、こちらのチェックを外してください。
    Connection Drainingの有効化
      : LBがEC2インスタンスを強制的に切り離す時間を設定します。
      ファイルダウンロード等長時間のリクエスト処理があった場合、
      切り離してもここで指定した秒数は待つ、という設定です。
      メンテナンス等で片系切り離して設定変更などする時に大変有用な機能です。

  8. タグの追加
  9. 確認
    LBが作成されます。

  10. 作成状況確認
    一覧画面へ遷移します。

    作成されたELBを選択して、概要タブを選択します。

    ステータスが「2 個のうち 2 個のインスタンスが実行中です」となっていれば準備完了です。
    DNS名はメモってください。

    インスタンスタブを選択します。

    インスタンスの各ステータスが「InService」となっていることも確認します。

  11. 動作確認
    それでは接続しましょう。
    その前に、、、
    接続先がわかるように各Webサーバのindex.htmlをホスト名等分かり易いように編集してください。

    $ curl http://lb-*********.ap-northeast-1.elb.amazonaws.com/index.html
    ip-192-168-100-35

    Web#2(複製先)へアクセスされました。
    それではもう一回。

    $ curl http://lb-*********.ap-northeast-1.elb.amazonaws.com/index.html
    ip-192-168-0-54

    Web#1(複製元)へアクセスされました。
    はい、ちゃんとバランシングされていますね。

    次に片系を落としてみて切り離されることを確認しましょう。

    今回はWeb#2を落とします。

    [root@ip-192-168-100-35 ~]# /etc/init.d/httpd stop
    Stopping httpd:                                            [  OK  ]

    15秒ぐらいでWeb#2のステータスがOutOfServiceとなりました。
    それでは接続

    $ curl http://lb-*********.ap-northeast-1.elb.amazonaws.com/index.html
    ip-192-168-0-54

    Web#1にアクセスされました。
    10回ほどアクセスしても全てWeb#1へアクセスされました。

    Web#2を起動します。

    [root@ip-192-168-100-35 ~]# /etc/init.d/httpd start
    Starting httpd: httpd: apr_sockaddr_info_get() failed for ip-192-168-100-35
    httpd: Could not reliably determine the server's fully qualified domain name, using 127.0.0.1 for ServerName
                                                               [  OK  ]
    >||
    
    10分ほどでWeb#2のステータスはInServiceとなりました。
    
    >||
    $ curl http://lb-*********.ap-northeast-1.elb.amazonaws.com/index.html
    ip-192-168-0-54
    $ curl http://lb-*********.ap-northeast-1.elb.amazonaws.com/index.html
    ip-192-168-100-35

    またWeb#2へ振り分けられるようになりました。
    以前、ELBは切り離されたインスタンスはアップしても自動復旧しない(ELBに組み込まれない)という情報を得たのですが、改善したようです。

いかがでしたでしょうか?

AWSを使えば負荷分散、冗長構成のWebAPシステムを簡単に構成することが出来ます。
メンテナンスも考慮されたシステムとなっており、インフラ担当の悩みをどんどんAWSが吸収してくれます。
インフラ担当の運用・保守をAWSに任せることができるので、
その分、楽しい楽しい開発業務に専念することができます。

次回はElastic Cacheを利用してステートレスのWebAP構成を作りましょう。

「Amazon Web Services徹底活用ガイド」を読みました

Amazon Web Services 徹底活用ガイド (日経BPムック)

Amazon Web Services 徹底活用ガイド (日経BPムック)

AWSのノウハウ、Tipsを学びたく読んでみました。

AWSを触ったことがない人にはオススメの一冊だと思います。

AWSの機能紹介はいらなかった。
もっと多くの事例紹介が欲しかったです。

とにかく感じたことはオンプレから移行するなら方式設計、運用設計は大きく見直す必要がある。
郷に入っては郷に従え!に尽きます。

脱vSphereClient (Apple信者向け)

最近、自宅マシンにESXiをインストールしました。

プライベートの端末がOSXということもあり、vSphereClientの利用がサポートされません(*)。
VirtualBox上のWindowsマシンにインストールして操作する、という利用方法もありますが、
vSphereClientを利用するのに、VirtualBoxを起動して、Windowsを起動して、vSphereClientを起動。
んで、GUI操作ぽちぽち。

めんどい!

実はある程度のことはESXiにSSH接続してコマンドラインで出来るんです!

今回はVM作成を実施してみます。

ESXiのインストールやストレージ、ネットワークは準備済みとします。

ほとんどはvim-cmdコマンドを利用します。

vim-cmd

サブコマンドを実施し、色々なことを実現できます。

[root@localhost:~] vim-cmd
Commands available under /:
hbrsvc/       internalsvc/  solo/         vmsvc/
hostsvc/      proxysvc/     vimsvc/       help

[root@localhost:~] vim-cmd hostsvc
Commands available under hostsvc/:
advopt/                   enable_ssh                refresh_firewall
autostartmanager/         firewall_disable_ruleset  refresh_services
datastore/                firewall_enable_ruleset   reset_service
datastorebrowser/         get_service_status        runtimeinfo
firmware/                 hostconfig                set_hostid
net/                      hosthardware              standby_mode_enter
rsrc/                     hostsummary               standby_mode_exit
storage/                  login                     start_esx_shell
summary/                  logout                    start_service
vmotion/                  maintenance_mode_enter    start_ssh
connect                   maintenance_mode_exit     stop_esx_shell
cpuinfo                   pci_add                   stop_service
disable_esx_shell         pci_remove                stop_ssh
disable_ssh               queryconnectioninfo       task_list
enable_esx_shell          querydisabledmethods      updateSSLThumbprintsInfo

例えば、
vim-cmd hostsvc/enable_sshを実行すると、ESXiホストでssh接続が有効となります。
vim-cmd maintenance_mode_enterを実行すると、ESXiホストをメンテナンスモードにします。
とかです。

それではいってみよう。
今回はCentos6のVMを作成します。

  1. VM作成
  2. ブランクVMを作成します。

    [root@localhost:~] vim-cmd vmsvc/createdummyvm centos6-1 /vmfs/volumes/local_ssd
    4

    戻り値はVMのIDです。

    [root@localhost:~] ls -l /vmfs/volumes/local_ssd/centos6-1_3/
    total 1024
    -rw-------    1 root     root       1048576 May 10 10:42 centos6-1_3-flat.vmdk
    -rw-------    1 root     root           465 May 10 10:42 centos6-1_3.vmdk
    -rw-r--r--    1 root     root             0 May 10 10:42 centos6-1_3.vmsd
    -rwxr-xr-x    1 root     root           977 May 10 10:42 centos6-1_3.vmx
    [root@localhost:~] cat /vmfs/volumes/local_ssd/centos6-1_3/centos6-1_3.vmx
    .encoding = "UTF-8"
    config.version = "8"
    virtualHW.version = "11"
    nvram = "centos6-1_3.nvram"
    pciBridge0.present = "TRUE"
    virtualHW.version = "11"
    nvram = "centos6-1_3.nvram"
    pciBridge0.present = "TRUE"
    svga.present = "TRUE"
    pciBridge4.present = "TRUE"
    pciBridge4.virtualDev = "pcieRootPort"
    pciBridge4.functions = "8"
    pciBridge5.present = "TRUE"
    pciBridge5.virtualDev = "pcieRootPort"
    pciBridge5.functions = "8"
    pciBridge6.present = "TRUE"
    pciBridge6.virtualDev = "pcieRootPort"
    pciBridge6.functions = "8"
    pciBridge7.present = "TRUE"
    pciBridge7.virtualDev = "pcieRootPort"
    pciBridge7.functions = "8"
    vmci0.present = "TRUE"
    hpet0.present = "TRUE"
    floppy0.present = "FALSE"
    scsi0.virtualDev = "lsilogic"
    scsi0.present = "TRUE"
    scsi0:0.deviceType = "scsi-hardDisk"
    scsi0:0.fileName = "centos6-1_3.vmdk"
    scsi0:0.present = "TRUE"
    displayName = "centos6-1"
    guestOS = "other"
    uuid.bios = "56 4d fd a6 54 0c 8e 22-a5 d9 9a 10 df 9d be ae"
    uuid.location = "56 4d fd a6 54 0c 8e 22-a5 d9 9a 10 df 9d be ae"
    vc.uuid = "52 79 a6 30 04 b7 91 37-35 5d ad 4c 42 08 59 a4"

    vmxファイル、vmdkファイル等おなじみのファイルが作成されています。
    勝手に付加された「_3」はどこから来たのでしょう。

  3. ISOファイルアップロード
  4. scpコマンドでCentOS6のisoファイルをdatastoreにアップロードします。

  5. VMリソース設定
  6. 最低限必要な箇所のみ設定します。

    まずは以下を設定。

    • CPU数 -> 2core
    • メモリ値 -> 2GB
    • ネットワーク -> 1つ
    • CDROMデバイス
    • ゲストOS種別
    • VNC
    [root@localhost:~] vi /vmfs/volumes/local_ssd/centos6-1_3/centos6-1_3.vmx
    
    以下、編集
    guestOS = "other"
     ↓
    guestOS = "rhel6-64"
    
    以下、追記
    numvcpus = "2"
    memSize = "2048"
    ide1:0.startConnected = "TRUE"
    ide1:0.deviceType = "cdrom-image"
    ide1:0.fileName = "/vmfs/volumes/local_ssd/CentOS-6.6-x86_64-minimal.iso"
    ide1:0.present = "TRUE"
    ethernet0.virtualDev = "vmxnet3"
    ethernet0.networkName = "VM Network"
    ethernet0.addressType = "generated"
    ethernet0.present = "TRUE"
    RemoteDisplay.vnc.enabled="true"
    RemoteDisplay.vnc.port="5901"
    RemoteDisplay.vnc.password="password"
    RemoteDisplay.vnc.keyMap="jp"

    ide1:0.fileNameはアップロードしたisoファイルのパス指定
    ethernet0.networkNameはポートグループ名
    RemoteDisplay.vnc.portはVNC接続するポート番号
    後述しますがVNC接続する用のポートをESXiで開放する必要があります。
    RemoteDisplay.vnc.passwordはVNC接続時のパスワード


    次はディスクサイズを設定。

    • ディスクサイズ -> 16GB
    [root@localhost:~] vmkfstools -c 10g -d thin /vmfs/volumes/local_ssd/centos6-1_3/centos6-1_3-1.vmdk
    [root@localhost:~] vi /vmfs/volumes/local_ssd/centos6-1_3/centos6-1_3.vmx
    
    以下、編集
    scsi0:0.fileName = "centos6-1_3.vmdk"
     ↓
    scsi0:0.fileName = "centos6-1_3-1.vmdk"
    
    [root@localhost:~] vmkfstools -U /vmfs/volumes/local_ssd/centos6-1_3/centos6-1_3.vmdk

    元のvmdkファイルは削除して構いません。

  7. VNC接続設定
  8. ESXiでVNC接続できるようにポートを開放します。

    セキュリティプロファイル定義
    [root@localhost:~] cp /etc/vmware/firewall/service.xml /etc/vmware/firewall/service.xml.bak
    [root@localhost:~] chmod u+w /etc/vmware/firewall/service.xml
    [root@localhost:~] chmod +t /etc/vmware/firewall/service.xml
    以下を<ConfigRoot>タグ内に追記
      <service id='0140'>
        <id>vnc</id>
        <rule id='0000'>
          <direction>inbound</direction>
          <protocol>tcp</protocol>
          <port type='dst'>5901</port>
        </rule>
       <enabled>false</enabled>
       <required>false</required>
      </service>
    [root@localhost:~] chmod 444 /etc/vmware/firewall/service.xml
    ポート開放
    [root@localhost:~] esxcli network firewall refresh
    [root@localhost:~] esxcli network firewall ruleset list
    vnc                    false
    [root@localhost:~] esxcli network firewall ruleset set -r vnc -e true
    [root@localhost:~] esxcli network firewall ruleset set -r vnc -a true
    Already allowed all ip

  9. VM起動
  10. [root@localhost:~] vim-cmd vmsvc/power.on 3
    Powering on VM:

  11. コンソール接続
  12. OSXの場合、デフォルトでVNCクライアントが備わっています。
    Finderのメニューから[移動] - [サーバへ接続]を選択
    f:id:sfujimoto:20150510215217p:plain

    vnc://:<設定したポート番号>
    f:id:sfujimoto:20150510215013p:plain

    vmxファイルに設定したパスワードを入力
    f:id:sfujimoto:20150510220408p:plain

    コンソールの画面が表示されます。
    f:id:sfujimoto:20150510215356p:plain

このようにコマンドとVNC接続により、VMを構築できました。
細やかな設定は難しいところもありますが、
多くの機能を扱うことができます。

私は省エネのため、利用しない時はESXiを落としています。
そのため、VMを全てシャットダウンして、シャットダウンが完了したらESXiをシャットダウンするスクリプトと、
ESXiを起動したらtxtファイルに登録したVMを自動で起動するスクリプトを作成しています。
またESXiはpythonを標準搭載しているのでpythonistaには助かります。

(*) Wineでほとんどの機能を利用可。