10
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

JunosのConfiguration Archival機能を使ってコンフィグを自動でGitにPushしてみる

Last updated at Posted at 2015-12-27

はじめに

今回は、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上の設定は以下のようにして行います。

sample.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する簡単なゲートウェイを実装してみました(今のところ、あくまで自宅で使ってるだけなので、テキトーです)。動作の流れとしては以下の様な感じです。

  1. FTPサーバとしてファイルを待ち受ける
  2. FTPでファイルを受信したら、ファイル名をパースして、ホスト名と日時を抽出
  3. Gzip圧縮されているか確認し、圧縮されていたら展開
  4. Gitのリポジトリをクローン
  5. ホスト名のConfigを更新
  6. Commit messageを生成
  7. Commit & Push

エラー処理とかは全くもって適当なので、実運用に使う場合は真面目に書き直すことをオススメします。認証も行っていません。というか、多分OSコマンドインジェクションとか出来ると思います……。

ftp-server.rb
#!/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します。

auto-archive.junos
# 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を組み合わせる事によって、もっといろいろ楽しいことができるはずです。
是非参考にして頂けると幸いです。

10
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?