mackerelでhubotを監視する

raspberry piでhubotを運用していると、いつの間にかhubotが突然死していることがあります。
botが動いてくれないと色々と困るので、先日導入したmackerelで監視して、死亡したらslackにアラートが飛ぶようにします。

観測用のスクリプトを作成

mackerelでカスタムな値を監視するには、自分で監視用のスクリプトを用意する必要があります。

#!/bin/bash

name="process.count.hubot"
monitor_time=`date +%s`
count=`ps aux | grep node | grep hubot | grep -v grep | wc -l`
echo -e "${name}\t${count}\t${monitor_time}"

hubotが生きている = hubotのプロセスが存在する。
と定義して、hubotのプロセス数をmackerelに監視させます。
細かな仕様は以下を参照してください。

ホストのカスタムメトリックを投稿する - Mackerel ヘルプ

mackerel-agent.confに設定

先ほど作成したスクリプトをmackerelに読み込ませます。
mackerel-agent.confに以下のように追記します。

[plugin.metrics.process]
command="/hoge/huga/process.sh"
type="metric"

Agentの再起動

変更を反映させるために再起動します。

$ sudo service mackerel-agent start

以上でhubotの監視は完成です。
あとは、mackerelでアラートと通知先の設定をするだけです。

Raspberry PI 2にLet's Encryptとnginxでhttps環境を構築

Let's Encryptを使ってhttps環境を構築してみたいと思います。

最新のnginxをインストール

せっかくなので、最新のnginxをインストールしてhttp2にも対応させたいと思います。
基本的には以下の記事を見ればできます。

qiita.com

私の環境では、nginxの設定ファイルは/etc/nginx/conf.d/*****.confとしました。
以下では、この記事ではあまり触れられていないLet's Encryptのセットアップを中心に書いていきます。

Let's Encryptのインストール

まずは、Let's EncryptをGitHubからクローンしてきます。

git clone https://github.com/letsencrypt/letsencrypt
cd letsencrypt

証明書の発行

上記の記事ですでにnginxのセットアップは完了しているので、--webrootオプションを使って
サーバーを起動したまま取得します。

./letsencrypt-auto certonly --webroot -d ${DOMAIN} --webroot-path ${WEBROOT}

-dオプションをつけるとマルチドメイン証明書となるので、複数ドメインを指定できます。
${WEBROOT}にはnginxで指定したドキュメントルートを指定します。
成功したら、以下のような出力があります。

IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at
   /etc/letsencrypt/live/******.**/fullchain.pem. Your cert
   will expire on 2016-**-**. To obtain a new version of the
   certificate in the future, simply run Let's Encrypt again.
 - If like Let's Encrypt, please consider supporting our work by:

   Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
   Donating to EFF:                    https://eff.org/donate-le

この後、nginx -s reloadでnginxを再起動したらhttpsができるようになっているはずです。

以上で完成です。
ずいぶんお手軽にhttpsが利用できるようになりました。
これを機に個人のwebサーバーもhttpsで運用していきたいところです。

Raspberry PI 2にMackerelを導入して監視してみる

サーバーを監視するツールにMackerelというものがあるらしく、
勉強がてら家で持て余してるRaspberry PI 2に導入してみます。

今回はインストールして、最低限の監視ができるところまでやります。

インストール

Raspberry PI 2で動かすためにはARM版のバイナリが必要になります。
最新版をインストールするために、GitHubにあるリリースページからURLを確認して、wgetでダウンロードします。

wget https://github.com/mackerelio/mackerel-agent/releases/download/v0.30.2/mackerel-agent_linux_arm.tar.gz

次に、展開して/opt下に移動させます。

tar xzf mackerel-agent_linux_arm.tar.gz
sudo mv mackerel-agent_linux_arm /opt/mackerel-agent

これでインストールは完了です。

設定

APIキーの設定をします。
APIキーは以下から確認できます。

https://mackerel.io/my#apikey

確認したAPIキーをmackerel-agent.confに追記します。

apikey = "<YOUR_API_KEY>"

起動

起動したら自動的にデータの収集がはじまります。
バックグラウンドで起動したほうがなにかと便利なのでnohupを使って起動します。

nohup ./mackerel-agent --conf=./mackerel-agent.conf &

以上で終わりです。

ここからアラートを設定して運用していきます。
公式が日本語なので、この後はスムーズにできると思います。
デフォルトで用意されている測定項目は最低限のものだけなので、カスタムした内容を測定する方法を次回紹介します。

さくらVPSにdocker-composeでテスト環境を作る

OSは標準のCentOS6.7を使いました。
毎度おきまりの自分用メモに近くなっています。

OSのインストール

契約しただけではOSすら入っていないのでインストールします。
公式のGUIをぽちぽちしていくだけです。

セキュリティの初期設定

http://qiita.com/yu_0105/items/b7cce7a504eed4e31c79を参考に設定しました。

