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

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

Rubyでひらがなカタカナをヘボン式ローマ字に変換する

業務で大量の人名の読み仮名データ(しかも半角カタカナ)をヘボン式ローマ字に変換しなくてはならなくなったので、Rubyでひらがなカタカナをヘボン式ローマ字に変換するライブラリを作成してみました。

github.com

個人的には訓令式の方が読みやすくて好きなのですが……。

旭川市オープンデータを使った避難場所検索Webアプリを作ってみた

旭川市オープンデータライブラリ が公開されていて、その中に旭川市指定避難場所の一覧が位置情報付きで公開されているのを知って、プログラミングの勉強がてら、旭川市避難場所検索Webアプリを試作してみました。

旭川市避難場所検索(ベータ版)

2017/03/12 追記 : YOLPが常時SSL(AOSSL)対応となり無料版でもHTTPSが使えるようになった ため、現在位置が取得できない問題が解決しましたので、エントリの内容を一部修正しました。

JavaScriptでクライアントの現在位置の緯度経度を取得し、オープンデータから取得した避難場所の緯度経度と計算し、避難場所までの距離を算出し、現在位置から近い避難場所の情報を表示するものです(距離は三角関数を使って算出しているもので、実際の道路状況を考慮した近さを算出できるわけではないので、あくまで近さの目安を確認するものになります)。

最初Ruby on Railsの勉強として作ろうと思ったのですが、やっているうちにERBの使い方や、Rackの仕組みを勉強したいと思い、プログラムはRubyで書き、HTTP周りの部分はRackを使いました。

地図はYahoo! Open Local Platform (YOLP)API(無料版)を使わせてもらっています。

サーバーはHerokuのdyno無料版を使わせてもらっています。

無料版のみで構築したので、このWebアプリには次のような弱点があります。

  1. 1日18時間しかアクセスできない。
  2. 前述のとおり、道路状況を考慮した避難場所までの近さを表示できていない。

1番目の弱点はHeroku dyno無料版の制限です。有料版に移行するか、自分のWebサーバに移すかすれば解決できそうです。

2番目はYOLPのAPIの機能でなんとかできるのかな?これから調べてみたいと思います。

そんなわけで、あくまで私の個人的なプログラミングの練習材料ということで……。

iPhoneから快適にsshするために持ち運びできるblootoothキーボードを買った

時々空いた時間にiPhoneから自宅にVPNを張って、自宅鯖にsshして色々(最近はRuby on Railsチュートリアルやってます)していたのですが、iOSのソフトウェアキーボードでちまちまコマンド打ったりテキスト書いたりするのが辛いので、Ankerというメーカーのbluetoothキーボード買いました。

横幅がiPhone 6を2台縦に並べたくらいあり、折り畳みもできないのだけど、軽いのでカバンの中でさほど邪魔にならない感じ。作りはしっかりしていてキーは打ちやすいです。あとESCキーがちゃんとある。amazonで他のモバイル用bluetoothキーボード見てたらESCキーが無いものが結構あって、自分はNEC Express5800のBMCにsshしてテキストコンソール使うときに、コンソールから抜けるのにESCキーが要るから、ESCキーの有無も選定理由になりました。ちなみにこの手のキーボードは英語配列のものが多いようで、今回買ったキーボードもそうでしたが、元々英語配列キーボード使いの自分には好都合でした。

早速iPhoneのServerauditorという無料だけど使い勝手のいいsshクライアント立ち上げて、bluetoothキーボード繋いで使ってみたら、「これsshするだけならノートPC要らなくね?」ってレベルで快適でした。

Serverauditor - SSH Shell / Console / Terminal

Serverauditor - SSH Shell / Console / Terminal

  • Crystalnix
  • ユーティリティ
  • 無料

