Squidがアクセス集中時に遅くなったときに行った対策
始業時や昼休みなど、アクセスが集中する時間帯になるとLinuxのSquidプロキシサーバのレスポンスが悪くなって、「Webアクセスが遅い!!」とあちこちから苦情を言われてしまっていたのですが、次のような対策を行ったところ、無事解決できたっぽいのでメモることにします。
現状の確認
CPUやメモリの使用率を確認
まずはお決まりのtopコマンドをアクセス集中時に実行(topコマンド自体が高負荷時にはサーバに更に追い打ちをかけそうですが、原因の切り分けのためにはやむを得ないと思います)。やはりsquidプロセスが一番CPUもメモリも喰っていましたが、load averageは低く、CPUやメモリに負荷がかかっているようには見えない状況でした。
ネットワークの状況を確認
次に同じくアクセス集中時にnetstatコマンドを実行。すると状態がESTABLISHEDのコネクションよりもTIME_WAITやSYN_RECVのものが多く、ネットワークの待ち行列がボトルネックになっている状況が見えてきました。
ネットワーク周りのカーネルパラメータのチューニング
そこで、カーネル周りについて、次のようなチューニングを実施しました。基本線は、OSで受け付けられるコネクションを増やし、不要になったクライアントのコネクションを早く次のクライアントのコネクションに回せるようにします。
なお、全て /etc/sysctl.conf に追記し、sysctl -pで設定を反映させています。
tcp_fin_timeout
ソケットを強制的にクローズする前に、 最後の FIN パケットを待つ時間を秒単位で指定する。 Man page of TCP
tcp_fin_timeoutを短くすれば、通信が終わって不要になったコネクションを解放する時間も早くなるのでは?と考え、30秒に設定(元は60秒でした)。
net.ipv4.tcp_fin_timeout = 30
tcp_max_syn_backlog
接続してきているクライアントから ack を受信していない状態の接続リクエストをキューに置ける最大数。 この数値を越えると、カーネルはリクエストを捨て始める。
この値が少ないと、アクセス集中時に接続してきたクライアントのコネクションを切ってしまう→クライアントのリクエストはタイムアウト、となっていたと思われるので、元の設定(1024でした)よりも大きめに設定。
net.ipv4.tcp_max_syn_backlog = 4096
somaxconn
TCP ソケットでの backlog 引き数の振る舞いは Linux 2.2 で変更された。 現在ではこの引き数は、 受け付けられるのを待っている、 完全に 確立されたソケットのキューの長さを指定する。 以前は不完全な接続要求の数であったが、これを置き換えた。 不完全なソケットのキューの最大長は /proc/sys/net/ipv4/tcp_max_syn_backlog を用いて設定できる。 syncookie が有効になっている場合、 論理的な最大長は存在せず、この設定は無視される。
backlog 引き数が /proc/sys/net/core/somaxconn の値よりも大きければ、 backlog の値は暗黙のうちにこの値に切り詰められる。 このファイルのデフォルト値は 128 である。 バージョン 2.4.5 以前のカーネルでは、この上限値は コード埋め込みの固定値 SOMAXCONN であり、その値は 128 であった。
tcp_max_syn_backlogの値を上げても、somaxconnの値が低ければこちらに引っ張られてしまうとのこと(参考→DSAS開発者の部屋:高負荷サイトのボトルネックを見つけるには)。そこでsomaxconnもtcp_max_syn_backlogと同じ値に設定。
net.core.somaxconn = 4096
netdev_max_backlog
Sets the maximum number of packets allowed to queue when a particular interface receives packets faster than the kernel can process them. The default value for this file is 300.
こちらもバックログ関係の設定。同じく4096に設定。
net.core.netdev_max_backlog = 4096
tcp_max_tw_buckets
システムが許容する TIME_WAIT 状態にあるソケットの最大数。 この制限が存在するのは、 単純な使用不能 (denial-of-service) 攻撃を防ぐために過ぎない。 デフォルト値は NR_FILE*2 で、システムのメモリに応じて調整される。 この数値を越えると、そのようなソケットはクローズされ、警告が表示される。
TIME_WAITで待てる数を増やすため、元よりも大きな値に設定。
net.ipv4.tcp_max_tw_buckets = 360000
tcp_tw_reuse
プロトコルの面から見て問題ない場合に新規コネクションに TIME_WAIT 状態のソケットを再利用することを許可する。
待ちコネクションの回転率を上げるために有効に設定。
net.ipv4.tcp_tw_reuse = 1
カーネルチューニングの効果
再びアクセス集中時にnetstatコマンドを実行。すると状態がTIME_WAITやSYN_RECVのコネクションが格段に減少しました。チューニングの効果は確かにありました。これでSquidのパフォーマンスも上がるはず……が、ほとんどWebアクセスのレスポンスは変わりませんでした。
再び現状の調査
OSの資源は枯渇していない、ネットワークのボトルネックも解消した、それでもSquidが遅いとなると、Squid自身の問題を疑うしかありません。access.logを眺めても異常は見つからず。悩みながらふとcache.logを見てみると、Your cache is running out of filedescriptorsという見慣れないログが。grepすると出るわ出るわ……それもちょうどアクセスが集中する時間帯にばかり多発しているようでした。これで原因はSquidキャッシュのオープンがファイルディスクリプタの上限を超えてしまっていることに絞られました。
Squidのファイルディスクリプタの上限値を変更
squidのコンフィグ変更
/etc/squid/squid.conf のmax_filedescの値を4096に増やし、コメントアウトを解除します(元の設定にない場合は追記)。
$ sudo sed -i -e 's/^# max_filedesc 1024/max_filedesc 4096/' /etc/squid/squid.conf
もしかしたら次項のulimitの設定だけでいいのかも。ドキュメントにはulimitの設定を引き継ぐならこの設定を消しなさいと書いてあるように読めます。
Reduce the maximum number of filedescriptors supported below the usual operating system defaults.
Remove from squid.conf to inherit the current ulimit setting.
Squid実行ユーザーの上限値変更
rootや一般ユーザーとは違い、デーモンを実行するユーザー(squidやapacheなど)は、/etc/security/limits.conf に設定してもulimitの設定は反映されないそうです(知らなかった)。
ファイルディスクリプタ数の上限変更とlimits.confの罠 (ゆめ技:ゆめみスタッフブログ)に詳しく解説されていました。以下引用します。
daemon系プロセスのファイルディスクリプタ数上限を設定する際、/etc/security/limits.conf は使えません。状況によっては一見設定されたように見えますが、大きな落とし穴にはまることになります。
面倒ですが、必要なプロセス毎にulimitを用いて適切に設定しましょう。
そこで、squidの起動スクリプト /etc/rc.d/init.d/squid に次を追記します。これにより、Squidの実行ユーザー(squid)がスクリプト実行した際にulimitの設定が行われます。
ulimit -HSn 4096
追記後はSquidを再起動すれば設定が反映されます。
結果
無事にアクセスが集中する時間帯でもファイルディスクリプタのエラーは起きなくなったみたいです。Webアクセスのレスポンスも一気に改善しました。
反省点は、切り分けの順番として先にSquidの全部のログを調べるべきだったなというところです。いずれにしてもネットワーク周りのチューニングも必要だったでしょうけど。
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すると、yumでvim-enhancedが正常にインストールされます。しかし、CentOS 6のyumでインストールするVimはRubyに対応していないので、ソースからコンパイルして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でしかアクセスしないため、IPv4でSMTP 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)が付与されているため、ipv6でipsecを張ることにします。
色々調べた結果、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 ipv6のipsecをサポートしているので、頑張れば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は、sshdとRuby 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でビルドすれば、sshdとRuby on Railsが動くCentOS6コンテナができますが、なぜかwebrickを起動するときにexecJSのライブラリが無い的なエラーが出て起動してくれません。
解決策は次の通りです。
- rails newしてプロジェクトを作成した後、Gemfileに"gem 'therubyracer'"を追記する。
- bundle installする。
- rails serverすればwebrickがちゃんと起動します。
勉強会では、上記のDockerfileからコンテナをビルドして実行し、railsのwebrickを起動してブラウザに表示させるところをデモとして見せたかったのですが、ど緊張して解決策の2番目を忘れてしまい、うまく行きませんでした……。
とりあえず勉強会で発表した内容をブログにまとめてみました。本当はこんなことを伝えたかったんだなと思っていただければ幸いです……。
今回主催の@smaguchiさん、発表の機会を与えていただきありがとうございました。また、勉強会の際に私がKVM on Dockerは出来るけどKVM on KVMはできないと話したんですが、後日@plansetさんから可能にする方法を教えていただきました。参考→KVM on KVM(nested KVM)。ありがとうございました!