Amazon Linux に Elasticsearch 5.5 をインストール

https://www.elastic.co/guide/en/elasticsearch/reference/5.5/rpm.htmlの通りですが、メモしておきます。

elasticsearch リポジトリの設定を/etc/yum.repos.d/elasticsearch.repoに下記の内容で保存します。

[elasticsearch-5.x]
name=Elasticsearch repository for 5.x packages
baseurl=https://artifacts.elastic.co/packages/5.x/yum
gpgcheck=1
gpgkey=https://artifacts.elastic.co/GPG-KEY-elasticsearch
enabled=1
autorefresh=1
type=rpm-md

Elasticsearch をインストール

$ sudo yum install -y elasticsearch

8 以降の java が必要との事なのでインストール

$ sudo yum install -y java-1.8.0-openjdk

CentOS と違ってデフォルトで java 7 がインストールされているのでバージョンを切り替えます。

$ sudo /usr/sbin/update-alternatives --set java /usr/lib/jvm/jre-1.8.0-openjdk.x86_64/bin/java

Elasticsearch 起動

# 起動時に自動で起動
$ sudo chkconfig --add elasticsearch
$ sudo -i service elasticsearch start

Starting elasticsearch: OpenJDK 64-Bit Server VM warning: INFO: os::commit_memory(0x000000008a660000, 1973026816, 0) failed; error='Cannot allocate memory' (errno=12)

メモリサイズが足りないとのことなので /etc/elasticsearch/jvm.options を修正

-Xms500m
-Xmx500m

試しに日本語用アナライザ用らしいプラグインをインストール、Elasticsearch 再起動

sudo /usr/share/elasticsearch/bin/elasticsearch-plugin install analysis-kuromoji

起動しているか確認

$ curl "localhost:9200"
{
  "name" : "hoRwe69",
  "cluster_name" : "elasticsearch",
  "cluster_uuid" : "50OEG4CdTmKwB8o7fQVJag",
  "version" : {
    "number" : "5.5.1",
    "build_hash" : "19c13d0",
    "build_date" : "2017-07-18T20:44:24.823Z",
    "build_snapshot" : false,
    "lucene_version" : "6.6.0"
  },
  "tagline" : "You Know, for Search"
}

参考

java バージョン切り替え http://qiita.com/genreh/items/5ca16775442f3e3a355a

update-alternativesの使い方 http://graziegrazie.hatenablog.com/entry/2015/11/14/101050

GraphQL 入門

DDD + CQRS で RESTAPI を開発している過程で、クエリーの API のインターフェースで悩んでいました。
具体的には >= (以上)とか <= (以下)みたいな関係演算子等を含んだパラメータを REST API でどう受け取るのが良いのか、です。
GraphQL ならその辺りの仕様だとかベストプラクティスがありそうだと思い、とりあえずチュートリアルをやってみたのでそのメモです。 このチュートリアルでは graphql-jsExpress を使用しています。

var express = require('express');
var graphqlHTTP = require('express-graphql');
var { buildSchema } = require('graphql');

// GraphQL schema
var schema = buildSchema(`
  # GraphQL schema において Query と Mutation は特別で、エントリーポイントになります。
  # REST でいう GET  のように副作用のない API は Query 、副作用がある場合は Mutation を使います。今回は Query だけ使います。
  type Query {
    quoteOfTheDay: String                                           # String 型で返却
    random: Float!                                                          # Not Null な Float 型で返却 
    rollDice(numDice: Int!, numSides: Int): [Int]           # サイコロの数とサイコロの目の数を受け取ってサイコロの目の配列を返却
  }
`);


// API のエンドポイント
var root = {
  quoteOfTheDay: () => {
    return Math.random() < 0.5 ? 'Take it easy' : 'Salvation lies within';
  },
  random: () => {
    return Math.random();
  },
  rollDice: function ({ numDice, numSides }) {
    var output = [];
    for (var i = 0; i < numDice; i++) {
      output.push(1 + Math.floor(Math.random() * (numSides || 6)));
    }
    return output;
  }
};

var app = express();
app.use('/graphql', graphqlHTTP({
  schema: schema,
  rootValue: root,
  graphiql: true,
}));
app.listen(4000);
console.log('Running a GraphQL API server at localhost:4000/graphql');

