Tech Beans

Web企業で働くエンジニアのBlog


Capistranoでオペレーション自動化をしようとして諦めた

社内のチームで数十台~のサーバーを管理していて、その上でElasticSearchやHadoopを構築している。 これまで、そのクラスタのオペレーションをほぼ手作業なりシェルスクリプトでやっていて、 手順の再利用性もないし何よりめんどくさいので、 何か自動化できるツールを導入したいと思った。

そこで注目したのがCapistranoだった。

Capistranoはデプロイツールであるけれども、シェルコマンドの自動化にも使えるらしい。

qiita.com

以下、実験的にいくつかオペレーションをCapistrano3に移行してみて、結果挫折した話。

なぜCapistranoを選んだか

チームにRubyエンジニアが多く、また構成管理はChefがデファクトになりつつあって、 Rubyで書ける仕組みが欲しかった。

デプロイ作業もrsync上等!でやっていて、将来的にきちんとCI環境を整えたく、 後々デプロイツールとしても利用できるものを使いたかった。

Capistranoの壁

実際にタスクをCapistranoにしてみて、いくつか壁があった。

学習コストが高い

Capistranoは学習コストが高い。 確かにきちんと環境とタスクを切り離して管理したり、HostFilterの仕組みも便利だった。

しかし、仕組みがしっかり作られることによって、逆に学習コストが高くなってしまっている。 Chefと同じくタスクの記述がDSLになっていて、いちいち覚える事が多い。

設定ファイルのローディング順やCapistranoがどうやってタスクを生成しているか理解していないと、

  • setした値がfetchできない
  • setできてるのにタスクに反映されない

みたいなことが頻繁にあり、

「あれ、この記述どこに書けばいいんだっけ」

となることが多かった。

さらに、Capistrano3になってからオプションや記述方法の変更が入っていて、 ネット上のノウハウも2と3が混在してしまっている状況。

別に自分たちがバージョンを上げなければ問題ない話なのだけれど、せっかく積み上げたネット上のノウハウが 陳腐化したりバージョンで分断されたりするのはもったいない。。

チームで使っていくにあたって、ここらへんの学習コストなりツールの更新によるメンテナンスが問題になりそうだなぁとは初期の段階から感じた。

並列志向であること

もともとデプロイツールということを考えると仕方がないところだけど、Capistranoは並列志向だ。 原則、タスクは並列に実行される。

デプロイツールではそれで問題ないというか、そのほうが反映ラグが少なくていいのだろうけど、 サーバーオペレーションだと並列に実行するというシチュエーションはそれほど多くないと思う。

むしろ1台ずつ状況を確認しながら実行したいし、 やりたかったのがESのローリングリスタートだったので、なおさら直列にしか実行してはいけないものだった。

一応Capistranoでも、タスクにin: sequenceとかけばを直列実行できるオプションは用意されている。

でもこいつが曲者。

例えば、あるタスク(parent task)から別のタスク(child task)を呼び出すような場合

role :web, %w{host1, host2}

task :parent do
  on roles(:web), in: :sequence do |host|
    puts 'parent start@%s' % host
    invoke 'child'
    puts 'parent end@%s' % host
  end
end


task :child do
  on roles(:web) do |host|
    puts 'child exec@%s' % host
  end
end


"""
$ cap production parent
parent start@host1,
child exec@host1,
child exec@host2
parent end@host1,
parent start@host2
parent end@host2
"""

host02のchildがparentより先に呼ばれてしまっている。

最初にあれ?っと思ったけど、よくよく調べればinvoke 'child'の時点でhost02も呼ばれてしまうのだった。 これはchildにin: :sequenceを指定しても変わらない。

んー、仕組みがわかれば納得なんだけど、全然直感的じゃないというか、 普通にtask AからBを呼ぶ、みたいなことはやるわけで、それがさくっとできないのはつらい。

capistrano2ではinvoke 'child', hosts=>hostとすれば実現できた。なぜなくなったし。。

カスタマイズしづらい

上の入れ子タスクの問題をなんとか実行時に解決するすべはないかとコードを追ってみたけれど、 Capistranoはタスク設定時にroleやhostを決めてしまっていて、 実行時にset :hosts, XXXでむりやり実行サーバーを限定する、のようなことができなかった。

