ルーターのコンフィグを git にぶち込んで世代管理してみたはなし
この記事を三行で
- みんな好き勝手な名前でコンフィグファイルをアップロードしてサーバーがパンク&どれが最新版かわからない状況に
- 命名規則を作り、サーバーに置いておくコンフィグを最新世代1つのみとした
- さらにファイルを社内 GitLab に自動アップロードし履歴確認ができるようにした
※ GitLab を入れ物として使うだけで、git を使ったワークフローを導入したとかのカッコイイ話じゃありません
あらまし
とある通信会社の委託でIPネットワークの監視作業員をやっています(非エンジニア)。うちの会社ではルーター・スイッチ類のコンフィグをTFTPサーバーへアップロードしてバックアップとしていますが、先日そのサーバーで反応が5分くらい返ってこなかったり、ファイルのアップロードに失敗したりと動作が不安定に・・・。HDDでも壊れたかな?と思いながら調べてみると...
$ df -h Filesystem サイズ 使用 残り 使用% マウント位置 /dev/cciss/c0d0p3 195G 195G 0 100% / /dev/cciss/c0d0p1 99M 13M 81M 14% /boot
Σ(;゙゚ω゚) ディスクフル!!!!
$ ll /var/tftp/ | wc -l 239662
Σ(;゙゚ω゚) 24万ファイル!!!!
この異常なまでの膨大なファイルは、いろんな組織や人間が好き勝手な名前でコンフィグをアップロードしてきたなれの果てです。ホスト1つを取っても、こんな感じでファイルがうじゃうじゃ出てきます。
$ ll /var/tftp | grep tkycore01 -rw-rw-rw- 1 nobody nobody 124862 10月 10 2015 20140303_tkycore01-confg -rw-rw-rw- 1 nobody nobody 127522 10月 10 2015 after_tkycore01.config -rw-rw-rw- 1 nobody nobody 123456 10月 10 2015 before_tkycore01.config -rw-rw-rw- 1 nobody nobody 122899 10月 10 2015 tkycore01 -rw-rw-rw- 1 nobody nobody 123456 10月 10 2015 tkycore01-confg -rw-rw-rw- 1 nobody nobody 126984 10月 10 2015 tkycore01-confg.txt -rw-rw-rw- 1 nobody nobody 119853 10月 10 2015 tkycore01-confg-old -rw-rw-rw- 1 nobody nobody 127556 10月 10 2015 tkycore01-confg_20130712.config -rw-rw-rw- 1 nobody nobody 126574 10月 10 2015 tkycore01-config
しかも以前に行ったサーバーの引っ越し時にタイムスタンプが狂ってしまった(属性のコピーを忘れた)らしく、もはやどれが最新のファイルかわからない状態に・・・。しかもこれが原因で誤って古いコンフィグで設備を復旧させてしまい、通信障害を起こしてしまった事故が何度も起きています。(ちなみに社内では、ヒューマンエラーに起因する障害を "人為故障" と呼ぶ)
対策方針
ふつうなら RANCID あたりで管理すべきところですが、うちの会社は優れたオープンソースソフトウェアを導入したり、ワークフローを大きく変えることに強い抵抗があるため、あくまで従来の暖かみのあるマニュアルオペレーション()を踏襲しつつ、差分管理のみを自動化することにしました。というわけで立てた方針がコチラ。
- TFTPサーバーに保存しておくコンフィグは最新世代一つのみとし、ファイル名は Cisco ライクに
ホスト名-confg
に統一する - 定期的にTFTPサーバー上の変更されたファイルを 社内GitLab へ自動アップロードし、バックアップ兼ヒストリーとして活用する
これによって、作業者のオペレーションは次のようになります。
Before
- ダウンロード : TFTPサーバーをホスト名で検索し、中身を見比べて最新ファイルを探して取得する = オペミスの恐れ!!
- アップロード : 特にルールがなく、上書きしたり、ファイル名に日付をつけたり様々 = オートメーション化できない
After
- ダウンロード:
ホスト名-confg
で取ってくる - アップロード:
ホスト名-confg
で上げる
と、シンプルなオペレーションになりました。
サーバー大掃除
さっそくTFTPサーバー上から不要なファイルを削除し、最新ファイルをリネームしなければならないのですが、どのファイルが最新か調べるのは大変です。さらにディスク溢れのせいか、破損しているファイルもいくつか見つかったため、この際すべて取得し直すことにしました。
私が以前作った Telnetter というバッチ実行環境を使い、自担当が管轄している全ホストに Telnet して copy running-config tftp:
でTFTPサーバーへ現用コンフィグをアップロードするシナリオファイルを作成して実行。そのあと命名規則に則っていないゴミファイルをサーバーから削除。これによって約14万ファイルを削減できました!!
$ ll /var/tftp | grep tkycore01 -rw-rw-rw- 1 nobody nobody 124862 5月 30 00:00 tkycore01-confg
(*´ω`*) すてき
ファイルが最新版1つになったことによって、サーバーが軽くなりましたし、コンフィグ復旧時に誤って古いコンフィグをダウンロードしてしまうといったヒューマンエラーも防止することができます。
git を使って履歴管理
ただもしかすると今後、以前のコンフィグを参照したいニーズが出てくるかもしれません。でもここで、またファイル名に日付を付けだしたりすると、せっかくの大掃除が水の泡になってしまいます。よい方法を考えていた矢先、「そうだ、履歴管理といえばバージョン管理システムじゃん」と気づき、git を使ってみようと思い立ちました。
バージョン管理システムを使うことで、いわゆる「いつ、だれが、何のために」変更を行ったかを記録できるメリットが得られるのですが、うちの作業員(ノンエンジニア)に git を使ってもらうのは困難なので、定期的に更新されたファイルをリモートリポジトリ(GitLab)に push させるスクリプトを動かし、今回は GitLab を単なる "時間軸をもった入れ物" として使うことに。
私は PHP しか書いたことのないゆとりちゃんなのですが、さすがにシェルスクリプト的なものを PHP で書くのもアレなので、今回人生で初めて Python を使ってみることにしました。
全体の流れは次のとおり
- TFTPサーバーから rsync でファイルを取ってくる
- ファイル名を解析し、フォルダ分けを行う
- 追加・削除・変更があったファイルを git add / git rm して社内GitLabサーバーへ push
1. rsync でファイルサーバーからファイルを取ってくる
os.chdir(os.path.dirname(os.path.abspath(__file__))+'/') args = [ 'sshpass', '-p', rsync_remote_pass, 'rsync', '-auvz', '--delete', '--include-from', 'rsync_include.list', '--exclude', "*", rsync_remote_uri, 'tftp/' ] subprocess.call(args)
python から rsync コマンドを叩いているだけです。ポイントは次の通り
sshpass
を使って対話式ログイン処理を省略expect \"password:\"
とかやってる場合じゃない! 参考:sshpassでssh/scpの対話型のパスワード入力を自動化する - Qiita- 本当は公開鍵認証方式にしたかったのだけど、サーバー側のSSHdの設定をいじる権限が無いので断念
--include-from
を使って自担当が管轄する装置のコンフィグファイルだけを転送させる--delete
オプションを使ってサーバー上のファイルが削除されたら、作業環境のファイルも同期して削除するようにする
2. ホスト名をもとにフォルダ分け
このまま数万のファイルを git にぶちこんでしまうと、リポジトリが大変なことになってしまうので階層化します。今回はロケーション(都道府県名/データセンタービル名)で分けることにしました。
# 既存ファイル一覧を取得 (削除対象フラグとして使う) arealist = [ 'tokyo', 'kanagawa', 'chiba', 'saitama', 'ibaraki', 'tochigi', 'gunma', 'yamanashi', 'nagano', 'nigata', 'miyagi', 'fukushima', 'iwate', 'aomori', 'yamagata', 'akita', 'hokkaido' ] old_files = {} for area in arealist: for dirpath, dirnames, filenames in os.walk(area): for file in filenames: old_files[file] = dirpath+'/'+file
まずは分類済みのファイルリストを生成します。これはのちに削除されたファイルの検出に用います。(都道府県で会社がバレますな)
# TFTPディレクトリから一覧を取得、ファイル名解析しビルごとのディレクトリへコピー pattern = r"^(secret)$" host_re = re.compile(pattern) for file in os.listdir('tftp'): # ファイル名解析 match = host_re.search(file) if match is None: print('\033[91m**WARNING**\033[0m Invalid filename format : '+file) continue bldg = match.group(2) area = areacode[match.group(3)] src = 'tftp/'+file dst = area+'/'+bldg+'/' # 削除フラグを消す if file in old_files: old_files.pop(file) # すでに同じファイルがある場合はスキップ if os.path.isfile(dst+'/'+file) and filecmp.cmp(src, dst+'/'+file): continue # コピー先ディレクトリが無ければ作成 if not os.path.isdir(dst): os.makedirs(dst) # ファイルをビルディレクトリへコピー shutil.copy2(src, dst) args = ['git', 'add', dst+'/'+file] subprocess.call(args)
つぎにさきほど rsync で取ってきたファイルに対し、ファイル名を正規表現で解析して適切なディレクトリへコピーしします。
# TFTPディレクトリから無くなったファイルを削除 for path in old_files.values(): args = ['git', 'rm', path] subprocess.call(args) print('Removed deleted file: '+path)
ファイルの走査が終わった後も分類済みファイルリストに残っているファイルは、オリジナルのTFTPサーバーに存在しない、すなわち削除されたファイルなので、git rm
で分類先ディレクトリから削除します
3. git push
d = datetime.datetime.today() today = '%s/%s/%s' % (d.year, d.month, d.day) args = ['git', 'commit', '-m', 'auto backup at '+today] subprocess.call(args) args = ['git', 'push', '-u', 'origin', 'master'] subprocess.call(args)
python から git コマンドをコールし、社内 GitLab サーバへ push します。
このスクリプトを1日1回動かし、その日に変更されたファイルを自動で GitLab へ push するようにしました。
git を使ったコンフィグ履歴確認
その結果、こんな感じでWebブラウザからコンフィグの閲覧や差分の確認ができるようになりました。
また、git コマンドを使えばさらに高度な操作ができます。
- 特定ホストのコンフィグ変更履歴をみたい :
git log <ファイルパス>
- 特定ホストの過去のコンフィグと現在のコンフィグを比較したい :
git diff <コミットID> <コミットID> <ファイルパス>
- 特定の期間にコンフィグが変更されたホスト一覧を調べたい :
git log --since=<開始日時> --until=<終了日時>
ほんと git は何でもできますね。恐るべし!
今後やりたいこと
- えらい人に本施策の正式な許可をもらう(もらってないのかよ!)
- 他部署も巻き込んでコンフィグの管理ルール・命名規則を決め標準化する
- ゆくゆくは作業者に git を使ってもらう
- ルーター内のフラッシュメモリカードも同様にぐちゃぐちゃになっているので対策したい
- サーバーがディスクフルなのを誰も気づいてなかったので監視ツール入れたい(私の管轄じゃないけど)
余談 | Python 初体験の感想
結論:ググればなんとかなった
- 変なことが起きたらとりあえず例外を投げてくれるので、PHP でありがちな「何も言わずに落ちた」・「正常に終わったと見せかけて、正常に終わってない」ということが起きにくい
- ネストの
{}
や文末の;
、変数の$
など、PHP と比べて記号が少なく全体的にすっきりしている - Python のリスト(配列)の検索はめちゃくちゃ遅い。PHPの「とりあえず連想配列にぶち込む」的なノリで使ったら痛い目に遭った。Python で配列を検索するなら辞書かセットを使ってキーを検索する(ハッシュ照合なので速い)
- 今回ディレクトリ一覧の取得やファイルのコピーなどファイルシステムまわりの操作を多用したが、PHP よりかゆいところに手が届く感
subprocess.call
が引数を自動でエスケープしてくれることに驚き。(PHPだと自分でやらないとOSコマンドインジェクションが容易に行えてしまう)- 文字列の連結で、どうしても PHP 式の
foo.bar
表記にしてしまい毎回怒られてしまう(慣れてくると、今度は PHP でfoo+bar
って書いちゃうんだろうな・・・)
典型的な食わず嫌いでした。
後日談
後日、説明資料を作って本社の管理部門の方とお話したのですが、「ギットとかよくわかんないし、組織間で協議してルール作るのもめんどくさいので、これまで通り好きな名前でどんどんアップロードしてくれ」とやんわり全面却下されました。
たくさんファイルが作られることで、またサーバーが重くなったり、ヒューマンエラーが起きてしまうリスクについては「課題としては認識している(が対応はしない)」とのことです。
(´-`)
さらに後日談
ルールを決めてもやっぱり誤った命名規則でコンフィグをアップロードする人や、違うディレクトリにアップロードする人が後を絶たなかったため、全ファイルを定期的にチェックしてチャット(Mattermost)にアラートをあげるように改良しました。
コンフィグ置き場を定期的にチェックして、異常を見つけたらチャットに通知するようにした。チャットからもHubot経由で実行できる。まるでIT企業😍 pic.twitter.com/v5ArlvdJsW
— Miyahan (@miyahancom) 2017年1月20日
やっぱりここを自動化しないとダメだよなぁ・・・。
本記事では「いらすとや」さんのイラストを使わせていただきました。
- 2016/6/10 : 新規ファイルがあるとファイル比較時にエラーで止まるバグがあったのでコード修正
- -
if filecmp.cmp(src, dst+'/'+file):
- +
if os.path.isfile(dst+'/'+file) and filecmp.cmp(src, dst+'/'+file):
- -