初級情報インフラ管理者の技術メモ

ほぼ自分用の技術メモです。

opscode-cookbooks/vimのコミュニティクックブックでVim 7.4をインストールしようとしてハマったメモ

chefが提供している、vimをインストールするコミュニティクックブックを使って、Vagrant上のCentOS 6.6にchefでVim 7.4をソースからインストールしようとしたらハマってしまったので、メモしときます。

まずBerksfileを次のとおり編集します。

source "https://supermarket.chef.io"
(中略)
cookbook ['vim']

コミュニティクックブックをberks vendorコマンドでインポートします。

$ bundle exec berks vendor ./cookbooks

この状態でvagrant provsionすると、yumvim-enhancedが正常にインストールされます。しかし、CentOS 6のyumでインストールするVimRubyに対応していないので、ソースからコンパイルしてVim 7.4をインストールしようと思いました。

インポートしたコミュニティクックブックのREADME.mdを読むと次のように書いてあります。

If you would rather compile vim from source, as the case may be for centos nodes, then override the node['vim']['install_method'] with a value of 'source'.

そこで./cookbooks/vim/attributes/default.rbを次のように変更します。

(省略)
default['vim']['install_method'] = 'source'

そしてvagrant provisionを実行すると、次のようなエラーが出力されてプロビジョンが失敗します。

==> default: [2014-12-07T03:29:33+09:00] FATAL: Stacktrace dumped to /var/chef/cache/chef-stacktrace.out
==> default: [2014-12-07T03:29:33+09:00] ERROR: remote_file[/var/chef/cache/vim-7.4.tar.bz2] (vim::source line 29) had an error: Chef::Exceptions::ChecksumMismatch: Checksum on resource (607e13) does not match checksum on content (d0f5a6)
==> default: [2014-12-07T03:29:33+09:00] FATAL: Chef::Exceptions::ChildConvergeError: Chef run process exited unsuccessfully (exit code 1)

チェックサムが合わないとのことなので、Vimの配布元でMD5SUMの値を確認します。

607e135c559be642f210094ad023dc65 vim-7.4.tar.bz2

./cookbooks/vim/recipes/source.rbの内容を確認します。

(省略)
remote_file "#{cache_path}/vim-#{source_version}.tar.bz2" do
  source "http://ftp.vim.org/pub/vim/unix/vim-#{source_version}.tar.bz2"
  checksum node['vim']['source']['checksum']
  notifies :run, "bash[install_vim]", :immediately
end
(省略)

このレシピではVimのソースファイルのチェックサムを確認するようになっています。ということはダウンロードしようとするファイルのチェックサムと、レシピに記載したチェックサム値が合っていないからプロビジョンに失敗したと思われます。そこで、./cookbooks/vim/attributes/source.rbの内容と比較してみます。

(省略)

default['vim']['source']['checksum']      = '607e135c559be642f210094ad023dc65'
(省略)

合っている……。

しばらく悩みながら調べてみると、やっと原因が分かりました。

chefのドキュメントを読むと、remote_fileのchecksumはMD5ではなく、sha256で比較しているとのことでした。

checksum Optional, see use_conditional_get. The SHA-256 checksum of the file. Use to prevent the remote_file resource from re-downloading a file. When the local file matches the checksum, the chef-client will not download it.

そりゃエラーになるわけです。Vimの配布元にはsha256の値は公開されていないため、自分で調べる必要があります。

$ wget ftp://ftp.vim.org/pub/vim/unix/vim-7.4.tar.bz2
$ shasum -a 256 vim-7.4.tar.bz2
d0f5a6d2c439f02d97fa21bd9121f4c5abb1f6cd8b5a79d3ca82867495734ade

./cookbooks/vim/attributes/source.rbのdefault['vim']['source']['checksum']の値をsha256の値に変更します。

$ sed -i -e 's/607e135c559be642f210094ad023dc65/d0f5a6d2c439f02d97fa21bd9121f4c5abb1f6cd8b5a79d3ca82867495734ade/' ./cookbooks/vim/attributes/source.rb

これでvagrant provisionが成功するようになります。

しかしなぜchefの公開しているレシピなのにdefault['vim']['source']['checksum']にMD5の値を書いているのだろう。もしかしてMD5でチェックするオプションがあるんでしょうか?

タグが<none>になってしまったdocker imageをまとめて削除するワンライナー

よくDockerfileの中身を精査しないままdocker buildをして失敗する私のような人間は、docker imagesしたらこんな状態になっていると思います。

REPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
takedah             postfix             684aa52fc89e        24 hours ago        515.2 MB
takedah             rails               66bf45ac2ac2        11 weeks ago        1.241 GB
<none>              <none>              c97d7e856595        11 weeks ago        1.233 GB
<none>              <none>              31f14a3de2ae        11 weeks ago        1.233 GB
<none>              <none>              f22584c8eaa8        11 weeks ago        1.233 GB
<none>              <none>              0f843867c4f6        11 weeks ago        1.063 GB

このタグが<none>になっているイメージをまとめて消すワンライナーです(実行は自己責任で!)。

$ sudo docker images | awk '{if ($2 ~ "<none>") print $3}' | xargs sudo docker rmi

docker imagesコマンドの標準出力をawkに渡して、第2フィールド(TAG)が<none>であれば、第3フィールド(IMAGE ID)を出力し、最後にxargsに渡してdocker rmiコマンドの引数にする、という仕掛けです。

って書いて、先日のDocker勉強会@旭川で@smaguchiさんが既にやり方を@SamuraiT01くんのGistに書いていたことに気付いた。

しつこくSMTP Authを試みてくるIP Addressをiptablesでブロックする

インターネットからアクセスできるSMTPサーバを立て、自ドメイン以外のメールリレーを全て拒否した上でSMTP Authを有効にして管理しているのですが、定期的に外部から何度もSMTP Authを試されてしまっています。

/var/log/maillogには次のようなログが繰り返し記録されています。

Sep 17 15:35:32 hoge postfix/smtpd[22321]: connect from fuga.example.com[xxx.xxx.xxx.xxx]
Sep 17 15:35:34 hoge postfix/smtpd[22321]: warning: SASL authentication failure: Password verification failed
Sep 17 15:35:34 hoge postfix/smtpd[22321]: warning: fuga.example.com[xxx.xxx.xxx.xxx]: SASL PLAIN authentic
ation failed: authentication failure
Sep 17 15:35:37 hoge postfix/smtpd[22321]: warning: fuga.example.com[xxx.xxx.xxx.xxx]: SASL LOGIN authentic
ation failed: authentication failure
Sep 17 15:35:37 hoge postfix/smtpd[22321]: disconnect from fuga.example.com[xxx.xxx.xxx.xxx]

自分しか使わないSMTPサーバなので、複数回SMTP Authに失敗しているイコール不正中継の踏み台にしようとしているのは明らかな状況です。

大変気持ちが悪いので、一定回数以上SMTP Authに失敗しているIP Addressを/var/log/maillogから抽出し、当該IP AddressをiptablesでDROPしてしまおうと考えました。

maillogから一定回数以上認証失敗しているIPを抽出するワンライナー

まずは/var/log/maillogから一定回数以上SMTP Authに失敗しているIP Addressを抽出できるか試してみます。

# cat /var/log/maillog | grep 'authentication failed' | egrep -o -e '\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\]' | sed -e 's/^\[\(.*\)\]$/\1/g' | sort | uniq -c | awk '$1 > 5 {print $2}'

上記ワンライナーで、/var/log/maillogから「authentication failed」の行の中からIPv4 Addressを取り出し、重複カウントした上で重複カウント数が5を超えるIPv4 Addressのみを抽出できました。

一応解説します。

egrep -o -e '\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\]' | sed -e 's/^\[\(.*\)\]$/\1/g'

egrepの-oオプションで、パターンにマッチした文字列のみを取り出します。-eオプションでパターンに正規表現を使ってIPv4 Addressっぽい文字列(数字1桁から3桁までの固まりがピリオド区切りで4つ連続している文字列)を取り出しています。

スマートではないのですが、maillogのauthentication failedログの行には[xxx.xxx.xxx.xxx]という形でIP Addressが書かれているため、まずこの部分を抽出してから更にsedにパイプして正規表現[]を取り除いています。ただ、万一[999.256.1.254].netのようなホスト名がログに記録されていればマッチしてしまうので、もう少しちゃんと書いた方がいいかもしれません。

| sort | uniq -c | awk '$1 > 5 {print $2}'

sortした上でuniqに-cオプションを付けて渡せば、重複している行をまとめて重複したカウント数も教えてくれます。次のような出力が返ります。

      2 xxx.xxx.xxx.xxx
     10 yyy.yyy.yyy.yyy
      6 zzz.zzz.zzz.zzz

一行がカウント数+半角スペース+IP Addressという形で返るため、更にこれをawkに渡してやれば、カウント数とIP Addressを分けて変数に格納できるため、最後はawkでカウント数が5以上であればIP Addressを表示する、というスクリプトになっています。

最終的に一行にカウント数5以上のIP Addressという形式の標準出力を得られました。

これを後述するiptablesから読み込むため、標準出力を任意の場所にテキストファイルとして出力しておきます。

# cat /var/log/maillog | grep 'authentication failed' | egrep -o -e '\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\]' | sed -e 's/^\[\(.*\)\]$/\1/g' | sort | uniq -c | awk '$1 > 5 {print $2}' >> /etc/postfix/sasl_failed_ip.txt