上記内容で server.js ファイルを作成し、起動。

node server.js

curl で確認。

# quoteOfTheDay クエリを実行
$ curl -X POST -H "Content-Type: application/json" -d '{"query": "{ quoteOfTheDay }"}' http://localhost:4000/graphql
{"data":{"quoteOfTheDay":"Salvation lies within"}}
# もしくは {"data":{"quoteOfTheDay":"Take it easy"}} が返ってくる。

# rollDice クエリを実行
$ curl -X POST -H "Content-Type: application/json" \
    -d '{"query": "{rollDice(numDice: 3, numSides: 6)}"}' \
    http://localhost:4000/graphql
{"data":{"rollDice":[1,2,3]}}
# さいころの目なので結果はランダム。

# 変数も使える。↓と↑は同義。
# $ が前についてあるものは変数。query キーと同階層に variables キーを用意して値を定義する。
$ curl -X POST \
    -H "Content-Type: application/json" \
    -d '{"query": "query RollDice($dice: Int!, $sides: Int) { rollDice(numDice: $dice, numSides: $sides) }", "variables": {"dice":3, "sides":6} }'  \
    http://localhost:4000/graphql
{"data":{"rollDice":[3,5,6]}}

また、下記から GraphiQLという GUI クライアントツールが使えます。 http://localhost:4000/graphqlにアクセス。
左側に ↓ をコピペして実行すると右側に結果が出力されます。

{
  quoteOfTheDay
  random
  rollDice(numDice:3)
}

全体のソースコードこちらになります。

ここまでやったところで僕が元々イメージしていたものとは大分違う気がしてたので探してみました。 GraphQL はそこまでサポートしていないのでやりたい場合は自分で定義しなければいけないようです。 forums.meteor.com

クロスドメイン問題を回避した時のメモ

 今の業務ではフロントエンドアプリケーションとバックエンド API 用で分けて開発しています。 フロントエンドアプリケーションはサーバーレスにする予定です。 動作確認のために一々デプロイするのは面倒なため、バックエンドサーバーの設定を変えてクロスオリジン通信を許可したメモです。


* バックエンドサーバーの nginx の設定ファイルを編集して適切なロケーションディレクティブにレスポンスヘッダを追加

location / {
        if ($request_method = 'OPTIONS') {
                add_header 'Access-Control-Allow-Credentials' 'true';
                add_header 'Access-Control-Allow-Headers'     'Authorization,Content-Type,Accept,Origin,User-Agent,DNT,Cache-Control,X-Mx-ReqToken,Keep-Alive,X-Requested-With,If-Modified-Since';
                add_header 'Access-Control-Allow-Methods'     'GET, DELETE, OPTIONS, POST, PUT';
                add_header 'Access-Control-Allow-Origin'      'http://example.com';
                add_header 'Access-Control-Max-Age'           2592000;
                add_header 'Content-Length'                   0;
                add_header 'Content-Type'                     'text/plain charset=UTF-8';
                return 204;
        }

        add_header 'Access-Control-Allow-Origin' '*' always;
        add_header 'Access-Control-Allow-Credentials' 'true';
        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE';
        add_header 'Access-Control-Allow-Headers' 'Content-Type, Accept';
}


  • 設定反映のため、nginx 再起動
nginx -s reload


  • バックエンドサーバーにリクエストを送り、レスポンスを確認。
# -I : HTTP レスポンスヘッダを表示 
curl -I https://[site-url]

# 設定したレスポンスヘッダが確認出来れば OK
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
Access-Control-Allow-Method: GET, POST, OPTIONS, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Accept


  • nginx 設定ファイルが間違っている場合はシンタックスチェックしながら修正
nginx -t -c /path/nginx.conf


  • nginx 設定補足