残念

結論: Capistranoはデプロイツールであってオペレーションツールではない

そもそもCapistranoはデプロイツールなわけで、がんばってオペレーションもこれでできなくはないけど、 そこまでする意味はなかったかなと。。

すでにCapistranoガッツリ使ってるわけでないのであれば、わざわざ学習してまで導入するメリットはなさそう。 もちろんCapistranoのが悪いわけではなく、デプロイツールとしては実績あるいいフレームワークだと思う。

そしてFabricへ

結局、Capistranoは諦めてFabricを導入することにした。

FabricはCapistranoに比べて学習コストが低く、チームで導入しても負担が少ない。 (そもそもほとんどシェルコマンドだし)

薄いフレームワークだけ提供されているので、 コードを読むのも楽だし、独自の処理を差し込むのが簡単だ。

実際、CapistranoにあるHostFilter、RoleFilterの機能をデコレーターを独自に実装して導入している。

今回学んだことは、

  • ツールは本来の目的に使用してこそ力を発揮する
    • いろんなツールが混在するのもよくないが、かといって1つのツールでなんでもやろうとしてはいけない。
  • 学習コストを慎重に検討するべき。まず試すならシンプルなものを。 simple is best

ということ。

しばらくFabricを試してみよう。

GangliaでHDD温度監視

Gangliaにカスタムモジュールを入れてHDDの温度監視をしてみる。

前提
CentOS 7.1
Gangalia 3.7

セキュリティを重視していないので、実験用環境での使用を想定しています

Package インストール

python モジュールで拡張するので、以下のパッケージをインストールする

ganglia-3.7.2-2.el7.x86_64
ganglia-gmetad-3.7.2-2.el7.x86_64
ganglia-web-3.7.1-2.el7.x86_64
ganglia-gmond-python-3.7.2-2.el7.x86_64
ganglia-gmond-3.7.2-2.el7.x86_64

ganglia-gmond-python package

ganglia-gmond-pythonパッケージには以下のファイルが入っている

...
/usr/lib64/ganglia/modpython.so
/etc/ganglia/conf.d/modpython.conf
...

modpython.soはPython拡張を有効にするためのモジュール。 modpython.confの中身は以下のようになっている。

modules {
  module {
    name = "python_module"
    path = "modpython.so"
    params = "/usr/lib64/ganglia/python_modules"
  }
}

include ("/etc/ganglia/conf.d/*.pyconf")
  • modules ... Python拡張モジュールを有効にするための、modpython.soを読み込んでいる。 また、拡張モジュールの配置場所をpathによって設定している。
  • include ... Python拡張モジュール用の設定ファイルであるpyconfファイルを、conf.d以下から一括で読み込むように指定している。

拡張用Pythonモジュールの書き方

上のpython拡張パッケージがインストールされていれば、pythonモジュールは使えるようになっているはず。

あとは拡張用モジュールの用意と、設定ファイルを用意する。

Python モジュール

拡張用のPythonモジュールでは、以下の関数を実装すればよい。

  • def metric_init(params): 初期化のために一度だけ実行される関数
  • def metric_cleanup(): gmondが終了する時に一度だけ呼ばれる関数
  • def metric_handler(name): 監視目的の値を返す関数。名前は自由

詳細は https://github.com/ganglia/monitor-core/wiki/Ganglia-GMond-Python-Modules

コード

例えば、/dev/sdaの温度をsmartctlから取得するコードは以下。

# -*- coding: utf-8 -*-
import os
import re
import random
from subprocess import check_output

NAME_PREFIX = 'disk_temp'

def get_disktemp(name):
    disk = name.replace(NAME_PREFIX + '_', '')
    disk_info = check_output(['sudo', '/sbin/smartctl', '-a', '/dev/%s' % disk])
    temp_line = [line for line in disk_info.split('\n') if line.find('Temp') >= 0][0]
    return float(re.split('[\s\t]+', temp_line)[-1])