iptablesにファイルから読み込んだIP Addressをブロックする処理

以下は完全におまけです。シェルスクリプトに、上記手順で作成したテキストファイルを一行ずつ読み込んで、iptablesコマンドを生成する処理を作成します。

#!/bin/bash
while read LINE
  do
    iptables -A INPUT -s $LINE -j LOG --log-prefix "[Iptables DROP_SASL_FAIL]" || exit 1
    iptables -A INPUT -s $LINE -j DROP
  done < /etc/postfix/sasl_failed_ip.txt

一応ログプレフィクスを付けてログを出力するようにしています。

最終形のシェルスクリプト

一連の処理をシェルスクリプトにまとめるとこんな感じでしょうか。

#!/bin/bash
# Deny SASL Auth failed ip address
cat /var/log/maillog | grep 'authentication failed' | egrep -o -e '\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\]' | sed -e 's/^\[\(.*\)\]$/\1/g' | sort | uniq -c | awk '$1 > 5 {print $2}' >> /etc/postfix/sasl_failed_ip.txt || exit 1
while read LINE
  do
    iptables -A INPUT -s $LINE -j LOG --log-prefix "[Iptables DROP_SASL_FAIL]" || exit 1
    iptables -A INPUT -s $LINE -j DROP
  done < /etc/postfix/sasl_failed_ip.txt
exit $?

注意点は繰り返しになりますが、万一[999.256.1.254].netのようなホスト名だった場合、iptablesの処理で必ずエラーになるので、正規表現を練り直す必要があるかもしれません。

ちなみに私は自分のSMTPサーバにはIPv6でしかアクセスしないため、IPv4SMTP Authに失敗している時点で問答無用で弾いてしまってもいい状況です。

今のところIPv6でこの手の探索行為は受けている形跡はなさそうですが、もしIPv6の対策も考えなければならなくなったとき、正規表現がめんどくさそう……という気がします。

さくらのVPSとRTX1100をopenswanでipv6 over ipv6 ipsecを張ってみる

さくらのVPS上のCentOS 6.5と、自宅のYAMAHA RTX1100との間の通信をipsecで暗号化したいと思い立ちました。

さくらのVPSは固定グローバルipv4アドレスが一つ付与されているため、自宅側にも固定グローバルipv4アドレスがあれば話が早いのですが、あいにくこれがありません。しかし、自宅には半固定ipv6グローバルアドレスが割り当てられる環境になっており、さくらのVPSも固定グローバルipv6アドレス(Global Unicast Address、GUA)が付与されているため、ipv6ipsecを張ることにします。

色々調べた結果、openswanでipv6 ipsecができるようだったので、CentOS 6.5にはopenswanをインストールします。

※個人的に手探り状態で設定したので、正しくない箇所があるかもしれません。もし参考にする方がいれば、十分検証のうえでお願いします。

事前共有キーの設定

openswanで事前共有キー(PSK)方式のipsecを張ります。事前共有キーは十分に複雑で長い文字列を設定しましょう。

RTX1100の設定

WAN側インターフェイスへGUAの付与

自宅はひかり電話ありフレッツ光ネクストに、IIJmio FiberAccess/NFで、IPoE方式でipv6を使える環境です。ひかり電話ありの場合、ひかり電話ルータにRTXを繋げば、DHCPv6-PDでprefix移譲を受けられます。

余談ですがIIJmio FiberAccess/NFの場合、RTXには/60のprefixが移譲されてきたため、64bit-60bit=4bit分、2の4乗分サブネットをRTX1100で切ることができます。うちではRTX1100でVLAN切って各VLANにGUAを付与し、自宅サーバKVMにトランク接続しているので、KVM仮想マシンにはグローバルのipv6サブネットを10以上割り当てられる状態になっています。

あとはGUAをRTXのWAN側インターフェイスに割り当てる設定が必要です。具体的にはYAMAHAのサイトに設定例が出ているので(フレッツ 光ネクストでインターネット接続(IPv6 IPoE)を使用するには、どうすればよいですか? )、この通り設定すれば大丈夫ですが、RTX1100のファームウェアが古い場合、設定例通りにするとひかり電話ルータにRTX1100を繋いだ途端にRTX1100が再起動を繰り返してしまう不具合があるため、必ずRev. 8.03.94以降のファームウェアを適用してから設定しましょう。

LAN側インターフェイスへULA(Unique Local IPv6 Unicast Addresses)の付与

今回ipsecトンネルを通すipv6アドレスは、RTX側もCentOS側も、ipv4でいうところのプライベートIPアドレスであるULAのipv6アドレスにします。