if ($request_method = 'OPTIONS') {

HTTP DELETE メソッドなどのリソースを変更する可能性のあるリクエストを送る場合、ブラウザはまずプリフライトリクエストを HTTP OPTIONS メソッドで送信します。

add_header 'Access-Control-Max-Age'           2592000;

プリフライトリクエストを毎回送らなくて良いように、プリフライトリクエストのレスポンスをブラウザでキャッシュして良い時間。
この例では 60 (秒) x 60 (分) x 24 (時間) x 30 (日) で 30 日間です。
実際にはブラウザ毎に設定されている上限値になります。

add_header 'Access-Control-Allow-Origin' '*' always;

always パラメータを指定しておくと、4XX 、5XX の HTTP ステータスコードだった場合でもヘッダ情報を付加してくれます。

  • ハマったところ

設定するロケーションディレクティブが間違っていた。 ロケーションディレクティブは読み込む優先順位があって、上に書いてあるから読み込まれる訳ではなかった。

Nginx で location の判定方法と優先順位を調べる | レンタルサーバー・自宅サーバー設定・構築のヒント

Ansible で PHP 環境構築 ② - vim インストール -

seven-greenz.hatenablog.com

の続きです。 ゲスト OS ( CentOS) に最新の vim をインストールしました。

最新の vim インストール用プレイブック

playbook.yml

    # Ansible が標準で提供しているモジュールには基本的に冪等性があります。冪等性については後述
    # shell モジュールは冪等性がありません。
    # 下の install vim タスクで shell モジュールを使用しているためプロビジョニング毎に処理を実行してしまい無駄に処理時間が長くなってしまいます。
    # そのため is vim installed タスクで vim --version の返却値を登録しておき install vim タスクでチェックして vim --version が失敗した場合はインストールをスキップしています。
    - name: is vim installed
      shell: /usr/local/bin/vim --version
      register: is_vim_installed
      # 初回実行時に発生する vim が見つからない異常終了を無視して処理を続行します。
      ignore_errors: True
      # 本タスク自体も shell モジュールを指定しているため毎回 changed が出力されます
      # 無用な混乱をもたらしそうであり、環境を変えるコマンドではないことがわかっているため changed は出力しないようにします。
      changed_when: False

    # lua 付きの vim でないと使えないプラグインがあるため。ただ最新の vim をインストールするだけでは必ずしも必要ではないかと思います。
    # vimの依存パッケージインストール
    - name: install dependency packeges
      yum:
        name: "{{ item }}"
        state: latest
      with_items:
        - lua-devel
        - ncurses-devel

    - name: download vim
      git:
        repo: 'https://github.com/vim/vim.git'
        dest: /usr/src/vim

    - name: install vim
      # --prefix: インストール先
      shell: "{{ item }}"
      args:
        chdir: /usr/src/vim
      with_items:
        - make distclean
        - ./configure --prefix=/usr/local --enable-multibyte --with-features=huge --disable-selinux --enable-pythoninterp=yes --enable-luainterp=yes
        - make
        - make install
      when: is_vim_installed|failed
    - shell: /usr/local/bin/vim --version
      register: vim_version
      changed_when: False
    # register で登録された値の構成
    # http://qiita.com/szk3/items/29e827f90a543c764a5e
    - debug: var=vim_version.stdout_lines[0]

プロビジョニングの結果、8.0 の vim がインストールされました。

TASK [debug] *******************************************************************
ok: [simple] => {
    "changed": false,
    "vim_version.stdout_lines[0]": "VIM - Vi IMproved 8.0 (2016 Sep 12, compiled May 13 2017 18:36:55)"
}

ソースコードこちらです。

冪等性について

上の install dependency packeges タスクは yum モジュールを使用していて初回実行時と 2 回目以降では結果が違います。 詳しいことはわかりませんが、2 回目以降はok が出力されていて変わっていないことが保証されています。

  • 初回
TASK [install dependency packeges] *********************************************
changed: [simple] => (item=[u'lua-devel', u'ncurses-devel'])
  • 2 回目以降
TASK [install dependency packeges] *********************************************
ok: [simple] => (item=[u'lua-devel', u'ncurses-devel'])

それに対して shell モジュールを使用している vim install タスクは何回やっても changedが出力されるため実行する度に環境が変わってしまっている可能性があります。

  • 初回、2 回目以降
TASK [install vim] *************************************************************
changed: [simple] => (item=make distclean)
changed: [simple] => (item=./configure --prefix=/usr/local --enable-multibyte --with-features=huge --disable-selinux --enable-pythoninterp=yes --enable-luainterp=yes)
changed: [simple] => (item=make)
changed: [simple] => (item=make install)

Ansible で PHP 環境構築 ① - git インストール -

seven-greenz.hatenablog.com Vagrant を使って仮装環境を作成しました。その CentOS 仮装環境上に PHP 環境を構築したいと思います。 が、トライしてみたところ全然進まなかったので今回は git のインストールまでです。

Vagrantfile

Vagrant.configure("2") do |config|

  config.vm.box = "centos7.2"
  config.vm.network "private_network", ip: "192.168.33.20"

  config.vm.define :simple do |simple|
    simple.vm.hostname = 'simple'
    # ゲスト側にansibleをインストール
    config.vm.provision 'ansible_local' do |ansible|
      ansible.playbook = './playbook.yml'
    end
  end
end

playbook.yml

- hosts: simple
  become: yes
  tasks:
    #    - name: upgrade all packeages
    #      yum:
    #        name: '*'
    #        state: latest
    # 最新のgitをインストールするため
    - name: add IUS repository
       yum_repository:
         name: ius
         description: IUS YUM repo for install the latest git
         baseurl: https://dl.iuscommunity.org/pub/ius/stable/CentOS/7/x86_64
         # https://access.redhat.com/documentation/ja-JP/Red_Hat_Enterprise_Linux/6/html/Security_Guide/sect-Security_Guide-Updating_Packages-Verifying_Signed_Packages.html
         gpgkey: https://dl.iuscommunity.org/pub/ius/IUS-COMMUNITY-GPG-KEY
         gpgcheck: yes
    - name: install git
       yum:
         # 2 系は git2u というパッケージ名のようです。
         name: git2u
         enablerepo: ius
         state: present
    # インストールした git のバージョンの確認、いちいちログインするのが面倒だったため
    # もっと良いやり方がありそうなので調査
    - shell: git --version
       register: result
    - debug: var=result

これで vagrant up、もし起動済みであれば vagrant provisionすれば git がインストールされます。

$ vagrant up
Bringing machine 'simple' up with 'virtualbox' provider...
==> simple: Importing base box 'centos7.2'...
==> simple: Matching MAC address for NAT networking...
==> simple: Setting the name of the VM: ansible-playbook_simple_1493999707194_20088
==> simple: Fixed port collision for 22 => 2222. Now on port 2200.
==> simple: Clearing any previously set network interfaces...
==> simple: Preparing network interfaces based on configuration...
    simple: Adapter 1: nat
    simple: Adapter 2: hostonly
==> simple: Forwarding ports...
    simple: 22 (guest) => 2200 (host) (adapter 1)
==> simple: Booting VM...
==> simple: Waiting for machine to boot. This may take a few minutes...
    simple: SSH address: 127.0.0.1:2200
    simple: SSH username: vagrant
    simple: SSH auth method: private key
    simple: Warning: Remote connection disconnect. Retrying...
    simple: Warning: Connection reset. Retrying...
    simple: Warning: Remote connection disconnect. Retrying...
    simple:
    simple: Vagrant insecure key detected. Vagrant will automatically replace
    simple: this with a newly generated keypair for better security.
    simple:
    simple: Inserting generated public key within guest...
    simple: Removing insecure key from the guest if it's present...
    simple: Key inserted! Disconnecting and reconnecting using new SSH key...
==> simple: Machine booted and ready!
==> simple: Checking for guest additions in VM...
    simple: The guest additions on this VM do not match the installed version of
    simple: VirtualBox! In most cases this is fine, but in rare cases it can
    simple: prevent things such as shared folders from working properly. If you see
    simple: shared folder errors, please make sure the guest additions within the
    simple: virtual machine match the version of VirtualBox you have installed on
    simple: your host and reload your VM.
    simple:
    simple: Guest Additions Version: 4.3.30
    simple: VirtualBox Version: 5.1
==> simple: Setting hostname...
==> simple: Configuring and enabling network interfaces...
==> simple: Mounting shared folders...
    simple: /vagrant => /Users/sevengreenz/dev/ansible-playbook
==> simple: Running provisioner: ansible_local...
    simple: Installing Ansible...
    simple: Running ansible-playbook...

PLAY [simple] ******************************************************************

TASK [Gathering Facts] *********************************************************
ok: [simple]

TASK [install IUS repository] **************************************************
changed: [simple]

TASK [install git] *************************************************************
changed: [simple]

TASK [command] *****************************************************************
changed: [simple]

TASK [debug] *******************************************************************
ok: [simple] => {
    "changed": false,
    "result": {
        "changed": true,
        "cmd": "git --version",
        "delta": "0:00:00.008418",
        "end": "2017-05-05 16:58:27.387269",
        "rc": 0,
        "start": "2017-05-05 16:58:27.378851",
        "stderr": "",
        "stderr_lines": [],
        "stdout": "git version 2.12.2",
        "stdout_lines": [
            "git version 2.12.2"
        ]
    }
}

PLAY RECAP *********************************************************************
simple                     : ok=5    changed=3    unreachable=0    failed=0

上に書いた通りですが下記にあげています。
https://github.com/sevengreenz/ansible-php/tree/v0.1.1

memo

playbook.yml の検証

プロビジョニングはゲスト側にインストールした Ansible を使用しているのでホスト側にはいらないのですが、playbook の検証のためにいれました。

$ sudo easy_install pip
$ sudo pip install ansible

# playbook.yml 読み込みにシンタックスエラーが発生しないか検証
$ ansible-playbook -i /dev/null playbook.yml --syntax-check

-iオプションはインベントリファイル(プロビジョニング先の設定)の指定ですが、Vagrant 側でやっているのでインベントリファイルはありません。指定しないと怒られるので /dev/nullにしています。

モジュールについて

Ansible はモジュールと呼ばれるものを提供していて、リソースの管理やコマンドの実行を行えるようです。 今回だと playbook.yml の yum_repojitory と yum がモジュールに当たります。 Ansible で何かする時はモジュール一覧を見て提供されていないか確認するのが良さそうです。使用例も結構あって使い方も乗ってます。

VirtualBox + Vagrant で CentOS 環境構築[macOS]

macOS 上に VirtualBoxVagrant を使った仮装環境( CentOS )の構築方法です。

環境

macOS Sierra(version 10.12.3)

VirtualBox

VirtualBox は仮想化ソフトです。今回の場合だと Mac OS 上で Linux OS を動かせるようになります。
下記からダウンロードします。
http://www.oracle.com/technetwork/server-storage/virtualbox/downloads/index.html?ssSourceSiteId=otnjp

Vagrant

Vagrant を使うと仮装環境( 今回の場合だと VirtualBox )上に簡単な設定で複製可能な環境を構築、配布できるようになります。
下記からダウンロードします。
https://www.vagrantup.com/downloads.html

準備

# 何らかの version が表示されれば OK
$ VBoxManage -v
5.1.22r115126
$ vagrant -v
Vagrant 1.9.4

仮装環境起動

# ベースとなるイメージファイルをダウンロード ( http://www.vagrantbox.es/ から使いたい box を選択 )
# ベースとなるイメージファイルは vagrant ユーザー作成、sshd 起動など必要最低限のものだけ設定されているようです。
$ vagrant box add centos7.2 https://github.com/CommanderK5/packer-centos-template/releases/download/0.7.2/vagrant-centos-7.2.box
# カレントディレクトリに Vagrantfile を作成
$ vagrant init centos7.2
# 起動
$ vagrant up
# ssh ログイン
$ vagrant ssh
Last login: Tue May  2 10:09:20 2017 from 10.0.2.2
[vagrant@localhost ~]$

後片付け

# Vagrant 管理している仮装環境の確認
$ vagrant status
Current machine states:

default                   running (virtualbox)    // 起動中

# 仮装環境破棄
$ vagrant destory
$ vagrant status
Current machine states:

default                   not created (virtualbox)    // 仮装環境無し

発生したエラー

ヘルプが見れない

$ vagrant -h
/opt/vagrant/embedded/lib/ruby/2.2.0/rubygems/core_ext/kernel_require.rb:54:in `require': cannot load such file -- vagrant-share/helper/api (LoadError)


https://github.com/mitchellh/vagrant/issues/8519通りに下記でとりあえず解決
vagrant-share は別端末からインターネットで仮装環境にアクセスするためのプラグインのようです。何で解決するんだろ。。

$ vagrant plugin install vagrant-share --plugin-version 1.1.8