今までソフトウェアキーボードで画面の下半分が隠されていたのがなくなり、それなりに表示領域が確保できるし、キー入力の反応もストレス感じないです。唯一ちょっと不満なのは、キー押しっぱなしを認識してくれないこと。例えばVimでざっと行を下に移動しながら中身見ようとJのキーを押しっぱなしにしてもカーソルは1行しか動いてくれないです。まあ普通にキー連打すればいい話なんだけど。

自分はiPhone6だけど、6 plusならもっと快適なんじゃないかなと思います。2000円前後の値段でこの快適さが手に入るならもっと早く買っていれば良かった。

そんなわけで快適な環境が手に入って大満足なのですが、iPhoneの小さい画面にsshクライアント立ち上げてコマンド打ってる様は正直絵面にキツイものがあるようです。家でウキウキでセッティングしていたら妻にまるで犯罪者を見るような目で見られました。ただ小学生の息子がえらい食い付いてきて、「(お下がりした)iPhone4に繋ぎたい!」とか言い出してちょっと困った。何する気なんだろう……。

YAMAHA RTX1200とIIJmio FiberAccess/NFでDS-Lite (IPv4 over IPv6) を使ったIPv4接続環境を構築する

NTTのフレッツ光ネクストファミリー・ハイスピードタイプで、インターネット接続プロバイダーサービスにIIJmio FiberAccess/NFを使うと、IPoE接続のIPv6通信が可能です。IPoE接続の場合、ファミリー・ハイスピードタイプの下り通信速度200Mbpsの制限を受けず、下りの通信速度が概ね1Gbpsとなります(上りは100Mbpsで変わらず)。なので、Gigabitのインターフェイスを持つルーターを使えば、IPv6については上位の通信回線プランを使わなくても下り1Gbpsの通信ができることになります。

とはいえうちのルーターYAMAHA RTX1100だったので、さほど恩恵を受けられずにいました。そんな時にtwitterで、DS-Liteについて書かれたブログを発見しました。

IIJmio FiberAccess/NFでは、DS-LiteというIPv4 over IPv6によるIPv4接続サービス(要するにプロバイダーが用意するNATルーター経由でIPv4インターネット接続ができるもので、NATルーターまでの足回りにIPv6が使う(IPv6でNATルータとIPIPトンネルを張る)というもの、と私は理解しています。)が使えるため、これを使えば、トンネルのオーバーヘッドはあるにせよ、IPv6での通信速度と同程度のIPv4インターネット接続が期待できるものです。

DS-Liteとは

DS-Lite(Dual-Stack Lite)は、RFC6333で規定された通信規格で、本機能を利用することにより、IPv6のみの通信環境においてIPv4 over IPv6技術を利用し、IPv4での通信も可能となります。DS-Lite対応の通信機器をご用意いただければ、一台の機器でIPv6/IPv4双方の通信が可能となります。また、通信速度はIPv6ネットワークの通信速度に準じますので、ご利用回線によっては、IPv4 PPPoE接続での通信速度よりもより高速な通信をご利用いただけます。

IIJmio:DS-Liteについてから引用

今までIPv4インターネット接続はIIJmio FiberAccess/NFのPPPoEと、格安で固定IPv4アドレスが一つ貰えるi-revoのPPPoEをPPPoEマルチセッションで使っていたのですが(フレッツ光ネクストは基本料金でPPPoEを2セッションまで張れる)、このブログ記事を見て早速RTX1100にDS-Liteの設定を投入したところ、PPPoEでの接続時に比べて倍近く下りの通信速度が出るようになりました。

こうなるとGigabitインターフェイスを持っているルーターで試したい気持ちが抑えられず、オークションをさまよってみたら運良く安く中古のRTX1200が手に入ったので、これを機にIPv4接続はDS-Liteのみに切り替えることにしました。

RTX1200のコンフィグは次の通りです。ちなみに同じコンフィグでRTX1100でもDS-Lite接続できました。

# IPv4のデフォルトルートをIPIPトンネルへ向ける設定
# (IPIPトンネルインターフェイスをtunnel 1とする場合)
ip route default gateway tunnel 1