ULAは他のipv6アドレスと重複しないよう、基本的にインターフェイスMacアドレスを基に計算して生成します。ULAの生成Webサービスを使うと楽です(Generate Unique Local Address)。

RTX側、CentOS側のいずれのULAにも外部から到達できませんが、今回RTX側、CentOS側にそれぞれ互いのULAのサブネットに対してipsecトンネル経由でルーティングするよう設定すれば、ULA同士で通信できるという仕組みです。

RTXのLAN側インターフェイスに、生成したULAを付与します。ちなみにRTX1100は一つのインターフェイス複数ipv6アドレスを振れるので、各インターフェイスにはGUAとULAをそれぞれ付与することも可能です。

ipsecの設定

コンフィグの該当部分のみ抜粋します。なお、ip filterを設定する場合は、WAN側インターフェイスでUDP546(DHCPv6-PD)、esp、UDP500(ipsec)辺りのポートが開いている必要があるかと思います。

# 対向(CentOS側)ULAのサブネットへのルーティング追加
ipv6 route (CentOS側ULAサブネット) gateway tunnel 1
# ipsecトンネルの設定
tunnel select 1
 ipsec tunnel 1
  ipsec sa policy 1 1 esp 3des-cbc sha-hmac
  ipsec ike encryption 1 3des-cbc
  ipsec ike esp-encapsulation 1 off
  ipsec ike group 1 modp1024
  ipsec ike hash 1 sha
  ipsec ike keepalive use 1 on dpd
  ipsec ike local address 1 (RTXのGUA)
  ipsec ike log 1 key-info message-info payload-info
  ipsec ike payload type 1 3
  ipsec ike pre-shared-key 1 (事前共有キー)
  ipsec ike remote address 1 (CentOSのGUA)
  ipsec auto refresh 1 on
 ip tunnel tcp mss limit auto
 tunnel enable 1

CentOS 6.5の設定

eth1インターフェイスの追加

CentOSのeth0にグローバルipv6アドレスが付与されているので、eth1を作ってこれにULAを振ることにします。最初eth0にセカンダリipv6アドレスとしてULAを振って試したんですが、うまく行きませんでした。

eth1を追加するため、VPS同士をローカル接続させる仮想スイッチの仕組みを使います。コントロールパネルにて仮想スイッチを作成し、これにeth1を接続するよう設定すれば、さくらのVPS上のCentOSでeth1インターフェイスが活性化しますので、後はこれにULAを設定するだけです。

openswanのインストール

CentOS標準リポジトリからyumでインストールできます。

$ sudo yum install openswan

対向(RTX側ULAのサブネットへのルーティングテーブル追加)

ここが一番嵌りました。ipv6の静的ルーティングテーブル追加はipv4のようにコマンドで設定できません。

/etc/sysconfig/network-scripts/route6-eth1ファイルを作成し、そこに経路情報を記載します。

$ sudo echo '(RTXのULAのサブネット) via (CentOSのULA)' > /etc/sysconfig/network-scripts/route6-eth1

openswanの設定

基本的に以下の3つの設定ファイルの変更が必要です。

/etc/sysctl.conf

openswanを起動して設定を確認すると、カーネルパラメータを修正するようメッセージが表示されます。

$ sudo service ipsec start
$ sudo ipsec verify

そこで、次の通り追記します(たぶんipv6しか使わないなら関係ない設定だとは思います)。

$ sudo echo 'net.ipv4.conf.all.send_redirects = 0' >> /etc/sysctl.conf
$ sudo echo 'net.ipv4.conf.default.send_redirects = 0' >> /etc/sysctl.conf
$ sudo echo 'net.ipv4.conf.eth0.send_redirects = 0' >> /etc/sysctl.conf
$ sudo echo 'net.ipv4.conf.lo.send_redirects = 0' >> /etc/sysctl.conf
$ sudo echo 'net.ipv4.conf.all.accept_redirects = 0' >> /etc/sysctl.conf
$ sudo echo 'net.ipv4.conf.default.accept_redirects = 0' >> /etc/sysctl.conf
$ sudo echo 'net.ipv4.conf.eth0.accept_redirects = 0' >> /etc/sysctl.conf
$ sudo echo 'net.ipv4.conf.lo.accept_redirects = 0' >> /etc/sysctl.conf

また、ipv6パケットのフォワーディングができるように次の通り追記します。

$ sudo echo 'net.ipv6.conf.all.forwarding = 1' >> /etc/sysctl.conf

/etc/ipsec.d/connection.conf

ipsec設定の核の部分です。コンフィグはOpenswan/ipv6.conf.in at master · xelerance/Openswanを参考にしました。