鍵認証の説明だけ少なかったので補足します。
鍵はどこで作成しても問題ありません。
ただ、秘密鍵を転送するのはリスクが大きいのでアクセスするマシンでssh-keygenを実行して、公開鍵をサーバーに転送する方法がベストです。

zshとtmuxのインストールは任意で問題ありません。
使えれば便利なのでインストールすることをおすすめします。
最新のtmuxはhttp://dev.cutecolors.jp.net/install-tmux/を参考に、
zshhttp://gitpub.hatenablog.com/entry/2013/07/07/182014を参考にインストールします。

gitのインストール

CentOSのgitは古すぎてzshプラグインとかが動かないので、インストールします。
http://www.task-notes.com/entry/20150622/1434942000を参考にインストールします。

docker関連のツールをインストール

dockerとdocker-composeをインストールします。

dockerのインストール

dockerのインストールにはEPELが必要なのでインストールします。

$ sudo yum -y install http://dl.fedoraproject.org/pub/epel/6/x86_64/epel-release-6-8.noarch.rpm

続いて、dockerをインストールします。

$ sudo yum -y install docker-io
$ sudo service docker start
$ sudo chkconfig docker on

これでインストール完了です。

docker -hなどを実行してみると、
dial unix /var/run/docker.sock: permission deniedのような警告がでると思います。
rootユーザーで実行するか、sudoをつければ問題ありませんが、
この先、何度もdockerコマンドはうつと思うので、一般ユーザーでもでないようにします。
一般ユーザーでもDockerグループに所属させれば、この警告はでなくなるので、

$ usermod -G docker ユーザ名
$ service docker restart

を実行します。

docker-composeをインストール

docker-composeのインストールは簡単です。
以下を実行します。

$ curl -L https://github.com/docker/compose/releases/download/1.1.0/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose
$ chmod +x /usr/local/bin/docker-compose

以上で環境構築は完了です。
あとはcloneするなりして、docker-composeを実行するだけです。

やっぱ、LinuxでDockerを使うのは簡単ですね。

Dockerについての理解を深める 〜その1〜

Dockerは進化が速いので情報が古くなっている場合があります。

いけてる人たちは使っているDockerについて、複数回にわけて理解を深めていきます。
今回はDockerを扱うツールが入ったDocker Toolboxとdocker-machineについて解説していきます。

動作環境

2016/02/13現在で以下のバージョンで動作確認をしています。

$ docker --version
Docker version 1.9.1, build a34a1d5

$ docker-machine --version
docker-machine version 0.5.5, build 02c4254

$ docker-compose --version
docker-compose version 1.5.2, build 7240ff3

Docker Toolbox

Docker Toolbox | Docker

Docker Toolboxとは、VMでDocker用の仮想環境を立ち上げて簡単にDockerを使えるようにしたツールです。

この中には、

  • VirtalBox
  • docker
  • docker-machine
    • docker用の仮想環境を管理するツール
  • docker-compose
    • 複数のdockerコンテナを1つのファイルで管理することができる

などが入っています。

docker-machine

docker-machineで使うコマンドを紹介します。

ls

マシンリストを表示します。

$ docker-machine ls
NAME           ACTIVE   DRIVER       STATE     URL                         SWARM   DOCKER    ERRORS
default        *        virtualbox   Running   tcp://192.168.99.100:2376           v1.10.1

create

dockerマシーンを作成します。

$ docker-machine create --driver virtualbox default
INFO[0000] Creating CA: /Users/akaimo/.docker/machine/certs/ca.pem
INFO[0000] Creating client certificate: /Users/akaimo/.docker/machine/certs/cert.pem
INFO[0002] Downloading boot2docker.iso to /Users/akaimo/.docker/machine/cache/boot2docker.iso...
INFO[0141] Creating SSH key...
INFO[0141] Creating VirtualBox VM...
INFO[0149] Starting VirtualBox VM...
INFO[0150] Waiting for VM to start...
INFO[0182] "default" has been created and is now the active machine.
INFO[0182] To point your Docker client at it, run this in your shell: $(docker-machine env default)

env

dockerマシーンを操作するためには環境変数を設定する必要があります。

$ docker-machine env default
export DOCKER_TLS_VERIFY="1"
export DOCKER_HOST="tcp://192.168.99.100:2376"
export DOCKER_CERT_PATH="/Users/shu/.docker/machine/machines/default"
export DOCKER_MACHINE_NAME="default"
# Run this command to configure your shell:
# eval $(docker-machine env default)

出力されたコマンドを一つずつ実行するのは面倒なので、以下のコマンドを実行します。

$ eval $(docker-machine env default)

stop

dockerマシーンを停止させます。

$ docker-machine stop default
(default) Stopping VM...
Machine "default" was stopped.

start

dockerマシーンを起動します。

$ docker-machine start default
(default) Starting VM...
Machine "default" was started.
Started machines may have new IP addresses. You may need to re-run the `docker-machine env` command.

dockerマシーンのIPが変わっている可能性があるので、環境変数の設定を行う必要があります。

ssh

dockerマシーンにssh接続します。