# IPIPトンネルの設定
tunnel select 1
 tunnel encapsulation ipip
 tunnel endpoint address (DS-Lite対向ルーターのIPv6アドレス)
 ip tunnel mtu 1500
 ip tunnel tcp mss limit auto
 tunnel enable 1

最低限のコンフィグはこれだけです(IPv6接続の部分は既に確立している前提で)。PPPoEでIPv4インターネット接続していたらデフォルトルートをppインターフェイスに向けていると思いますが、これをIPIPトンネルインターフェイスに向けて、IPIPトンネルの設定を入れるだけです。もちろんトンネルインターフェイスには適切なip filterを設定することが必要かと思いますがここでは割愛します。

嵌まりポイントが一つあって、トンネルの対向を指定する箇所(tunnel endpoint address 〜の部分)をtunnel endpoint name 〜とすれば対向ルーターIPアドレスではなくホスト名で指定できるのですが、ホスト名で指定するとRTX1200ではトンネルが張れませんでした。

どうも対向ルーターのホスト名の名前解決ができないっぽいのが原因のように思える(IIJDNSサーバーを指定してdigで引いてもIPv6アドレスを返してくれないっぽい)のですが、インターネットマルチフィードの「DS-Lite接続確認情報」のYAMAHA NVR500の設定例を見ると、ホスト名ではトンネルを確立できないと書いてあるので、YAMAHAルーターの仕様で駄目なのかもしれません。おとなしくホスト名での指定を諦めて、直接対向ルーターIPv6アドレスを指定することでトンネルが張れるようになりました。

ちなみに対向ルーターのホスト名は、IIJの会員専用ページ(要ログイン)の「サービス詳細情報」→「FiberAccess/NF」→「設定情報」に書かれています。

対向ルーターIPv6アドレスも上記インターネットマルチフィードのWebページに記載がありますが、NTT東西でアドレスが違うので注意です。

DS-Liteの詳細については、IIJ Technical Week 2014の講演資料(番外編)てくろぐ・セレクト「自宅でもIIJをつかってみませんか?」がとても参考になりました。

さて肝心の通信速度ですが、Speedtest.net by Ooklaで確認したところ、筑波のSoftEtherのサーバー相手に下り350Mbps近く出ていました。フレッツ光ネクストファミリー・ハイスピードタイプの上限200Mbpsを大きく超えており、十分に試す価値ありかと思います。

ただ注意点として、DS-Liteのルーターが東京にあるようで、RadikoのようなIPアドレスから接続している地域を判別するサービスで東京地区と判定されてしまいます(AIR-G'が聞けない……)。

もう一つ重要な注意点が。IIJmio FiberAccess/NFはIPv6は固定のPrefixを貰えますが*1、DS-Liteの出口のIPv4アドレスは非固定です。というかそもそも自分で設定をいじれないNATルーターの配下です。つまり、IPv4でサーバーを外部へ公開したり、VPNで接続したりすることはできないことになります。

これで自分は嵌ってしまいました……。今まで外出先から自宅鯖にアクセスしたい時は、RTX1100とIPv4でL2TP/IPSec接続し、自宅鯖のNEC Express5800のBMCにsshで入ってサーバーの電源オンオフをしていたので、これが出来なくなるととても困る。

直接IPv6でBMCにsshできるならいいのだけど、自分の持っているExpress5800に搭載のものはBMCのIPアドレスIPv4のみ指定可のものでした……。

Openswanを使えばRTX1200とLinuxとでIPv6VPNが張れるので、VPN経由でRTX1200にsshして、そこからBMCにsshすればいけるのだけど、iPhone 6の標準のL2TPクライアントではIPv6でのVPNができないし、そもそも外出先でIPv6アドレス貰える環境はほとんどないし……(IIJmioのSIMとIPv6対応のモバイルルーターNEC MR03LN)持ってるけど、モバイルルーターの電池が切れると詰む)。