conn rtx1100
        connaddrfamily=ipv6
        type=tunnel
        authby=secret
        auth=esp
        ike=3des-sha1;modp1024
        esp=3des-sha1;modp1024
        keyexchange=ike
        pfs=yes
        left=(CentOSのGUA)
        leftid=(同上)
        leftsubnet=(CentOSのULAのサブネット)
        leftsourceip=(CentOSのULA)
        right=(RTXのGUA)
        rightid=(同上)
        rightsubnet=(RTXのULAのサブネット)
        rightsourceip=(RTXのULA)
        ikelifetime=8h
        keylife=1h
        compress=no
        dpddelay=5
        dpdtimeout=20
        dpdaction=restart
        auto=start

/etc/ipsec.d/rtx1100.secrets

事前共有キーとなる文字列を記載しておくファイルです。

(CentOSのGUA) (RTXのGUA): PSK "(事前共有キー)"

openswanの起動

以上で、openswanを起動するとipv6 over ipv6 ipsecを張ってくれるはずです。

$ sudo service ipsec start

システム起動時に自動実行させておきます。

$ sudo chkconfig ipsec on

余談

RTX1100はipv4 over ipv6ipsecをサポートしているので、頑張ればipv4で通信できると思ったのですが、力及ばず駄目でした。

ipv6 over ipv6でのipsecする需要がどのくらいあるか分かりませんが、私はさくらのVPS上のCentOSでzabbix、rsyslogを動かしているので、これに使う通信経路が暗号化できて満足しています。

ちなみにさくらのVPS側でDNSも動かしているのですが、RTX1100自身からのDNSクエリについて送信元IPを指定できないため(ipv4ならできるのに!)、DNSクエリのパケットがipsecトンネルの方へ向いてくれません。syslogは送信元IPを指定できるのでipsecトンネルを経由してさくらのVPS側へ届くんですけどね……。YAMAHAさんがファームウェア更新で対応してくれないかななんて虫のいいことを考えています。

Markdown記法が使えるpukiwikiをインストールするDockerfile

タイトルのとおりです。ちょっとしたメモツールが欲しくて作りました。

基本的にLAN内で使う想定なので、セキュリティ対策が全くされていません。もし参考にする方がいれば、適宜セキュリティ対策をお願いします。

DockerfileはGithub/takedahに上げています。

CentOS6.5でDockerを動かしてみる(2)

Dockerイメージのビルドはもちろん手作業でやってもいいのですが、Dockerfileを使うのが良いと思いますので、ここからDockerfileを使ったDockerイメージのビルドについて説明します。

Dockerfileとは

Dockderfileとは、元となるイメージをプルして、ソフトウェアのインストールやコンフィグの変更を行うなどの一連の操作をファイル(Dockerfile)に順番に記載しておき、ファイルを元にイメージのビルドを自動実行するための仕組みです。これは次のようなメリットがあります。

  • 事前に構築手順を確認できるので、手作業でのビルドよりもミスを減らせる。
  • Dockerfile自体が引き継ぎドキュメントになり得る。
  • 異なるDockerホストで同一のDockerfileを使ったイメージのビルドを行えば、同一のイメージが出来上がる。

Dockerfileに書くこと

私のようにDockerfileを使ってとりあえずイメージのビルドをしてみようというレベルで必要となるDockerfileの記載事項を説明してみます。

FROM

元となるイメージを指定します。

RUN

実行する処理を指定します。これは複数書けるので、RUN yum -y updateして次にRUN yum -y install openssh-serverして、……といったように、Dockerfileのメインとなる部分です。

EXPOSE

コンテナに外部ネットワークからアクセスさせるときに使用するポートを記載します。ただ、この記載、本当に必要か分かりません。ここでついでにコンテナのネットワークの扱いについて説明します。

Dockerコンテナのネットワーク

Dockerコンテナにはそれぞれ仮想のネットワークインターフェイスがありますが、実体はホストOSに仮想インターフェイスが作られ、これがコンテナ用に割り当てられているようです。そのため、仮想インターフェイスからホストOSの実インターフェイスにNAPTすることで、コンテナが外部ネットワークとやりとりできる仕組みです。

ホストOSのルーティングテーブルを見てあげると、コンテナ用仮想インターフェイスと経路情報が分かります。

$ route
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
10.1.0.0        *               255.255.255.0   U     0      0        0 eth0
link-local      *               255.255.0.0     U     1002   0        0 eth0
172.17.0.0      *               255.255.0.0     U     0      0        0 docker0
default         10.1.0.254      0.0.0.0         UG    0      0        0 eth0

上記の例では仮想インターフェイス名がdocker0で、172.17.0.0/16がコンテナに割り当てられるネットワークとなっています。