def metric_init(params):
    units = 'Celcius'
    disks = ['sda']
    descriptors = [{
        'name': '_'.join([NAME_PREFIX, disk]), # metircs name
        'call_back': get_disktemp,
        'time_max': 60,
        'value_type': 'float',
        'units': units,
        'slope': 'both',
        'format': '%f',
        'description': 'Disk temperature (%s) on disk /dev/%s' % (units, disk),
        'groups': 'disk'
    } for disk in disks]
    return descriptors

def metric_cleanup():
    pass

このファイルをdisktemp.pyとして、/usr/lib64/ganglia/python_modulesに配置する

pyconf 設定ファイル

実装した拡張モジュールで監視を行うための設定を追加する

modules {
  module {
    name = "disktemp" # my python module name
    language = "python"
  }
}

collection_group {
    collect_every  = 180 # metrics span
    time_threshold = 60 # metrics send span

    metric {
        name_match = "disk_temp_(.+)"
    }
}

ここでは取得間隔を180sec、gmetadへの通知間隔を60secにしている。

sudoers設定

gangliaを実行しているユーザーがroot出ない場合(例えばgangliaユーザー)、sudoersの設定が必要

# ttyなしでsudo実行
Defaults:ganglia !requiretty
# /sbin/smartctl 実行許可
Cmnd_Alias LOOK_SMART = /sbin/smartctl -a /dev/*
ganglia  ALL=(ALL) NOPASSWD: LOOK_SMART

結果

gmondを再起動して、ちゃんとメトリクスが収集されているか確認 f:id:soy_msk:20151210003946p:plain

※うまく収集されていない、gmondが起動しない場合、/var/log/messagesを確認する。

github.com

descriptorについて

今回遭遇したトラブル

  1. descriptorのvalue_typeとformatが違っていてgmondが起動しない
    • 公式によると予期せぬ測定値になるとあるが、エラーを吐いて起動しなかった
    • value_type: float, format: %f にちゃんと揃える
  2. value_typeを途中から変更した場合にgmetadがエラー
    • おそらく過去のrrdデータと不整合が起きるためだと思う。 int -> floatは問題ないが、逆だとだめそう。
    • rrdtoolのデータを削除して解決

CentOS7のKernelを最新版に更新する(yumで)

docker1.9のoverlay networkを試すにはLinux kernel3.16が必要だが、CentOS7では3.10までしか提供されていない。 kernelの更新はelrepoを使えば簡単にできるようなので試してみた。

注意: kernelの更新は危険なオペレーションのため、壊れても良い環境で試して下さい。

現在のkernel
3.10.0-229.14.1.el7.x86_64

elrepoの追加

yumのrepolistにelrepoを追加

$ rpm --import https://www.elrepo.org/RPM-GPG-KEY-elrepo.org 
$ rpm -Uvh http://www.elrepo.org/elrepo-release-7.0-2.el7.elrepo.noarch.rpm

elrepo-kernelを有効にする。enabled=0になっているのでenabled=1とする

$ vim /etc/yum.repos.d/elrepo.repo
----
[elrepo-kernel]
name=ELRepo.org Community Enterprise Linux KÂernel Repository - el7
baseurl=http://elrepo.org/linux/kernel/el7/$basearch/
        http://mirrors.coreix.net/elrepo/kernel/el7/$basearch/
        http://jur-linux.org/download/elrepo/kernel/el7/$basearch/
        http://repos.lax-noc.com/elrepo/kernel/el7/$basearch/
        http://mirror.ventraip.net.au/elrepo/kernel/el7/$basearch/
mirrorlist=http://mirrors.elrepo.org/mirrors-elrepo-kernel.el7
enabled=1 <==== 0から1に変更
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-elrepo.org
protect=0

install

# 古いkernelのパッケージを削除
$ yum remove kernel-headers-3.10.0-123.9.2.el7.x86_64 kernel-tools-3.10.0-123.9.2.el7.x86_64 kernel-tools-libs-3.10.0-123.9.2.el7.x86_64

$ yum update
$ yum install kernel-ml.x86_64 kernel-ml-devel.x86_64 kernel-ml-headers.x86_64 kernel-ml-tools.x86_64 kernel-ml-tools-libs.x86_64

grub起動設定変更

$ awk -F\' '$1=="menuentry " {print $2}' /etc/grub2.cfg

---
CentOS Linux 7 (Core), with Linux 4.3.0-1.el7.elrepo.x86_64
CentOS Linux 7 (Core), with Linux 3.10.0-229.20.1.el7.x86_64
CentOS Linux 7 (Core), with Linux 3.10.0-229.14.1.el7.x86_64

更新したいKernel4.3が1行目にあるので

# 起動Kernel
$ grub2-set-default 0
# 設定反映
$ grub2-mkconfig -o /boot/grub2/grub.cfg
# 再起動
$ reboot

再起動後、

$ uname -r
4.3.0-1.el7.elrepo.x86_64

更新されている!

kernel version

以下のページから利用できるrpmを知ることができるが、kernel3はもうないみたい http://elrepo.org/linux/kernel/el7/x86_64/RPMS/

参考

http://wiki.mikejung.biz/CentOS_7#Upgrade_CentOS_7_Kernel_to_4.0.1

RundeckからGmailを使ってメール送信する

Rundeckをジョブスケジューラとして使い始めて、メールをGmail経由で送れるようにした。 OSはCentOS7だが、他でも変わらないはず。

Rundeckのインストール

インストールは難しくない。yumで入るはず。 起動もsystemctl start rundeckd.serviceでできる。

詳細は割愛

設定

※以下、rundeckの設定は/etc/rundeck配下にあるものとしています。

公式ドキュメントには一応設定はある。 http://rundeck.org/docs/administration/email-settings.html localhostsmtpサーバーを立てている場合は

grails.mail.host=localhost
grails.mail.port=25

でいいらしいが、今回はSSL認証が必要はため別の手段が必要。

rundec-config.groovy

まずはより詳細な設定ができるように設定ファイルをGroovy形式のファイルに置き換える。

rundeck-config.propertiesと同じディレクトリにrundeck-config.groovyを作成する。

デフォルトのpropertiesは使わないので適当にリネームしておく。

  $ mv rundeck-config.properties rundeck-config.propertties.bak
  $ touch rundeck-config.groovy

基本設定はrundeck-config.propertiesと同じになるようにする。 それにメール設定を以下のように設定する。

// #### デフォルト設定 ####
loglevel.default="INFO"
rdeck.base="/var/lib/rundeck"
rss.enabled=false
dataSource {
        dbCreate="update"
        url="jdbc:h2:file:/var/lib/rundeck/data/rundeckdb;MVCC=true;TRACE_LEVEL_FILE=4"
}

grails {
        serverURL="http://localhost:4440"
        // #### Mail 設定 ####
        mail {
                host="smtp.gmail.com"
                port = 465
                username="XXXXXX@gmail.com"
                password="XXXXXXX"
                props = ["mail.smtp.auth":"true",
                      "mail.smtp.socketFactory.port":"465",
                      "mail.smtp.socketFactory.class":"javax.net.ssl.SSLSocketFactory",
                      "mail.smtp.socketFactory.fallback":"false"]
        }
}

grails.mail以下に上のようにGmail用の設定を記述すればOK.

起動オプション変更

上記で作成した.groovyファイルを読み込んで起動するように、 CONFIG_DIR/profile ファイルに以下の行を追加

# Add to load custom config
RDECK_JVM="$RDECK_JVM -Drundeck.config.location=/etc/rundeck/rundeck-config.groovy"

systemctl restart rundeckd.serviceでちゃんと再起動するか確認。 エラーが出る場合はgroovyファイルに間違いがないかどうか確認する。

ジョブ作成

メールは送信できるようになっているはずなので、あとはNotificationにメールを設定するだけだ。 f:id:soy_msk:20150917222026p:plain

うーん、それにしてもRundeckが相当DiskIO食ってるんだけどなんでだろう。。IO waitかなり出ている。。

Logictec Bluetooth[LBT-UAN04C1]をWindows10で使う

どこにも情報がなかったのでメモ

Windows7からWindows10にアップグレードしたところLogitecのBluethoothアダプタが使えなくなった。

どうやらドライバがうまく認識されていないらしいが、Logitecでも対応ドライバはないようだ。

要はCSRのドライバを削除してWindowsのデフォルトのドライバを使えばいいが、 そのままではインストールできない。

  1. CSR Harmony Wireless Software Stackの削除
  2. アプリ削除からCSRで検索して上記プログラムを削除
  3. アダプタを抜き差しして標準ドライバがインストールされることを確認

以降普通にBluetoothが使えるので、必要であれば新たにペアリングするなど。

この記事もペアリングしたキーボードでかけている!

centos7にdockerをインストール

手順

Centos7用のrpmを使った手順もある(https://docs.docker.com/installation/centos/)が、 以下の公式ドキュメントの方法を使うほうが楽そう。

http://docs.docker.com/linux/started/

この手順でもrpmがインストールされるので、管理も楽。

# 公式の手順ではsudoはいらないとされているが、実際失敗したのでsudo指定
$ wget -qO- https://get.docker.com/ | sudo sh

無事インストールできたかテスト用のコマンドを実行

$ docker run hello-world  
Post http:///var/run/docker.sock/v1.19/containers/create: dial unix /var/run/docker.sock: permission denied. Are you trying to connect to a TLS-enabled daemon without TLS?

あれ?これもsudoが必要か

$ sudo docker run hello-world
Cannot connect to the Docker daemon. Is 'docker -d' running on this host?

どうやらdocker daemonを起動しないといけないらしい。

$ sudo systemctl start docker

$ sudo docker run hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from hello-world
a8219747be10: Pull complete 
91c95931e552: Already exists 
hello-world:latest: The image you are pulling has been verified. Important: image verification is a tech preview feature and should not be relied on to provide security.
Digest: sha256:aa03e5d0d5553b4c3473e89c8619cf79df368babd18681cf5daeb82aab55838d
Status: Downloaded newer image for hello-world:latest
Hello from Docker.
~~~~~~

成功!

# 再起動後もdockerサービスを有効にする
$ sudo systemctl enable docker

$ docker -$ docker --version
Docker version 1.7.1, build 786b29d

$ rpm -qa docker*
docker-engine-1.7.1-1.el7.centos.x86_64

最新版がインストールされている。

Pythonにおける仮想環境構築ツール

Pythonの仮想環境ツールがいろいろありすぎて混乱するので、整理目的で調査してみる。 ざっと調べた感じでは以下のとおり。

Pyenv

複数バージョンのPythonバイナリを管理するもの。プロジェクト(ディレクトリ)単位での異なるバージョンのPythonが使用できるようになる。 ただし、コレ単体だとバージョンごとのsite-packagesは同じなので、例えばプロジェクトAとプロジェクトBで同じバージョンのPythonを使っていると、ライブラリも共有せざるを得ない。

Virtualenv

Pythonの仮想環境を作成するもの。プロジェクトごとにsite-packagesを分けることができる。Pyenvと違い、コレ単体ではPythonバイナリのバージョンは分けられないが、ライブラリは分けることができる。

pyenv-virtualenv

Pyenvのvirtualenvプラグイン。PyenvでPython本体のバージョン管理をしつつ、Virtualenvでライブラリの管理もできる。だいたいこれが使われているらしい。

Pyvenv

python3.3で追加された。これ自体は単なるvenvのラッパーらしい。venvではPythonバイナリのバージョンとsite-packagesディレクトリの管理ができる。ただし、バイナリの管理はすでにインストールされているバイナリに固定されているだけなので、バイナリのバージョン管理ができると言えるかどうか。。これ単体で使うくらいならpyenv-virtualenvをつかっておいた方がいいかも。ツール自体もPython3に依存しているのでPyvenvを使う場合は、まずはPython3.4を入れる必要がある。

まとめ

とりあえずpyenv-virtualenvをつかっておけば問題なさそう。 Python3.3以降を使う、かつバイナリのバージョンにはそれほど細かい管理は不要というのであれば、Pyvenvもいいかもしれない。

更新(2016/02/04)

記事を書いてからいくつかPythonで開発をしているけど、今のところpyenv-virtualenvでpython2も3も問題なし!