$ docker-machine ssh default
                        ##         .
                  ## ## ##        ==
               ## ## ## ## ##    ===
           /"""""""""""""""""\___/ ===
      ~~~ {~~ ~~~~ ~~~ ~~~~ ~~~ ~ /  ===- ~~~
           \______ o           __/
             \    \         __/
              \____\_______/
 _                 _   ____     _            _
| |__   ___   ___ | |_|___ \ __| | ___   ___| | _____ _ __
| '_ \ / _ \ / _ \| __| __) / _` |/ _ \ / __| |/ / _ \ '__|
| |_) | (_) | (_) | |_ / __/ (_| | (_) | (__|   <  __/ |
|_.__/ \___/ \___/ \__|_____\__,_|\___/ \___|_|\_\___|_|
Boot2Docker version 1.10.1, build master : b03e158 - Thu Feb 11 22:34:01 UTC 2016
Docker version 1.10.1, build 9e83765
docker@default:~$

rm

dockerマシーンを削除します。

$ docker-machine rm default

【Swift】複数のジェスチャを認識させる方法

UIScrollViewなどにgestureをaddしても通常は認識されません。
理由は簡単で、すでにgestureが登録されているからです。
ドラッグしたらスクロールしますよね? それです。

解決方法

解決するのは簡単です。
複数のジェスチャを認識するようにすればよいだけです。
方法としては、gestureのdelegateを設定し

let myPan = UIPanGestureRecognizer(target: self, action: "panGesture:")
myPan.delegate = self

gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:を実装するだけです。

func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWithGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool {
    return true
}

Swiftで文字数制限をつけた話

iOS側で作品を作ってサーバーに投げるアプリを開発していたときに、
SNSシェア用に作品を一つの画像にしなければいけなくなった。
その時に、「作品のタイトルが長すぎると画像に収まらない」とサーバーサイドで問題が発生した。
この問題を解決するために、フロント側で文字数制限をつけることとなった。

最初にやったこと

textViewのdelegateであるtextView:shouldChangeTextInRange:replacementText:を利用した。
このdelegateは、キーボードをタップしてから実際に表示される前に呼ばれ、boolで表示させるかを管理するものである。
こいつを使い、文字数をオーバーする場合はfalseを返せばOKだと思った。

問題点

  • 文字数をオーバーすると、キーボードが反応しなくなってしまう
  • 日本語のような変換が存在する言語では変換後に文字数制限を行わなければいけない
    • このままでは変換前に弾いてしまう
一つ目の原因

そもそもこのdelegateはキーボードの反応をboolで管理しているものなので、falseを返した時点でキーボードが反応しなくなってしまう。
つまり、文字数をオーバーしたときでもtrueを返さなければならないのだ。

これを解決するには、入力前の文字列を保管しておき、文字数をオーバーする場合は保管しておいた文字列を入れて返してやればいい。

二つ目の原因

日本語などは入力文字を変換した場合、文字数がオーバーしなくなる場合がある。
英語圏ではなにも問題ないが、日本人が利用するためこのままでは不便すぎる。

これを解決するには、変換後(つまり文字の反転が終わってから)の文字数チェックをすればよい。
変換後に呼ばれる(反転しない英語文字では常に呼ばれる)delegateであるtextViewDidChange:を利用する。
呼ばれる順番としてはtextView:shouldChangeTextInRange:replacementText:の次にtextViewDidChange:が呼ばれる。

全ての問題を解決する

全ての問題を解決するために

  • textView:shouldChangeTextInRange:replacementText:では入力文字の保管だけをする
  • textViewDidChange:で文字数のチェックを行い、オーバーする場合は保管してある入力前の文字列をtextViewにセットする。
    • 変換後もオーバーする場合はオーバーした部分は取り除く

以下がこの条件を満たすコードです。

    private let maxLength = 30
    private var previousText = ""
    private var lastReplaceRange: NSRange!
    private var lastReplacementString = ""

    func textView(textView: UITextView, shouldChangeTextInRange range: NSRange, replacementText text: String) -> Bool {
        self.previousText = textView.text
        self.lastReplaceRange = range
        self.lastReplacementString = text
        
        return true
    }

    func textViewDidChange(textView: UITextView) {
        if textView.markedTextRange != nil {
            return
        }
        
        if count(textView.text) > maxLength {
            var offset = maxLength - count(textView.text)
            var replacementString = (lastReplacementString as NSString).substringToIndex(count(lastReplacementString) + offset)
            var text = (previousText as NSString).stringByReplacingCharactersInRange(lastReplaceRange, withString: replacementString)
            var position = textView.positionFromPosition(textView.selectedTextRange!.start, offset: offset)
            var selectedTextRange = textView.textRangeFromPosition(position, toPosition: position)
            
            textView.text = text
            textView.selectedTextRange = selectedTextRange
        }
    }

まとめ

日本語ってめんどくさいね

参考

iOS で文字数制限つきのテキストフィールドをちゃんと作るのは難しいという話 - blog.niw.at