悩んだ結果、固定IPv4IPv6アドレスを貰えるさくらのVPSを借りて、SoftEther VPN Server立てて解決することにしました。これについては別の記事に書きたいと思います。

*1:NTTのメンテナンス等で変わる場合があるとのこと。割り当てられるIPv6アドレスは固定ですか? | IIJmio

自宅鯖ネットワークをIPv6中心に再構築しました

自宅鯖ネットワークのバックボーンはフレッツ光ネクストファミリーハイスピードにIIJmio FiberAccess/NFで、IPv6接続時は通信速度が下りのみ概ね1Gbps出せる環境だったのですが、ルーターYAMAHA RTX1100でインターフェイスが全てFast Ethernetだったため、その恩恵が受けられない状況でした。

最近中古でGigabit Ethernetインターフェイスを持つYAMAHA RTX1200が手に入ったため、これを機に自宅鯖ネットワークをIPv6を中心に再構築することにしました。再構築方針は次のような感じです。

  • IPv4インターネット接続をプロバイダーのPPPoE経由にすると通信速度が200Mbpsに制限されてしまうため、PPPoEをやめて、IIJmio FiberAccess/NFで提供されている DS-Lite *1IPv4インターネット接続をする。
  • 今まで外出先から自宅鯖ネットワークへのアクセス方法は、PPPoEで固定IPv4アドレスを貰えるプロバイダーを使って、RTX1100に固定IPv4アドレス振って、L2TP over IPSecVPN接続していたが、PPPoEをやめる方針に従い、IPv4IPv6ともに固定IPを一つ貰えるさくらのVPSSoftEther VPN Serverを立てて、これをハブにVPN接続をすることにする。具体的にはRTX1200とSoftEther VPN Server間をIPv6でL2TPv3 over IPSecで接続し、ノートPC(Ubuntu 14.04LTS)やiPhoneからはSoftEther VPN ServerにIPv4VPN接続する。
  • ついでにもう一つさくらのVPSにzabbixとsyslogサーバーを立てて、さくらのVPSからVPN経由で24時間zabbixで自宅鯖ネットワークを監視し、ルーター他のログをVPN経由でさくらのVPSのsyslogサーバーに飛ばせるようにする。これは 妻からのプレッシャー 家庭の事情により24時間自宅鯖を起動させておけないため、自宅鯖側にzabbixやsyslogを立てておけないという事情のため。
  • さらについでにVPN経由で自宅鯖であるNEC Express5800のBMC *2 にアクセスして、sshで電源オンオフできるようにする。
  • さらにさらについでにKVMLinux仮想ホストを作るときに楽をするため、kickstartでベースを作った後knife soloで設定を流し込めるようにする。

この方針に沿って年末からちまちま作業をしていて、ようやく少し形になってきましたので、Cacooで図を書いてみました。

cacoo.com

これから備忘録的に何回かに分けて、今回の再構築でやった作業を書いていこうと思います。

実行中のKVMゲストをまとめてシャットダウンするシェルスクリプト

KVMホストサーバをshutdownする前にKVMゲストをshutdownしたい時、一ゲストごとにコマンド打つのはちょっと面倒になってきたので、一回のシェルスクリプト実行でKVMゲストを全部シャットダウンできるようにしてみました。

シェルスクリプトの内容は次の通りです。

#!/bin/bash
for var in `/usr/bin/virsh list --all | egrep 'running$|実行中$' | awk '{print $2}'`
do
  echo "virtual machine $var is going to shutdown..."
  /usr/bin/virsh shutdown $var
done

以下解説。

シェルスクリプトのfor構文は、コマンドの実行結果を変数に代入することができるので、virsh listコマンドの出力からKVMゲストのドメイン名のみ抽出して変数に渡す→変数と組み合わせてvirsh shutdownコマンドを完成させて実行、という内容です。

コマンドvirsh list --allを実行すると次の出力が返ります。

[hoge@kvm ~]$ sudo virsh list --all
 Id    Name                           State
