t-hom’s diary

主にVBAネタを扱っているブログ…とも言えなくなってきたこの頃。

Linuxの設定ファイルをデプロイする為のBashスクリプト

Linuxの設定ファイルはviやnanoなどのエディタで簡単に更新できるが、間違えると致命的な障害に繋がるためロールバックできるようにバージョン管理したい。

前回はGitBucketサーバーを建てたという記事を書いたが、実際にやりたかったのはDNS/DHCPの設定管理である。
thom.hateblo.jp

これで一応バージョン管理できるようになったが、BINDもDHCPも複数フォルダに設定が散らばっているので逐一コピーするのは面倒だし手動コピーはミスの元になる。そこで今回は本番からGit作業フォルダへ設定ファイルを取り込んだり、Git作業フォルダからデプロイする為の汎用的なBashスクリプトを書いてみた。

(参考)BINDとISC-DHCPの主要設定ファイルのフォルダ構成

/
├─etc
│  ├─bind
│  │      named.conf
│  │      named.conf.local
│  │      named.conf.options
│  │
│  ├─default
│  │      isc-dhcp-server
│  │
│  └─dhcp
│          dhcpd.conf
│
└─var
    └─cache
        └─bind
                local.thom.jp.zone

差分チェック用スクリプト「.check.sh」

Gitのリポジトリ上に構成したフォルダ構造をベースにLinux実機上の同じファイルと比較を行い差分があれば表示する。

例えばリポジトリ作業フォルダが~/dns_dhcpだとすると、
~/dns_dhcp/etc/bind/name.confと/etc/bind/name.confで差分があれば表示
~/dns_dhcp/etc/bind/name.conf.localと/etc/bind/name.conf.localで差分があれば表示
~/dns_dhcp/etc/bind/name.conf.optionと/etc/bind/name.conf.optionで差分があれば表示
~/dns_dhcp/etc/default/isc-dhcp-serverと/etc/default/isc-dhcp-serverで差分があれば表示
以下略~

という風にフォルダツリー上の全てのファイルを比較する。

コード

#!/bin/bash

