はじめに
今回は、Junosの自動コンフィグバックアップ機能である Configuration Archivalを使って、Commitと同時にConfigを自動でGitにPushしてみます。
環境
このメモの内容は以下のような環境でテストしています。
- サーバ
- FreeBSD 9.3
- Ruby 2.0
- Gem: ftpd 1.1.1
- ルータ
- vSRX 12.1X47-D20.7
Configuration Archivalとは
詳しくは公式ドキュメントを読んで頂きたいところですが、一言で言うと、自動でConfigをリモートのサーバにバックアップできる機能です
バックアップのタイミングは、一定間隔毎か、Commit直後を選択可能です。また、リモートのサーバとしては、FTPあるいはSCPを選択可能です。
Junos上の設定は以下のようにして行います。
[edit system archival configuration]
archive-sites {
ftp://username:password@host/path;
}
transfer-on-commit;
転送されるファイル名は以下のようになります。このファイルはGzipで圧縮されてサーバに保存されます。
- 14.1以前
<ホスト名>_juniper.conf.n.gz_YYYYMMDD_HHMMSS
- 14.2 or 14.1X53-D30以降
<ホスト名>_YYYYMMDD_HHMMSS_juniper.conf.n.gz
FTP to Git ゲートウェイを作る
さて、この機能をただ使うだけでは、あくまでFTP/SCPにConfigをアップロードすることしかできません。単純なバックアップであれば十分かもしれませんが、やはりGit等での管理を考えたいところです。
そこで、FTPで受信したファイルをGitに自動的にPushする簡単なゲートウェイを実装してみました(今のところ、あくまで自宅で使ってるだけなので、テキトーです)。動作の流れとしては以下の様な感じです。
- FTPサーバとしてファイルを待ち受ける
- FTPでファイルを受信したら、ファイル名をパースして、ホスト名と日時を抽出
- Gzip圧縮されているか確認し、圧縮されていたら展開
- Gitのリポジトリをクローン
- ホスト名のConfigを更新
- Commit messageを生成
- Commit & Push
エラー処理とかは全くもって適当なので、実運用に使う場合は真面目に書き直すことをオススメします。認証も行っていません。というか、多分OSコマンドインジェクションとか出来ると思います……。
#!/usr/bin/env ruby
require 'ftpd'
require 'zlib'
require 'logger'
require 'tmpdir'
require 'json'
$log = Logger.new('ftp-server.log')
$log.level = Logger::DEBUG
GIT_URL = ENV['GIT_URL']
GIT_CONFIG_DIR = ENV['GIT_CONFIG_DIR']
class GitFileSystem
def initialize
end
#PathExpansion
def set_data_dir(datadir)
end
def expand_ftp_path(ftp_path)
$log.debug 'FS: called expand_ftp_path with '+ftp_path
return '/'
end
#Accessors
def accessible?(ftp_path)
$log.debug 'FS: called accessible? with '+ftp_path
return true
end
def exists?(ftp_path)
$log.debug 'FS: called exists? with '+ftp_path
return true
end
def directory?(ftp_path)
$log.debug 'FS: called directory? with '+ftp_path
return true if ftp_path == '/'
return false
end
#FileWriting
def write_file(ftp_path, stream, mode)
# todo
$log.debug 'FS: write_file: ' + ftp_path
end
def write(ftp_path, stream)
$log.debug 'WRITE: called write with ' + ftp_path
$log.info 'Receiving a file: ' + ftp_path
file_name = File.basename(ftp_path)
content = stream.read
if content[0,4].unpack("H*")[0] == '1f8b0800'
$log.debug 'WRITE: detect gziped'
begin
content = Zlib::GzipReader.new(StringIO.new(content)).read
rescue => e
$log.info 'WRITE: zlib exception...' + e.to_s
raise e
end
file_name.gsub!('.gz','')
end
$log.debug 'content: ' + content.to_s
hostname = nil
datetime = nil
if file_name=~ /.+_\d{8}_\d{6}\_juniper\.conf/
tmp = file_name.split('_')
hostname = tmp.shift(tmp.length - 3).join('_')
datetime = [tmp[0],tmp[1]].join('_')
elsif file_name=~ /.+_juniper\.conf_\d{8}_\d{6}/
tmp = file_name.split('_juniper.conf_')
hostname= tmp[0]
datetime = tmp[1]
else
return nil
end
$log.debug 'hostname: ' + hostname
$log.debug 'datetime: ' + datetime
conf_name = hostname + '.conf'
commit_message = {:hostname => hostname, :datetime => datetime}.to_json.to_s
$log.debug 'commit_message: ' + commit_message
Dir.mktmpdir do |dir|
Dir.chdir(dir) do
$log.debug 'Current dir: ' + Dir.pwd
$log.debug 'Clonning git repos...'
$log.debug `git clone #{GIT_URL} .`
raise 'GitCloneFailedException' if $? != 0
if GIT_CONFIG_DIR
Dir.mkdir(GIT_CONFIG_DIR) unless Dir.exists?(GIT_CONFIG_DIR)
Dir.chdir(GIT_CONFIG_DIR)
end
if File.exist?(conf_name)
$log.debug 'Found a current configuration'
current = File.read(conf_name)
end
$log.debug 'Writing a configuration'
File.write(conf_name, content)
$log.debug `git add #{conf_name}`
raise 'GitAddFailedException' if $? != 0
$log.debug 'Committing'
$log.debug `git commit -m "#{commit_message}" #{conf_name}`
raise 'GitCommitFailedException' if $? != 0
$log.debug 'Pushing'
$log.debug `git push origin master`
raise 'GitPushFailedException' if $? != 0
end
end
end
end
class Driver
def initialize()
$log.debug 'called Driver.initialize'
end
def authenticate(user, pass)
$log.debug 'called Driver.authenticate with ' + user
true
end
def file_system(user)
$log.debug 'called Driver.file_system with ' + user
GitFileSystem.new
end
end
raise 'GitRepositoryUndefinedException' unless GIT_URL
driver = Driver.new()
server = Ftpd::FtpServer.new(driver)
server.exception_handler = Proc.new{|e| puts e.to_s}
server.interface ='0.0.0.0'
server.port = 2221
server.log = $log
server.start
$log.info "Server listening on port #{server.bound_port}"
while true
sleep 0.1
end
コードの内容については特に説明はしませんが、まあ読み解くのに特に難しい所はないかと思います。
動作確認
実際に試してみましょう。適当なGitリポジトリを用意しておきます。ここでは例として、http://git.example.com/test.git
に認証無しでPushできるリポジトリがあることとします。
以下のコマンドで起動します。
GIT_URL="http://git.example.com/test.git" ruby ftp-server.rb
Junos機器に以下のようなConfigを投入し、Commitします。
# set system archival configuration transfer-on-commit
# set system archival configuration archive-sites pasvftp://<ftp-server>:2221/
# commit
Commitして少しすると、JunosからFTPサーバにConfigが送信され、GitへのPushが行われます。リポジトリを確認してみると…
$ git log
commit 6460ff47572d66926450fdcb33cee424f28efd86
Author: Junos <home-junos@home.example.net>
Date: Sun Dec 27 21:11:47 2015 +0900
{hostname:rt01,datetime:20151227_121124}
commit 26b6d8b57b1d56ee86c390e25b2f068cda87c987
Author: Kazubu <kazubu@git.example.net>
Date: Sun Dec 27 20:24:55 2015 +0900
initial commit
$ cat rt01.conf
## Last changed: 2015-12-27 21:11:20 JST
version 12.1X47-D20.7;
system {
host-name rt01;
time-zone Asia/Tokyo;
...<snip>...
Configが自動的にGitにPushされていることが確認できます。
ここで、Configを適当に編集して、再度Commitしてみましょう。
# set interfaces ge-0/0/0 description "Home Flets NGN"
# commit
そして、Gitリポジトリを確認してみると…
$ git log
commit ced44dea7bf623393b86a8dc84d4bad9d87dbcbf
Author: Junos <home-junos@home.example.net>
Date: Sun Dec 27 21:17:36 2015 +0900
{hostname:rt01,datetime:20151227_121708}
commit 6460ff47572d66926450fdcb33cee424f28efd86
Author: Junos <home-junos@home.example.net>
Date: Sun Dec 27 21:11:47 2015 +0900
{hostname:rt01,datetime:20151227_121124}
commit 26b6d8b57b1d56ee86c390e25b2f068cda87c987
Author: Kazubu <kazubu@git.example.net>
Date: Sun Dec 27 20:24:55 2015 +0900
initial commit
$ git diff 6460ff ced44d
diff --git a/rt01.conf b/rt01.conf
index 0ab3412..59e61cd 100644
--- a/rt01.conf
+++ b/rt01.conf
@@ -1,4 +1,4 @@
-## Last changed: 2015-12-27 21:11:20 JST
+## Last changed: 2015-12-27 21:16:45 JST
version 12.1X47-D20.7;
system {
host-name rt01;
@@ -56,6 +56,7 @@ system {
}
interfaces {
ge-0/0/0 {
+ description "Home Flets NGN";
unit 0 {
encapsulation ppp-over-ether;
}
こんな感じで、変更が正しくCommit & Pushされているはずです
さいごに
テキトーではありますが、JunosでのCommitをトリガーにして、GitリポジトリのConfigを更新する事が実現できました。やったね!
Junoserや、Commit Scriptを組み合わせる事によって、もっといろいろ楽しいことができるはずです。
是非参考にして頂けると幸いです。