----------------------------------------------------
 1     docker                         running
 2     proxy                          running
 3     router                         running
 -     www                            shut off

実行中のKVMゲストはState(状態)が「running(日本語環境だと「実行中」)」となるので、virsh list --allの出力からまずはegrepで行末が「running」か「実行中」になっている行を抽出し、パイプでawkへ渡します。2項目目がKVMゲストのドメイン名なので、awkで$2だけをprintします。結果、実行中のKVMゲストのドメイン名のみが出力されることになります。

後はfor構文に出力を渡せば、ここでは変数varにKVMゲストのドメイン名が代入されるので、変数varをvirsh shutdownコマンドの引数にするようにすれば、ループで全ての実行中のKVMゲストのvirsh shutdownコマンドが実行される仕組みです。

簡単な小ネタなので、例外処理とか一切考慮していません。

ちなみに、KVMゲストがCentOS6で、virsh shutdownコマンドでシャットダウンできない場合、acpidをyumでインストールすればシャットダウンできるようになります。

Squidがアクセス集中時に遅くなったときに行った対策

始業時や昼休みなど、アクセスが集中する時間帯になるとLinuxSquidプロキシサーバのレスポンスが悪くなって、「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 を受信していない状態の接続リクエストをキューに置ける最大数。 この数値を越えると、カーネルはリクエストを捨て始める。

Man page of TCP

この値が少ないと、アクセス集中時に接続してきたクライアントのコネクションを切ってしまう→クライアントのリクエストはタイムアウト、となっていたと思われるので、元の設定(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 であった。

Man page of LISTEN

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.

3.3.9.4. /proc/sys/net/

こちらもバックログ関係の設定。同じく4096に設定。

net.core.netdev_max_backlog = 4096

tcp_max_tw_buckets

システムが許容する TIME_WAIT 状態にあるソケットの最大数。 この制限が存在するのは、 単純な使用不能 (denial-of-service) 攻撃を防ぐために過ぎない。 デフォルト値は NR_FILE*2 で、システムのメモリに応じて調整される。 この数値を越えると、そのようなソケットはクローズされ、警告が表示される。

Man page of TCP

TIME_WAITで待てる数を増やすため、元よりも大きな値に設定。

net.ipv4.tcp_max_tw_buckets = 360000

tcp_tw_reuse

プロトコルの面から見て問題ない場合に新規コネクションに TIME_WAIT 状態のソケットを再利用することを許可する。

Man page of TCP

待ちコネクションの回転率を上げるために有効に設定。

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 : max_filedescriptors configuration directive

Squid実行ユーザーの上限値変更

rootや一般ユーザーとは違い、デーモンを実行するユーザー(squidapacheなど)は、/etc/security/limits.conf に設定してもulimitの設定は反映されないそうです(知らなかった)。

ファイルディスクリプタ数の上限変更とlimits.confの罠 (ゆめ技:ゆめみスタッフブログ)に詳しく解説されていました。以下引用します。

daemon系プロセスのファイルディスクリプタ数上限を設定する際、/etc/security/limits.conf は使えません。状況によっては一見設定されたように見えますが、大きな落とし穴にはまることになります。

面倒ですが、必要なプロセス毎にulimitを用いて適切に設定しましょう。

ファイルディスクリプタ数の上限変更とlimits.confの罠 (ゆめ技:ゆめみスタッフブログ)

そこで、squidの起動スクリプト /etc/rc.d/init.d/squid に次を追記します。これにより、Squidの実行ユーザー(squid)がスクリプト実行した際にulimitの設定が行われます。

ulimit -HSn 4096

追記後はSquidを再起動すれば設定が反映されます。

結果

無事にアクセスが集中する時間帯でもファイルディスクリプタのエラーは起きなくなったみたいです。Webアクセスのレスポンスも一気に改善しました。

反省点は、切り分けの順番として先にSquidの全部のログを調べるべきだったなというところです。いずれにしてもネットワーク周りのチューニングも必要だったでしょうけど。