NAPTを使っているので、当然ホストOSで仮想インターフェイスからの送信パケット、仮想インターフェイスへの受信パケットについて、転送(FORWARD)を許可してあげないと、コンテナが外部ネットワークと通信できずに嵌ります。ホストOSのiptablesの設定は忘れずに。

ではNAPTでホストOSに届いたパケットをコンテナが受け取れるようにするにはどうすればいいかというと、docker runするときに次の通り引数を与えます。

$ docker run -i -t -p 10022:22 centos:centos6 /bin/bash

これでホストOSのTCP10022番宛てのパケットは、コンテナのTCP22番ポートへ転送されます。

ここで話が戻るのですが、DockerfileにEXPOSEで10022:22のような書き方が出来ません。そのうえ、EXPOSEに書かないでビルドしたイメージでも、上記のようにdocker runするときにポートを指定してあげればちゃんとパケットは転送されます。ですので、あまりEXPOSEの記載には意味がないのではと思っています。

CMD

docker runしたときに必ず実行させたいプロセスがあれば、CMDに記載します。例えばCMD ["/usr/sbin/httpd -D FOREGROUND]と記載すれば、docker runしたときに何も指定しなくてもapacheをフォアグラウンドで実行させることができます。

Dockerfileを用いたイメージのビルド

上記の内容でDockerfileというファイル名でテキストファイルを任意のディレクトリに作り、これを指定してdocker buildコマンドを実行します。

$ docker build -t takedah:test2 .

最後のピリオドがDockerfileを指します。カレントディレクトリにないDockerfileからビルドするなら、ピリオドの前にパスを書いてください。

これで作ったDockerfileを基にしたイメージが出来上がります。とても手軽です。

Dockerfile嵌りどころのチェックポイント

今回私が勉強会用に作ったDockerfileは、GitHubにアップしました。このDockerfileは、sshdRuby on Railsをセットアップし、sshdはsupervisorで実行するようにしたイメージをビルドするものです。

これを基に以下嵌ってしまったポイントを説明してみます。

yumリポジトリは固定した方が早い

ビルドの際にyumを使う場合、デフォルトではyumfastestmirrorプラグインが近いミラーサイトを探すと思いますが、Dockerイメージでyumをすると毎回決まって海外鯖に繋ぎにいくような気がします。そのため、yumリポジトリは国内鯖に固定した方が無難な気がします。以下リポジトリIIJに固定する例。

RUN sed -i -e 's/^#baseurl/baseurl/' /etc/yum.repos.d/CentOS-Base.repo
RUN sed -i -e 's/mirror\.centos\.org/ftp\.iij\.ad\.jp\/pub\/linux/' /etc/yum.repos.d/CentOS-Base.repo
RUN sed -i -e 's/^mirrorlist/#mirrorlist/' /etc/yum.repos.d/CentOS-Base.repo

Dockerコンテナで複数プロセスを実行するには

基本的にDockerコンテナは起動時に1プロセスしか実行できないため、supervisorからデーモンとしてプロセスを複数実行させる必要があります。以下supervisorをyumでインストールし、sshdをsupervisorから実行させる設定。

RUN yum -y --enablerepo=epel install supervisor
RUN sed -i -e 's/^nodaemon=false/nodaemon=true/' /etc/supervisord.conf
RUN echo '[program:sshd]' >> /etc/supervisord.conf
RUN echo 'command=/usr/sbin/sshd -D' >> /etc/supervisord.conf

sshでログインできない

設定合ってるのにログインできなくて悩んでたら、解決方法がありました。/etc/pam.d/sshdの設定変更が必要です。

RUN sed -i -e 's/^session *required *pam_loginuid\.so/session optional pam_loginuid/' /etc/pam.d/sshd

openssl-develがcentosplusリポジトリにある

標準リポジトリにopenssl-develがなくcentosplusリポジトリにあるため、リポジトリを指定してインストールする必要があります。

RUN yum --enablerepo=centosplus -y install openssl-devel

Dockerfileの行が変わると環境変数の設定が初期化されちゃう

例えばビルド中に/etc/profileの内容を反映させるとします。しかしDockerfileの次の行に処理が移ると、これが初期化されてしまいます。そのため、RUN source /etc/profile && 次のコマンド、というように、一行にまとめないとうまくいきません。

最後に

上記のDockerfileでビルドすれば、sshdRuby on Railsが動くCentOS6コンテナができますが、なぜかwebrickを起動するときにexecJSのライブラリが無い的なエラーが出て起動してくれません。

解決策は次の通りです。

  1. rails newしてプロジェクトを作成した後、Gemfileに"gem 'therubyracer'"を追記する。
  2. bundle installする。
  3. rails serverすればwebrickがちゃんと起動します。

勉強会では、上記のDockerfileからコンテナをビルドして実行し、railswebrickを起動してブラウザに表示させるところをデモとして見せたかったのですが、ど緊張して解決策の2番目を忘れてしまい、うまく行きませんでした……。

とりあえず勉強会で発表した内容をブログにまとめてみました。本当はこんなことを伝えたかったんだなと思っていただければ幸いです……。

今回主催の@smaguchiさん、発表の機会を与えていただきありがとうございました。また、勉強会の際に私がKVM on Dockerは出来るけどKVM on KVMはできないと話したんですが、後日@plansetさんから可能にする方法を教えていただきました。参考→KVM on KVM(nested KVM)。ありがとうございました!

CentOS6.5でDockerを動かしてみる(その1)

ゆるい勉強会のお盆明け勉強会に参加し、Dockerについて発表する機会をいただきました。勉強会は色々な方向、ジャンルの話が聞けてとても楽しかったのですが、自分の発表は緊張でダメダメでした……。

しかしダメダメなままで終わるのは勿体ないので、発表したかった内容をもう一度文字で整理してみようと思います。

Dockerとは

仮想化技術の主流となっているハイパーバイザ型に対し、Dockerはコンテナ型に属する仮想化技術です。

ハイパーバイザ型仮想化

ハイパーバイザと呼ばれるソフトウェア(またはカーネルモジュール)の仕組みで、ホストOSのハイパーバイザ上に物理ハードウェアをエミュレートした仮想の物理マシンを作り、この仮想マシンでゲストOSを実行できるようにするものです。代表的なものに、VMWare ESXi、XenHyper-V、そしてオープンソースKVMがあります。

コンテナ型仮想化(Linux

ハイパーバイザを持たず、ホストOSとカーネル領域を共有し、ユーザー領域のみをコンテナとして独立させるものです。各コンテナには仮想のハードウェア、ネットワークが割り当てられるので、独立したゲストOSとして扱うことができます。コンテナのハードウェア資源、ネットワークはホストOSのカーネルに依存するものです。

コンテナ型仮想化のメリット・デメリット

ハードウェアをエミュレートするハイパーバイザ型の仮想化に比べ、ホストOSのカーネルをそのまま利用するコンテナ型の方がオーバーヘッドが小さく軽快に動作すると言われています。

ただし、ユーザー領域(ユーザープロセス)を仮想的に分離するという仕組み上、コンテナ間は独立していても、ホストOSからコンテナのプロセスは見えてしまうため、セキュリティには注意が必要です。

Dockerのいいところ

上記コンテナ型仮想化のメリットに加え、コンテナを用意するためのイメージという仕組みが用意されており、ベースとなるコンテナイメージからコンテナを起動することができます。

コンテナイメージはDockerhubに様々なもの(CentOSの公式イメージなど)があり、これを簡単に手元へ引っ張ってくることができます。引っ張ってきたイメージに独自のカスタマイズを加えて、オリジナルのイメージを構築することも簡単です。

更にDockerfileという仕組みを使えば、イメージの構築手順を全てスクリプトに記載しておき、イメージのビルドを自動化することができます。そのため、Dockerfile自体が構築手順書になり得ます。

さっそくCentOS6.5にDocker環境を構築する

今旬なDocker環境の構築は、RHEL7(やそれの互換を目指しているCentOS7)、CoreOS、本家本元のUbuntuを使うようですが、ヘタレな私は使い慣れたCentOS6.5を使いました……。

CentOS6.5でも、Docker環境構築はとても簡単でした。具体的な手順は次のとおりです。

yumでEPELリポジトリを使用できるようにする

RPMをダウンロードし、yumでEPELリポジトリを使用できるようにします。最後のコマンドで、リポジトリを指定しない限り標準リポジトリを使用するようコンフィグを書き換えています。

$ rpm --import http://ftp.iij.ad.jp/pub/linux/fedora/epel/RPM-GPG-KEY-EPEL-6
$ rpm -ivh http://ftp.iij.ad.jp/pub/linux/fedora/epel/6/i386/epel-release-6-8.noarch.rpm
$ sed -i -e 's/^enabled=1$/enabled=0/g' /etc/yum.repos.d/epel.repo

EPELリポジトリからyumでDockerをインストール

DockerはLXC(Linuxコンテナ)やcgroupの技術を使いますが、yumでインストールしてしまえば依存関係はyumが解決してくれるので楽です。

$ yum -–enablerepo=epel install docker-io

とりあえずのDockerの使い方

なにはともあれ、まずはDockerを触ってみます。概要は次の通りです。

  1. 元となるコンテナイメージを手元にプルする
  2. プルしたイメージからコンテナを起動し、何かプロセスを実行させる
  3. 終了したコンテナの状態を確認する
  4. 今度はコンテナを起動し、シェルを対話的に実行する
  5. 終了したコンテナを再利用するためイメージとしてコミットする
  6. コミットしたイメージからコンテナを起動する

それでは始めてみましょう。

元となるコンテナイメージを手元にプルする

コンテナイメージをDockerhubからプルするには、docker pullコマンドを使います。ここではCentOSの公式イメージをプルすることにします。

$ docker pull centos

これだけでcentosの公式イメージをローカルに引っ張ってくることができました。docker imagesコマンドでローカルのイメージの状態を確認します。

$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
centos              centos7             1a7dc42f78ba        6 minutes ago         236.4 MB
centos              latest              1a7dc42f78ba        6 minutes ago         236.4 MB
centos              centos6             cd934e0010d5        6 minutes ago

centosというリポジトリから、centos6、centos7、latestというタグの付いたイメージがローカルに保存されていることを示しています。

現時点でCentOSの最新イメージはCentOS7なので、latestとはCentOS7と同一です。

プルしたイメージからコンテナを起動し、何かプロセスを実行させる

docker runコマンドで、ローカルのイメージからCentOS6のコンテナを起動してプロセスを実行させます。

$ docker run centos:centos6 /bin/echo "Hello, World!!"

ここでポイントは、docker runの後に指定するcentos:centos6の部分で、コロンの前がリポジトリ名を、後がタグを指定しています。ローカルのcentosリポジトリにはCentOS6と7がありましたが、タグを指定することでCentOS6でコンテナを実行するようにしています。

上記の例ではechoで標準出力に「Hello, World!!」と出しています。

終了したコンテナの状態を確認する

Dockerコンテナは基本的に実行したプロセスが終了すると、コンテナも終了状態となります。終了したコンテナの状態を表示するためには、次のコマンドを使います。

$ docker ps -a
CONTAINER ID        IMAGE               COMMAND                CREATED             STATUS                      PORTS               NAMES
e6331182be91        centos:centos6      /bin/echo 'Hello, Wo   26 seconds ago      Exited (0) 23 seconds ago

docker psコマンドに引数aを付けて実行すると、終了したコンテナを含めて全てのコンテナの状態が表示されます。先程実行したコンテナは、echoのプロセスを終了し、コンテナも終了した状態(Exited)になっていることが分かります。

今度はコンテナを起動し、シェルを対話的に実行する

今度はコンテナでシェルを起動し、コンテナを対話的に操作したいと思います。

$ docker run -i -t centos:centos6 /bin/bash
bash-4.1#

これでコンテナのシェルを開くことができました。シェルを抜けるまでコンテナを実行され続けています。

この状態で例えばyumを使ってapacheをインストールしたり、ユーザーを追加したりといった設定を行うことができます。

ここではシェルからyumを使って何か適当なソフトウェアをインストールした後シェルを抜けたと想定します。シェルを抜けたことでコンテナは終了状態となりました。

終了したコンテナを再利用するためイメージとしてコミットする

先程シェルを実行したコンテナの状態を確認します。

CONTAINER ID        IMAGE               COMMAND                CREATED             STATUS                      PORTS               NAMES
2db39ef055e0        centos:centos6      /bin/bash              5 minutes ago       Exited (0) 5 seconds ago                        happy_carson

このままではせっかく先程yumでソフトウェアをインストールしたコンテナを再利用できません。そこで、終了したコンテナをコミットし、再利用できるDockerイメージにする操作をします。

$ docker commit 2db takedah:test1

docker commitコマンドの後にdocker ps -aで表示したコンテナIDを、その後にリポジトリ名:タグを指定します。コンテナIDは特定できればいいので、最初の数文字だけで認識してくれます。

コミット後のローカルのイメージの状態を確認します。

$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
takedah             test1               18799f69459e        5 minutes ago       212.5 MB
centos              centos6             b1bd49907d55        3 weeks ago         212.5 MB
centos              centos7             b157b77b1a65        3 weeks ago         243.7 MB
centos              latest              b157b77b1a65        3 weeks ago         243.7 MB

コミットしたイメージからコンテナを起動する

先程コミットしたイメージをベースにコンテナを起動してみます。

$ docker run takedah:test1 /bin/echo "Hello, World!!"

標準出力に文字が表示されましたか?

ここまでの一旦まとめ

まず手っ取り早くDockderイメージを作るには、

  1. 元となるイメージのプル
  2. コンテナ起動時にシェルを対話的に実行
  3. シェルからソフトウェアのインストールや設定などを行ってからコンテナを終了
  4. 終了したコンテナをコミットして確定する

という手順になるかと思います。

しかし、Dockerイメージを作るには、Dockerfileという方法を使わない手はありません。次の記事で、Dockerfileを使ったDockerイメージのビルドについて説明してみたいと思います。