while read -r f; do
	str1="${f:0:3}"
	if [ "$str1" != "./." ]; then
		if [ -f $f ]; then
			echo $f
			diff $f ${f#.}
		fi
	fi
done < <(find ./ -mindepth 1)

最初に紹介するので少し詳しめに書く。残り2スクリプトはコピペしてちょっといじくった程度なのでこれがベース。
まず一番ややこしいのはwhile文の構成。
以下を参考にほぼコピペなんだけど、一応どういう処理なのか調べてみた。
programwiz.org


前提として、whileの基本系はこう。

while 条件式
do
     繰り返す内容
done

セミコロンは単に2行を1行にまとめる為である。

while 条件式; do
     繰り返す内容
done

whileにreadとつけて変数を渡すと、リダイレクトされたファイル一覧から順次変数に格納するようなループが作れる。
VBAでいうFor Eachっぽいイメージ。

while read 変数; do
     繰り返す内容
done < ファイル一覧(空白文字や改行区切り)

元のコードではfが変数。

また、Bashでは標準でバックスラッシュがエスケープ記号扱いになってしまうため、readに-rオプションを付けるとそれを防いでくれる。
ここまでの説明を元コードに当てはめるとこうなる。

while read -r f; do
     繰り返す内容
done < ファイル一覧(空白文字や改行区切り)

最後、ファイル一覧を渡す場面ではプロセス置換という手法が使われており、<(コマンド)のように書くとコマンドの実行結果をそのまま参照できる。

つまりfindで現在フォルダ直下(./)を深度最低1、最大無限(指定なし)まで掘り下げてファイル一覧を表示すね処理がfind ./ -mindepth 1なので、これをプロセス置換でwhile read do doneに突っ込んでやると現在フォルダ直下に対してFor Each処理ができるという理屈。

ただ done <(find ./ -mindepth 1)でもうまく動く気がしてて、なんでdone < <(find ./ -mindepth 1)と、リダイレクトが1段階余計に入ってるのかは不明。
参考元サイトがそうしてて、イマイチ理由が分からないけどうまく動いてるのでいじってない。

ここまででwhile文は攻略できた。

while read -r f; do
    繰り返す内容
done < <(find ./ -mindepth 1)

str1="${f:0:3}"では、ファイル名が格納された変数fから先頭3文字を変数str1に格納している。
これはgit管理の.gitフォルダやスクリプト自身をチェック対象に含めないために"./."で始まるパス(つまり隠しファイル・隠しフォルダ)を次のIf文で除外する為の処理。
スクリプト自身が隠しファイルなので実行時は./.check.shとする。"./"は現在ディレクトリの~という意味で、そのあとの"."は隠しファイルのファイル名の接頭辞なのでとてもややこしい。。

if [ -f $f ]; thenは、$fというファイルが存在したら(-f)という意味。つまりディレクトリをチェック対象から除くためのif。

diff $f ${f#.}の部分も少しややこしい。
$fにgit作業フォルダ内のファイルパスが入っているのだが、実行パスを基準に表示されるので、ドットで始まるパスになっている。
例えば./etc/bind/name.confといった感じ。

${f#.}はその変数fから先頭から数えて一番ちかいドットを削除するという処理なので、上記の例だとドットが外れた/etc/bind/name.confとなり、本番の設定ファイル格納パスになる。

つまりdiffコマンドではGit上のパスにあるファイルと本番環境にあるファイルを比較している。

以上、長々となったけど解説終わり。

本番ファイル回収用スクリプト「.collect.sh」

本番機のファイルをGit作業フォルダに回収してくるコード。
Git作業フォルダ内が上書きされるので注意。
これはsudo ./.collect.shとして実行する。※chmod +xで実行権限を足してる場合の話。

コード

#!/bin/bash

while read -r f; do
	str1="${f:0:3}"
	if [ "$str1" != "./." ]; then
		if [ -f $f ]; then
			echo $f
			cp -pf ${f#.} $f
			chown $SUDO_USER:$SUDO_USER $f
		fi
	fi
done < <(find ./ -mindepth 1)

構成はほとんど同じ。
コピーコマンドに-pでパーミッションと所有者とタイムスタンプが保持される。※主にタイムスタンプを保持したかったため。
また、-fを付けると上書きしますかという確認をスキップして強制コピーとなる。
※環境によってはcpがcp -iにエイリアスされてる場合があるらしいのでその場合は先頭にバックスラッシュを付け\cp -fとすればエイリアス無効化された生のcpを実行できるようだ。

また、chownでオーナーをsudo実行ユーザーに変更している。
※このコマンドはsudo実行するので$USERだとrootになってしまう。

デプロイ用スクリプト「.deploy.sh」

これはGit作業ディレクトリから本番の各フォルダへデプロイするためのスクリプト。

コード

#!/bin/bash

while read -r f; do
	str1="${f:0:3}"
	if [ "$str1" != "./." ]; then
		if [ -f $f ]; then
			echo $f
			chown --reference=${f#.} $f
			chmod --reference=${f#.} $f
			cp -up $f ${f#.}
			chown $SUDO_USER:$SUDO_USER $f
		fi
	fi
done < <(find ./ -mindepth 1)

chownに--referenceが付いているが、これは本番ファイル${f#.}を参照に同じオーナーをGitHub作業フォルダのファイル$fに付与するというコマンド。
chmodも同じパーミッションを参照させて付与している。つまりデプロイ前に本番と環境を合わせているという処理である。
そしてcpに-pでタイムスタンプを維持しつつ、-uで更新アリの場合だけ更新するような処理。
タイムスタンプが本番より古い場合は更新されないので安心。

最後にGit作業フォルダ内のファイルオーナーをchowonで実行ユーザーに戻しているが、これは本番環境ファイルはroot所有だったりbind保有だったりするのでGit作業フォルダ内のファイル更新は通常ユーザー権限で更新できるようにするために戻す処理である。



さて、長々と説明が続いてしまったけど、取り急ぎこれで理想的なデプロイ環境が整った。
次回DNS・DHCPがぶっ壊れてもあんし。。。と思ったけどgitbucketへのアクセスがそもそもできなくなるか。。

まぁ少なくとも本番機のデータが飛んでもgitbucketサーバー上にコンフィグは残るので最悪の事態は避けられる。
そこらへんの対策はまた今度考えよう。

以上。

当ブログは、amazon.co.jpを宣伝しリンクすることによってサイトが紹介料を獲得できる手段を提供することを目的に設定されたアフィリエイト宣伝プログラムである、 Amazonアソシエイト・プログラムの参加者です。