このエントリーをはてなブックマークに追加

CentOS 7.x で最新の Node.js をインストールする方法

CentOS 7.x 公式で提供している Node.js のバージョンは、現時点で 0.10.42 とかなり古いバージョンになります。
現在の Node.js の最新の安定版は 6.x ですから、こちらをインストールしてみたいところです。

もちろん、普通にコンパイルしてインストールする方法があるのですが、コンパイルはせずにパッケージなどでさくっと導入したいと思って、その方法を調べてみました。すると、2つの方法がありそうです。

1. n package を使ってインストールする方法
この方法では、既存の node.js npm から n というパッケージを導入して、n パッケージ経由で node.js を複数バージョンできるものです。今回は、複数バージョンではなく、単に安定版だけをインストールしたかったので、この方法は見送りました。

2. NodeSource が提供しているパッケージを使う
NodeSource から、deb、yum、向けに各バージョンの node.js パッケージが提供されています。
CentOS 7 もサポートされているため、今回はこちらの方法で Node.js を差し替えてみました。

方法は、次のとおりです。

1. 既存の node.js を削除します

$ sudo yum remove -y nodejs npm

2. NodeSource の yum リポジトリを追加します

# curl -sL https://rpm.nodesource.com/setup_6.x | bash -
あるいは直接パッケージをインストールします
# rpm -i https://rpm.nodesource.com/pub_6.x/el/7/x86_64/nodesource-release-el7-1.noarch.rpm

3. node.js をインストールします

$ sudo yum install -y nodejs
※自動的に、対応している npm などもインストールされます
$ node -v
v6.4.0

ということで、手軽に CentOS 7.x に最新安定版の Node.js をインストールすることができました。
Node.js のバージョンアップ追従もパッケージ側の方もちゃんと行っているため、とても便利でした。

このエントリーをはてなブックマークに追加

[macOS] git-secret が動かなかった

ご存じ git-secret とても便利ですが、ふとちゃんと使っていたところ、git commit 時に、次のように失敗していました。


$ git commit -m "foo" foo.txt
[ERROR] Matched one or more prohibited patterns

Possible mitigations:
– Mark false positives as allowed using: git config –add secrets.allowed …
– Mark false positives as allowed by adding regular expressions to .gitallowed at repository’s root directory
– List your configured patterns: git config –get-all secrets.patterns
– List your configured allowed patterns: git config –get-all secrets.allowed
– List your configured allowed patterns in .gitallowed at repository’s root directory
– Use –no-verify if this is a one-time false positive

よくよく原因を調べてみると、git-secret の commit_msg_hook フックの grep コマンドに失敗しているのが分かりました。
macOS の grep は、BSD 由来の grep だったことが原因でした。


/usr/bin/grep --version
grep (BSD grep) 2.5.1-FreeBSD
83242 (prj/github/github-practic

GNU grep で切り替えて、無事対処しました。いちお、issue をこっそり本家に投稿しておきました。
alias grep=ggrep だとうまくいなかったので、思い切って gnu grep に入れ替えてみました。


$ brew tap homebrew/dupes
$ brew install grep --with-default-names

これで無事 git-secret が動作しているので、クラウド破産の危機が減ったかと思います。

あと、この情報を参考に、ついでにいくつか GNU のものにまとめて差し替えてみました。BSD 系のものを使うときには /usr/bin/ を指定すればよいので忘れないでおきます。

このエントリーをはてなブックマークに追加

EMR 上の Hadoop / Spark の Web UI にアクセスする方法

プライベートサブネット上に EMR 上の Hadoop / Spark の Web UI をインターネット経由でアクセスしたくなりました。

ちゃんとした方法ですと、EMR上でSparkのWeb UIにアクセスする方法 | Developers.IOにあるとおり、次の2つ選択肢があるようです。

  1. SSH トンネリング
  2. SSH トンネリング + SOCK プロキシ

どちらも SSH を利用するため、外部からは接続はできません。

あとは、AWS VPC VPN 接続を使ってプライベートサブネットに接続する選択肢が考えられますが、この場合拠点に VPN が必要です。

そのような環境はなかったので、同一プライベートサブネット上の Nginx からプロキシする方法で設定してみました。

まず試してみると、ほとんどプライベート DNS になっているため、Nginx プロキシでレスポンスボディを変更する必要がありました。

レスポンスボディを書き換えることができるモジュールを探してみたところ、openresty/replace-filter-nginx-moduleyaoweibin/ngx_http_substituions_filter_module がありました。

どちらのモジュールも試したところ、前者の replace-filter-nginx-module は複数箇所の書き換えがなぜかうまくできず、結果的には後者の  ngx_http_substitutions_filter_module を使うことになりました。

Hadoop UI を /cluster、Spark UI を /spark、としていますが、ちょっと長くなりますが、次のようにかなり無理矢理ですが、設定することで無事うまくいきました。
なお、proxy まわりの設定は必要最低限の設定にしてあります。

    # Ganglia
    location /ganglia {
        proxy_pass http://EMR Master Public DNS:80/ganglia;
        proxy_redirect off;

        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }

    # Hadoop UI
    location /cluster {
        resolver [Private DNS ※Ansible でいうと ansible_dns.nameservers[0] とかになりますね] valid=60s;
        set $backend "";
        set $my_host "";
        set $my_port "";

        if ($request_uri ~ /cluster/_/(.+)$) {
            set $backend $1;
        }

        if ($request_uri ~* ^/cluster/_/(.+?):(\d+)\/.+$) {
            set $my_host $1;
            set $my_port $2;
        }

        if ($backend != "") {
            proxy_pass http://$backend;
            break;
        }

        if ($request_uri ~* /cluster/static/(.+)$) {
            proxy_pass http://EMR Master Public DNS:8088/static/$1;
            break;
        }

        # この設定が、レスポンスボディを変更する設定です
        # Then の書き換えは必要ないですが、見栄え的に修正しました
        subs_filter "Then, " ". Then, " r;
        subs_filter "href=\"/static" "href=\"/cluster/static" r;
        subs_filter "src=\"/static" "src=\"/cluster/static" r;
        subs_filter "<a href=\"http://(.+):8088\"" "<a href=\"/cluster\"" r;
        subs_filter "<a href='http://(.+):8088/(.+)'" "<a href='/$2'" r;
        subs_filter "<a href=\"http://(.+):8088/cluster/(.+)\"" "<a href=\"/cluster/_/$1:8088/cluster/$2\"" r;

        subs_filter "<a href=\"//(.+):8042\"" "<a href=\"/cluster/_/$1:8042/node\"" r;
        subs_filter ",\"<a href='http://(.+):8042'" ",\"<a href='/cluster/_/$1:8042/node'" r;
        subs_filter ",\"<a href='http://(.+):8042/(.+)'" ",\"<a href='/cluster/_/$1:8042/$2'" r;
        subs_filter "http-equiv=\"refresh\" content=\"1; url=http://(.*)\"" "http-equiv=\"refresh\" content=\"1; url=https://$host/cluster/_/$1\"" r;

        subs_filter "<a href='http://.+:20888/(.+)'" "<a href='/spark'" r;
        subs_filter "<a href=\"http://.+:20888/(.+)\"" "<a href=\"/spark\"" r;

        subs_filter "href=\"/node\/node" "href=\"$request_uri" r;
        subs_filter "href=\"/node\/allApplications" "href=\"$request_uri/allApplications" r;
        subs_filter "href=\"/node\/allContainers" "href=\"$request_uri/allContainers" r;

        subs_filter "href=\"/jobhistory/(.+)" "href=\"/cluster/_/$my_host:$my_port/jobhistory/$1" r;
        subs_filter "href=\"/(conf)" "href=\"/cluster/_/$my_host:$my_port/$1" r;
        subs_filter "href=\"/(logs)" "href=\"/cluster/_/$my_host:$my_port/$1" r;
        subs_filter "href=\"/(stacks)" "href=\"/cluster/_/$my_host:$my_port/$1" r;
        subs_filter "href=\"/(jmx)(.+)" "href=\"/cluster/_/$my_host:$my_port/$1$2" r;

        proxy_pass http://EMR Master Public DNS:8088/cluster;
        proxy_redirect off;
     }

    location /spark {
        resolver [Private DNS ※Ansible でいうと ansible_dns.nameservers[0] とかになりますね] valid=60s;
        set $backend "";
        set $my_host "";
        set $my_port "";

        if ($request_uri ~ /spark/_/(.+)$) {
            set $backend $1;
         }

        if ($request_uri ~* ^/spark/_/(.+?):(\d+)\/.+$) {
            set $my_host $1;
            set $my_port $2;
        }

        if ($backend != "") {
            proxy_pass http://$backend;
            break;
        }

        if ($request_uri ~* /spark/static/(.+)$) {
            proxy_pass http://EMR Master Public DNS:18080/static/$1;
            break;
        }

        if ($request_uri ~* /spark/history/(.+)$) {
            proxy_pass http://EMR Master Public DNS:18080/history/$1;
            break;
        }

        # この設定が、レスポンスボディを変更する設定です
        subs_filter "href=\"/\"" "href=\"/spark\"" r;
        subs_filter "href=\"/static" "href=\"/spark/static" r;
        subs_filter "src=\"/static" "src=\"/spark/static" r;
        subs_filter "href=\"/history/application_(\w+)/(\d+)\"" "href=\"/spark/history/application_$1/$2/jobs/\"" r;
        subs_filter "?id=" "/?id=";
        subs_filter "href=\"/\?page(.+?)\"" "href=\"/spark/?page$1\"" r;

        subs_filter "<a href=\"http://(.+):8042/(.+)\"" "<a href=\"/cluster/_/$1:8042/$2\"" r;

        proxy_set_header Accept-Encoding "";

       proxy_pass http://EMR Master Public DNS:18080/spark;
       proxy_redirect off;
}

このようにして頑張って書き換えてプロキシさせてあげると、Hadoop/Spark UI をウェブ経由で手軽に見ることができます。

もちろん、上記にあげたポートは Security Group で許可してする必要があります。node も含めると、具体的には 80, 8041, 8042, 8088, 18080, 19888, 20888, ポートの接続許可が必要です。
は、EMR のクラスターごとの固有設定になりますので、僕はプライベート DNS を Route 53 に登録して、その内部ドメインを参照しています。

なお、この方法ですと、EMR がバージョンアップしたときにはその都度 UI に変更があれば subs_filter の変更が必要になります。

上の設定は EMR Release 4.6.0 Hadoop 2.72. + Spark 1.6.1 の環境で動作確認しています。

# 6/16 追記

一部 /spark の書き換えルールを修正しました。

この設定で問題がないと思ったんですが、Spark History Server どうも同じ URL なのにも関わらず初回は 302 を返すため、http 経由でもアクセス可能として、次のような設定を入れる必要がありました。
具体的には、 “https:// server_name/spark/history/application_1464686385340_0492/1/jobs/” にアクセスすると、”http://server_name/history/application_1464686385340_0492/1/jobs/” に 302 リダイレクトされます。。。

location /history {
    rewrite ^/(.*) https://server_name/spark/$1 redirect;
}
このエントリーをはてなブックマークに追加

td-agent のカスタムパッケージを作った

td-agent とても便利ですよね、fluentd をインストールするのに、もっとも手軽な方法だと思って、いつも愛用しています。

さて、fluentd のプラグインが増えていくると、特に複数台のサーバに導入するとき、けっこうな時間がかかって来ました。
せっかく、omnibus-td-agent が公開されているので、これをカスタマイズしてみました。

このカスタマイズでの、公式 omnibus-td-agent からの差分はこちらになりますが、おもに次のようなカスタマイズを行っています。

  • specific_install のインストール(たまに検証用に使ったり、rubygem にないものをどうしても使いたいとき)
  • fleuntd バージョンの固定(現行バージョンは 0.12.22  なので少し古いので、あとで更新しておきたいと思います)
  • 次のプラグインたちの同梱
    • dstat
    • datadog_event
    • dogstatsd
    • elasticsearch
    • file-alternative
    • filter_typecast
    • flowcounter
    • forest
    • grep
    • multiprocess
    • record-modifier
    • record-reformer
    • sampling-filter
    • typecast
    • map(公式では label 対応していないため、独自に拡張したものを利用)
    • dogapi gem

この変更で独自に拡張したい場合は、plugin_gems.rb に追加したいプラグインを設定することで、それらのプラグインを含めたパッケージを作ることができます。

あとは、Vagrant 上でビルドして、自身の Yum リポジトリに追加して、インストールするだけです。

この変更で、自分が使っているプラグインを含めた td-agent の配布がとても簡単になりました。

このエントリーをはてなブックマークに追加

GPG キーの移動方法

少し前に GitHub で GPG 署名がサポートされたため、さっそく使っているのですが、他のマシンに GPG キーを移動する方法を調べてみました。

OS X の場合は、brew install gpp2 して、gpg2 コマンドを使ったほうがよいわけですが、次のようになります。

移行元のマシンで実行

$ email=< メールアドレス >
$ name=< ファイル名 >
$ gpg2 -a --export $email > $name-public-gpg.key
$ gpg2 -a --export-secret-keys $email > $name-secret-gpg.key
$ gpg2 --export-ownertrust > $name-ownertrust-gpg.txt

移行先のマシンで実行

$ email=< メールアドレス >
$ name=< ファイル名 >
$ gpg2 --import $name-secret-gpg.key
$ gpg2 --import-ownertrust $name-ownertrust-gpg.txt
$ gpg2 --list-keys
...

こんな感じであっさりとできました。

あと、都度リポジトリで GPG の設定をするのはめんどくさいので、次のようなシェルスクリプトを書いて使っています。


#!/bin/sh

email=$1
if [ -z "$email" ]; then
email=$(git config user.email)
fi

gpg=gpg
if [ $(which gpg2) ]; then
gpg=gpg2
fi

gpg_pub_key=$($gpg --list-keys | grep -1 "< $email>"| grep -e "^pub")
gpg_pub_key_id=$(echo $gpg_pub_key | cut -d ' ' -f 2 | cut -d '/' -f 2)
if [ -z "$gpg_pub_key_id" ]; then
echo "Could not get gpg pub key - $gpg_pub_key_id"
exit 1
fi

git config --local gpg.program $gpg
git config --local user.signingkey $ggp_pub_key_id
git config --local commit.gpgsign true

このスクリプトでメールアドレスを省略された場合は、リポジトリ設定のメールアドレスを利用しているのでけっこう便利です。

コミット履歴に「Verified」と標示されるのは、少しかっこいいですよね!

このエントリーをはてなブックマークに追加

Packer で AMI を作るときにタイムアウトした

Packer で AMI を作るとき、Provisors で時間がかかってくるようになると、次のようなエラーメッセージが表示されて終了することがあります。
今のところ、Ansible + Serverspec を実行しているため、かなり時間がかかってきたようです。。。


...
2016/05/11 07:52:03 packer: 2016/05/11 07:52:03 Allowing 300s to complete (change with AWS_TIMEOUT_SECONDS)
2016/05/11 07:52:24 ui: ==> amazon-ebs: Creating the AMI: foo-2016-05-10T22-45-36Z
2016/05/11 07:52:25 ui: amazon-ebs: AMI: ami-xxx
2016/05/11 07:52:25 ui: ==> amazon-ebs: Waiting for AMI to become ready...
2016/05/11 07:52:25 packer: 2016/05/11 07:52:25 Waiting for state to become: available
2016/05/11 07:52:25 packer: 2016/05/11 07:52:25 Allowing 300s to complete (change with AWS_TIMEOUT_SECONDS)
2016/05/11 07:54:39 ui: ==> amazon-ebs: Modifying attributes on AMI (ami-xxx)...
2016/05/11 07:54:39 ui: amazon-ebs: Modifying: description
...

こんなときは、メッセージのとおり AWS_TIMEOUT_SECONDS 環境変数を設定してから実行するとよくなりました。
また、標準出力に表示されるログがかなり長くなってきたので、ログファイルに出力するようにしました。

今のところ、次のような環境変数を設定しています。
$packer_name というのは AMI 名、$packer_role は AMI のロール名になります。
今のところ、$packer_name の $packer_role ごとに AMI を作っています。


$ export PACKER_LOG=1
$ export PACKER_LOG_PATH="packer-$packer_name-$packer_role-$(date +%Y%m%d%H%M%S).log"
$ export AWS_TIMEOUT_SECONDS=600

Packer でビルドしているとき、途中で終了しても気づけないことがあるので、ログファイルはちゃんとみた方がよいと思います。
CI などで Packer をビルドしているときは、CI のログを見るのが良いかなと思います。

このエントリーをはてなブックマークに追加

Ubuntu 14.04 LTS から 16.04 LTS へのアップグレード方法

待望の Ubuntu 16.04 LSTS がリリースされましたね!
さっそく自サーバの Ubuntu 14.04 LTS Server をアップグレードしてみました。

Digital Ocean に詳しい解説がのっていますが、正式には 16.04.01 でアップグレードが正式に対応になることですので、アップグレードする際には自己責任でお願いします。

まずは、現行のものを最新にアップグレードして、必要なら再起動をしておきます。


$ sudo apt-get update
$ sudp apt-get upgrade

あとは、いつもどおり do-release-upgrade でアップグレードするだけです。ただし、現時点では -d オプションをつける必要があります。


$ sudo apt-get install update-manager-core
$ sudo do-release-upgrade -d

僕の環境では無事アップグレードできたのですが、SSH 接続すると、次のようなエラーになってしまいました。


Unable to negotiate with x.x.x.x: no matching key exchange method found. Their offer: curve25519-sha256@libssh.org,ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521,diffie-hellman-group-exchange-sha256,diffie-hellman-group14-sha1

これは、~/.ssh/config に、次のような設定が入っていたからです。


KexAlgorithms diffie-hellman-group1-sha1

これを次のように修正しましたところ、接続はできました。


KexAlgorithms curve25519-sha256@libssh.org,ecdh-sha2-nistp521,ecdh-sha2-nistp384,ecdh-sha2-nistp256,diffie-hellman-group-exchange-sha256

あとは、16.04 LTS の新機能をチェックしながら、Ubuntu 16.04 LTS を楽しみたいと思います。

また、まだ EC2 上には公式のイメージは提供されていないようですが、楽しみに待っておきたいと思います。

このエントリーをはてなブックマークに追加

Ansible on CentOS 7 ですこしハマった

CentOS 7 上で Ansible 2 系を使っていますが、EPEL にある Ansible パッケージが、現時点で最新の 2.0.2.0 にバージョンアップされていました。
さっそくこのバージョンを使ったところ、次のような JSON をパースするような処理がある場合は、エラーとなってしまいました。


- set_fact: foo_aws_access_key_id="{{ cloudformation_outputs | selectattr('OutputKey', 'equalto', 'FooKey') | map(attribute='OutputValue') | join(',') }}"

これは、取得した CloudFormation の JSON データの OUTPUTS から特にキーの値を取得するものですが、これを実行すると、次のようなエラーになります。


An exception occurred during task execution. To see the full traceback, use -vvv. The error was: TemplateRuntimeError: no test named 'equalto'
fatal: [foo]: FAILED! => {"failed": true, "msg": "Unexpected failure during module execution.", "stdout": ""}

原因を調査したところ、どうも Ansible が使っているテンプレートエンジン Jinja2 が 2.7 系のままだったことが原因みたいです。
たしかに yum 経由でインストールされる Jinja2 が 2.7 系のままでした。


sudo yum info python-jinja2.noarch
読み込んだプラグイン:fastestmirror
Loading mirror speeds from cached hostfile
* base: ftp.osuosl.org
* epel: mirror.symnds.com
* extras: mirror.confluxtech.com
* updates: mirrors.tripadvisor.com
インストール済みパッケージ
名前 : python-jinja2
アーキテクチャー : noarch
バージョン : 2.7.2
リリース : 2.el7
容量 : 3.0 M
リポジトリー : installed
提供元リポジトリー : base
要約 : General purpose template engine
URL : http://jinja.pocoo.org/
ライセンス : BSD
説明 : Jinja2 is a template engine written in pure Python. It provides a
: Django inspired non-XML syntax but supports inline expressions and an
: optional sandboxed environment.
:
: If you have any exposure to other text-based template languages, such
: as Smarty or Django, you should feel right at home with Jinja2. It's
: both designer and developer friendly by sticking to Python's
: principles and adding functionality useful for templating
: environments

ということで、あまりやりたくはないですが、仕方がなく pip コマンドでアップグレードするようにしました。
けっこう Ansible も Jinaja もリリースされるバージョンに問題があることが多いのでバージョンを固定するようにしました。


$ sudo pip install --upgrade Jinja2==2.8

これで、上のような Playobook もちゃんと通るようになりました。

このエントリーをはてなブックマークに追加

はじめての Gatling

Gatling を、とある環境のベンチマークに使ってみたので、入門向けとして「はじめての Gatling」としてまとめてみました。

Gatling は、Scala でベンチマークのシナリオを書くけれど、Scala 力が低いても、かなり手軽にシナリオを記述できるところが便利ですね。

このエントリーをはてなブックマークに追加

はじめての Datadog

本番環境に Datadog を導入し始めていますが、Datadog はじめての方の向けに簡単にスライドとして作成してみました。
Datadog に関するカジュアル会があれば、そこで発表したい内容でしたが、スライドを公開しておきます。

今回は、「はじめての Datadog」ということで Datadog の入門編として作成しましたが、応用編も本番環境へ導入して運用している中で知見がたまったところで公開する予定です。

Datadog すこしクセがあるところもありますけれど、とても便利なモニタリングサービスだと思います。