忙しい人向けのserverspec入門 ~HelloWorld編(解説)~
目的
前回と同じ。serverspecを使ってみたいけど、導入が大変そうで抵抗を感じている人に、 serverspecの便利さを体験してもらう。
とりあえず
前回、hello worldしただけで、解説がまだだったので、解説する。
やってること
HelloWorld 編で作ったserverspecは下記の流れでテストを実行する。
Rakefile を読み込んで、rakeタスクを実行する
rakeタスクは spec/ディレクトリの配下の
*_spec.rb
という名前のファイルを探索して実行する。(ここでは、192.168.56.201/sample_spec.rb
が該当する) を実行するsample_spec.rb 内でrequireされた
spec_helper.rb
が必要なライブラリのインポート 、その他必要な準備(SSH接続とか)を行うsample_spec.rb 内で記述された rspec形式のテストを実行する
(どれも重要なのだけど)重要なのは4の、sample_spec.rb に記述されたrspec形式のテスト。 serverspecのテストを書く時間の8割はこのsample_spec.rb のようなrspec形式のテストを書く時間である。
sample_spec.rbのテストを書いてみると、こんな感じ
require 'spec_helper' describe package('httpd'), :if => os[:family] == 'redhat' do it { should be_installed } end describe package('apache2'), :if => os[:family] == 'ubuntu' do it { should be_installed } end describe service('httpd'), :if => os[:family] == 'redhat' do it { should be_enabled } it { should be_running } end describe service('apache2'), :if => os[:family] == 'ubuntu' do it { should be_enabled } it { should be_running } end describe service('org.apache.httpd'), :if => os[:family] == 'darwin' do it { should be_enabled } it { should be_running } end describe port(80) do it { should be_listening } end
describe
と記述されたブロックが全部で7つあるが、このうち、テストとして実行されるのは、
1,3,7 番目のブロックのみ。他は、os[:family] == 〇〇
の記述によりスキップされる。
※このスキップの機能は、ディストリビューションが混在しているようなクラスタ環境で テストをするときや、複数のディストリビューションに対応したansibleのroleなどのテストを する際に効果を発揮する。
忙しい人向けのserverspec入門 ~HelloWorld編~
目的
serverspecを使ってみたいけど、勉強する時間がなかなか取れず、使い始めるのを躊躇している人のために、 とりあえずserverspecの便利さを体験してもらいたい。
動機
serverspecを使ってみたいけど、導入するための準備がなかなか進まない、という話を最近よく耳にするのがきっかけ。
serverspecは普段ansibleで作ったLinux環境をテストするのに頻繁に使っているので、個人的には使い手が 増えてほしいツールの一つなのだが、確かに、準備をするには面倒な作業が必要になる。 そのため、敷居を高く感じてしまう人もいると思う。
全く何もないWindows環境でserverspecを動かせる環境を作るには、少なくとも下記のステップを踏まなければならない。
Linuxサーバーを少なくとも2台(serverspecを実行するサーバーと、テスト対象のサーバー)用意する
serverspec を実行するサーバーに、ruby,gem, bundler をインストールする
servespec を実行するサーバーから、テスト対象のサーバーにSSHで接続できるようにする
rake コマンドでserverspecを実行
といった感じである。 Linuxやrubyを使い慣れている人にとってはなじみの作業が多いが、慣れていない人には、hello world するだけでも かなりの時間と労力を要する作業になってしまう。
ただ、ここで挫折して、serverspecを使わずに手作業で、リリースのたびに細かい環境テストを実行するのは非常に時間がもったいないし、 管理するサーバーの台数が増えればミスも増えてくるだろうから、お客さんにとっても、開発者にとっても、不幸な結果を 生みかねない。 単純な作業はserverspecに肩代わりしてもらって、余った時間を別の作業に充てたほうが、双方ハッピーになれるはずである。
なので、上記の作業に慣れていない人でも30分ぐらいで HelloWorldにこぎつけられるようにして、気軽にserverspecの導入を検討してもらえるようになってほしい。
動作環境 バージョン情報
Windows10
Vagrant2.0
VirtualBox 5.1.26
前提
Vagrant,VirtualBoxはインストール済みである前提です。(インストール方法は公式サイトを見てください。ごめんなさい)
やること
1. Linux サーバーを2台用意する & 2. serverspec を実行するサーバーに、ruby,gem, bundler をインストールする
いつも通り、下記のVagrantfileを作成して、VirtualBoxの仮想マシンを2台作ります。
Vagrant.configure(2) do |config| SERVERS.map do |hostname, ipaddress| config.vm.define hostname.to_s do |conf| conf.vm.provider "virtualbox" do |vb| vb.name = hostname.to_s vb.memory = "2048" end conf.vm.box = "bento/centos-7.2" conf.vm.hostname = hostname.to_s conf.vm.network "private_network", ip: ipaddress conf.vm.provision :shell, inline: <<-SHELL for interface in $(grep 'NM_CONTROLLED=no' /etc/sysconfig/network-scripts/ifcfg-* -l) do sudo sed -i -e 's/NM_CONTROLLED=no/NM_CONTROLLED=yes/' $interface sudo ifdown $interface sudo ifup $interface done SHELL conf.vm.provision :shell, inline: <<-SHELL if hostname.to_s == 'serverspec' sudo yum install -y git sudo git clone https://github.com/sstephenson/rbenv.git /usr/local/rbenv sudo git clone https://github.com/sstephenson/ruby-build.git /usr/local/rbenv/plugins/ruby-build sudo touch /etc/profile.d/rbenv.sh sudo chmod 777 /etc/profile.d/rbenv.sh echo 'export RBENV_ROOT="/usr/local/rbenv"' >> /etc/profile.d/rbenv.sh echo 'export PATH="${RBENV_ROOT}/bin:${PATH}"' >> /etc/profile.d/rbenv.sh echo 'eval "$(rbenv init -)"' >> /etc/profile.d/rbenv.sh sudo chmod -R 755 /usr/local/rbenv sudo yum install -y gcc openssl-devel readline-devel zlib-devel bundler sudo -i rbenv install 2.4.2 sudo -i rbenv global 2.4.2 sudo -i -u vagrant mkdir /home/vagrant/serverspec sudo -i -u vagrant ssh-keygen < <(echo; echo) SHELL conf.vm.provision :shell, inline: <<-SHELL if hostname.to_s == 'webserver' sudo yum install -y httpd sudo systemctl start httpd sudo systemctl enable httpd SHELL end end end SERVERS = { serverspec: '192.168.56.200', webserver: '192.168.56.201', }
上記のVagrantfile を適当なディレクトリに保存したあと、コマンドプロンプト(PowershellWindow)でそのディレクトリにcd
して、vagrant up
コマンドを実行すれば、VirtualBox上にLinuxサーバーが2台出来上がります。
出来上がったサーバーの簡単な情報を下表に示します。
# | サーバー名 | 概要 | IPアドレス | ログインユーザー | パスワード |
---|---|---|---|---|---|
1 | serverspec | サーバースペックを実行するサーバーです。ruby, gem, bundler はインストール済みです。 | 192.168.56.200 | vagrant | vagrant |
2 | webserver | テスト対象のサーバーです。apache httpdがインストール済みで、サービスが立ち上がっています。 | 192.168.56.201 | vagrant | vagrant |
出来上がったサーバーには、TeraTerm等のSSHクライアントを使ってアクセスしてください。
3. servespec を実行するサーバーから、テスト対象のサーバーにSSHで接続できるようにする
テスト対象のサーバーにSSHで接続するには、テスト対象のサーバーの~/.ssh/authorized_keys
に、serverspecサーバーの公開鍵情報を
書き込んであげる必要があります。
コピペでやってもいいですが、せっかくなので、 ssh-copy-id
を使います。
serverspec サーバーにSSH でログインしたら、下記のコマンドを実行しましょう。
ssh-copy-id -i ~/.ssh/id_rsa vagrant@192.168.56.201
上記のコマンドを実行すると、2回英語で質問されるので、1回目はyes
2回目は、vagrantユーザーのパスワードをそれぞれ答えてください。
終了すると、webserver の /home/vagrant/.ssh/authorized_keys
に、serverspecサーバーの公開鍵情報がめでたく書き込まれます。
ssh-copy-id
コマンドは、ディレクトリの作成やパーミッションの設定などを適宜やってくれるので、コピペに比べ、ミスも少なく安心です。
SSHで接続できることを確認するには、serverspecサーバー上で下記のコマンドを実行してください。
ssh vagrant@192.168.56.201 : ;echo $?
このコマンドを実行して、標準出力に0
と表示されれば設定は成功しています。
パスワードを聞かれたり、0以外の数字が出力され場合は、ssh-copy-id
コマンドを再度実行してみるか、webserverの/home/vagrant/.ssh/authorized_keys
に
serverspecサーバーの公開鍵情報をコピペしてください。
4. テストコードを書く
これでやっとserverspecを実行するための前提条件が整いました。
ここからは、『webserverにapacheがインストール済みであることを確認するテスト』を実行するまでの手順を説明します。
まず、serverspecサーバーで、下記コマンドを実行してください。
cd ~/serverspec sudo -i gem install serverspec rake
コマンドが成功したら、下記コマンドを実行して、必要なファイルを生成します。
serverspec-init
いくつか質問されるので、下記の通り答えましょう。
Select OS type:
1) UN*X
2) Windows
Select number: 1
Select a backend type:
1) SSH
2) Exec (local)
Select number: 1
Vagrant instance y/n: n
Input target host name: 192.168.56.201
ここまで出来たら準備完了。
下記のようなファイル、ディレクトリが出来上がるはずです。
/home/vagrant/serverspec/ | |- Rakefile |- spec_helper.rb |- spec/ |- 192.168.56.201/ |- sample_spec.rb
ここから、sample_spec.rbを編集して、テストをすることになるのですが、serverspec-init
コマンドで作成したsample_spec.rbには、
幸いにも、「webserverにapacheがインストール済みで、80番ポートで待ち構えていることを確認する」という内容のテストが
すでに記述されています。
ので、ひとまず実行します。
rake spec
コマンドを実行すると、オールグリーンでテストが終了します。
※webserverのapacheを止めたり、アンインストールしたりすると、それぞれテストが失敗するので、やってみてください。
やっていること。。。の説明は次回
前置きが長くなってしまったので、serverspecが実行している処理の中身や、制御方法については次回解説します。
awspecを使って、AWSリソース(セキュリティグループ)のテストを自動化してみる
動機
前回に引き続き、AWS環境周りのテスト。 LocalStackで動作確認、motoでmockテストができるようになったところで、本番環境向けにテストしてみたいなと思ってた矢先、 プロが awspec
という、AWSリソースのテスト(assertion機能付き)フレームワークがあると教えてくれたので、早速使ってみた。
バージョン情報
Windows10
Vagrant2.0
VirtualBox 5.1.26
CentOS 7.2
ruby 2.4.2
ゴール
こんな感じのセキュリティグループを以下の観点でテストしてみたい。
1.Name タグが sampleであること
2.22番ポートはIPアドレス 10.10.10.10 からのみアクセス可能(のみ、が重要)
3.3389番ポートはどこからでもアクセス可能(!)
事前準備
公式手順に従って、ruby gem から awspecをインストールするだけなので省略。。。したいところだが、ただ実行するだけだと、公式ドキュメントの丸パクリ(というか劣化版)になってしまう。 ので、インフラエンジニアの端くれとして多少の差別化をはかり、(申し訳程度に)Vagrantfileを添付することにする。
Vagrant.configure(2) do |config| config.vm.provider "virtualbox" do |vb| vb.name = "awspec" vb.memory = "2048" end config.vm.box = "bento/centos-7.2" config.vm.hostname = 'awspec' config.vm.network "private_network", ip: "192.168.100.201" config.vm.provision :shell, inline: <<-SHELL for interface in $(grep 'NM_CONTROLLED=no' /etc/sysconfig/network-scripts/ifcfg-* -l) do sudo sed -i -e 's/NM_CONTROLLED=no/NM_CONTROLLED=yes/' $interface sudo ifdown $interface sudo ifup $interface done SHELL config.vm.provision :shell, inline: <<-SHELL sudo yum install -y git sudo git clone https://github.com/sstephenson/rbenv.git /usr/local/rbenv sudo git clone https://github.com/sstephenson/ruby-build.git /usr/local/rbenv/plugins/ruby-build sudo touch /etc/profile.d/rbenv.sh sudo chmod 777 /etc/profile.d/rbenv.sh echo 'export RBENV_ROOT="/usr/local/rbenv"' >> /etc/profile.d/rbenv.sh echo 'export PATH="${RBENV_ROOT}/bin:${PATH}"' >> /etc/profile.d/rbenv.sh echo 'eval "$(rbenv init -)"' >> /etc/profile.d/rbenv.sh sudo chmod -R 755 /usr/local/rbenv sudo yum install -y gcc openssl-devel readline-devel zlib-devel sudo -i rbenv install 2.4.2 sudo -i rbenv global 2.4.2 sudo -i gem install awspec sudo yum install -y epel-release sudo yum install -y python-pip sudo pip install pip --upgrade sudo pip install awscli SHELL end
、、、上記のVagrantfileで作った環境にSSHでログインしたら、aws configure
を実行して、credentialを登録。
そのあと、 awspec init
を実行し、spec/seacrets.yml を下記のように記述すれば準備完了。
region: ap-northeast-1 aws_access_key_id: XXXXXXXXXXXXXXXXXXXX aws_secret_access_key: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
実行するコード
spec/security_group_spec.rb を下記のように記述する。
require 'spec_helper' describe security_group('セキュリティグループID') do it { should exist } it { should have_tag('Name').value('sample') } its(:inbound) { should be_opened_only(22).for('10.10.10.10/32')} its(:inbound) { should be_opened(3389).protocol('tcp').for('0.0.0.0/0')} end
結果
上記のコードを保存したら、Rakefileのあるディレクトリで、rake spec
を実行すればテスト完了。
下記のようにオールグリーンのコンソールが出ればO.K.
やってること
記述や実行方式は、serverspecやrspecと同じ。
1. have_tag
を用いて、タグのチェックを行っている。
2. be_opened_only
を用いて、22番ポートは、10.10.10.10/32から「のみ」アクセスを許可していることをチェックしている。
3. be_opened
を用いて、0.0.0.0/0 に対してアクセスを許可していることをチェックしている。
感想
serverspecに慣れ親しんでいる身としては、この方式(環境も)でテストが書けるのはうれしい。
作成した環境の内部の設定はserverspecで今までチェックできたが、セキュリティグループが「許可されている」ことは確認できても、 「余計なものが許可されていない」ことを確認するのは難しかった。 セキュリティグループに限らず、テストの範囲をEC2インスタンスの内部だけでなく、リソース全体に広げることができるので、テストの質が上がりそう。
また、前回、前々回の記事と併せて考えると、下記のような感じで環境構築作業、確認作業が自動化できそう。
(実装フェーズ)
1.LocalStackでCloudFormation や リソース作成の動作確認
2.motoでboto3スクリプトの自動テスト
(テストフェーズ)
3.awspecでリソース作成確認
4.serverspecでサービスの動作確認
どのツールがどの程度まで品質を担保できるものなのかは、もうちょっと使ってみてからまたブログに書きます。
蛇足 ~ be_opened_only のちょっと意地悪な例 ~
be_opened_only
の揚げ足を取るわけではないのだが、下記のような設定のセキュリティグループ(22番ポートにたいして、10.10.10.9/32と10.10.10.10/32 のIPアドレスを2つ列挙)に対して、10.10.10.8/30 からのみ許可しているかどうかテストすると、感覚的にはテスト成功しそうだが、実際はテスト失敗になる。(ブロードキャストアドレスと、ネットワークアドレスを入れても同様)
require 'spec_helper' describe security_group('sg-eb68a092') do it { should exist } it { should have_tag('Name').value('sample') } its(:inbound) { should be_opened_only(22).for('10.10.10.8/30')} its(:inbound) { should be_opened(3389).protocol('tcp').for('0.0.0.0/0')} end
。。。しかし、こんな「ポリシーがあるのかないのかよくわからないセキュリティポリシー(笑)」をテストするようなこともないだろうし、あったとしても、本来NGになるべきものがOKになっているわけではないので、無視していいでしょう。
motoをつかってboto3(AWS PythonSDK)スクリプトのテストを自動化する
動機
前回、LocalStackを使ってboto3のテストを実行してみて、リソース操作系スクリプトの動作確認を気軽に行えるようになった。
しかし、LocalStackは、EC2などのサービスには(2018年2月現在)未対応であったり、異常系のテストがエミュレートできないものが あったりと、現場で使うにはまだ改善点が多いことが分かったので、別の方法も考えてみることに。
で、情報を探してみたら、moto github.com
、、、というboto3のエミュレートを行えるライブラリを発見したので、使ってみた。
バージョン情報
Python 3.6.3
boto3 1.4.7
moto 1.2.0
対象のスクリプト
こんな感じの、「S3バケットを作成し、コマンドの実行結果を標準出力に出力する」ような スクリプトのテストを考えてみる。
import boto3 from botocore.exceptions import ClientError """ S3 にバケットを作成します。 """ bucket_name = 'kudarizakawonobore' SUCCESS = 0 ERROR = 1 def main(): client = boto3.client('s3') try: client.create_bucket(Bucket=bucket_name , CreateBucketConfiguration={ 'LocationConstraint': 'ap-northeast-1' } ) print('SUCCESS: バケットを作成しました') return SUCCESS except (ClientError) as e: print(e.response['Error']['Message']) error_code = e.response['Error']['Code'] if error_code == 'BucketAlreadyExists': print('ERROR: 他の人が使っている名前です') return ERROR else: print('予期せぬエラー') raise e if __name__ == '__main__': response = main() exit(response)
テスト側
上記のスクリプトを、正常系、異常系含めてテストするには、下記のようなテストを書く。
import boto3 import src.create_bucket as target import unittest from io import StringIO from unittest.mock import patch from moto import mock_s3 class TestCreateS3(unittest.TestCase): @mock_s3 def test_main(self): """正常系""" with patch('sys.stdout', new_callable=StringIO) as out: self.assertIs(target.main(), target.SUCCESS) print(out.getvalue()) self.assertTrue(out.getvalue().find('SUCCESS:')) @mock_s3 def test_main_error_bucket_already_exists(self): """すでに存在する場合はメッセージを表示して、ERRORコードを返す""" client = boto3.client('s3') client.create_bucket(Bucket=target.bucket_name, CreateBucketConfiguration={ 'LocationConstraint': 'ap-northeast-1' } ) with patch('sys.stdout', new_callable=StringIO) as out: self.assertIs(target.main(), target.ERROR) self.assertTrue(out.getvalue().find('ERROR: ')) if __name__ == '__main__': unittest.main()
やってること
motoを使うために必要な操作は二つで、テストケースでfrom moto import mock_s3
でmoto
をインポートするのと、
テスト用のメソッドに、 @mock_s3
とデコレータを付与すること。これだけ。
@mock_s3
のデコレータを付与されたメソッド内では、S3 関連の操作がすべてモック化される。
正常系のテストtest_main
では、S3バケットが一つも存在しない空間にS3のバケットを作成しにいくので、正常終了する。
一方、異常系のテストtest_main_error_bucket_already_exists
では、一度同名のバケットを作成してからスクリプトを
実行しているので、ClientErrorが発生して異常終了する。
ちょっとした問題点
異常系のパターンをすべて網羅しているわけではない。
例えば、S3のCreateBucketでは、BucketAlreadyOwendByYou のエラーが発生するパターンがエミュレートできない。
具体的にいうと、本来 motoを使わずスクリプトを2回実行する(すでに存在するバケットを再度作成する)と、BucketAlreadyExists(他の人が使っている)ではなく、BucketAlreadyOwnedByYou(自分がすでに所有している)エラーが発生するべきである。しかし、test_main__error_bucket_already_exists
で記述している通り、同一のコンテキストでバケットを2回作成したとしても、発生するエラーはBucketAlreadyExists となってしまう。
(motoのソースコードを少し見てみたが、 BucketAlreadyOwendByYouのエラーを発生させることはできなさそう。。。)
そのため、create_bucket
を行う前に、同名のバケットが存在しないか事前チェックを行うなど、実装側の方でカバーしたり、テストの書き方を工夫する必要がある。
LocalStackとの住み分け
このぐらい単純なスクリプトのテストだと、motoの方が簡単にテストできるので優れていそうだが、大量のリソースを操作するスクリプトや、CloudFormationの動作確認などでは、ダッシュボード機能でリソースの状態を確認できるLocalStackの方が活躍する場面が増えてくる(と思う)
どちらもまだ使いこなせていないので、引き続き学習していきます。
LocalStackを使ってPythonSDK(AWS)の動作確認をしてみた
概要
AWS環境構築用にboto3 や AWS CLIを使っているのだが、動作確認するだけで自分の環境が汚れてしまうのがどうも気にくわない。
また、スクリプト自体はunittest や shunit2 で単体テストを回して品質を担保したいが、一番重要なAWSコマンド実行の部分を動作させられないといまいち。
環境を汚さず品質担保するうまい方法はないかプロに聞いてみたら、LocalStackというのがあるからやってみれ、とのことだった。 ので、ひとまず使ってみた。
ゴール
WindowsのVirtualBox上に、LocalStackのサーバーを立てて、LocalStackに向けてS3のバケット作成コマンド実行する。
バージョン情報
Windows10
VirtualBox 5.1.30
Vagrant 2.0
Python3.6
結論
LocalStack仮想サーバーを構築する
下記のVagrantfileをローカルの適当なディレクトリに作成して、vagrant up
を実行する。
(IPアドレス、ポート、メモリサイズ等は環境に合わせて変更してください)
Vagrant.configure(2) do |config| config.vm.provider "virtualbox" do |vb| vb.name = "local_stack" vb.memory = "2048" end config.vm.box = "bento/centos-7.2" config.vm.hostname = 'localstack' config.vm.network "private_network", ip: "192.168.100.201" config.vm.provision :shell, inline: <<-SHELL for interface in $(grep 'NM_CONTROLLED=no' /etc/sysconfig/network-scripts/ifcfg-* -l) do sudo sed -i -e 's/NM_CONTROLLED=no/NM_CONTROLLED=yes/' $interface sudo ifdown $interface sudo ifup $interface done SHELL config.vm.provision :shell, inline: <<-SHELL sudo yum install -y docker sudo systemctl start docker sudo systemctl enable docker sudo -i docker run -itd -p 4567-4583:4567-4583 -p 8080:8080 localstack/localstack SHELL end
ブラウザで http://192.168.100.201:8080 にアクセスして、下記のような画面(ダッシュボード)が表示されれば、LocalStackの構築は完了。
LocalStackに向けて create bucketを実行する
以下のようなPythonコードを実行する。 boto3のインストール方法については割愛。
import boto3 as bt3 client = bt3.client('s3', endpoint_url='http://192.168.100.201:4572') client.create_bucket(Bucket='kudarizakawonobore-bucket')
実行成功したら、 LocalStackのダッシュボードを確認すると、S3のバケット作成が行われたことが確認できる。
やってること
vagrantのプロビジョニングでdocker のインストールとコンテナの構築を実施。
コンテナ作成時にポート4567~4583 までを仮想マシンのポートとつなげておけば、仮想マシンのポートをエンドポイントに指定するだけで応答を返してくれる。
(ダッシュボードは8080)
vagrantが使える環境であれば割と簡単に実行できる。
課題
今回はとりあえず動かしてみただけだが、単体テスト実行時だけは LocalStackを向いて、本番実行するときは所望の環境に向いてくれるように設定するのが好ましい。 やり方についてはこれから検討予定。
プロビジョニングツールのスレーブノードに公開鍵をいっせいに配布する
動機
サーバープロビジョニングにansibleやchefを使う前準備として、hostサーバーの公開鍵を複数台のスレーブノードに配布する必要がある。
ノードの数が2~3台程度なら、手順書を作って、各サーバーにsshログインして、エディタでコピペ、という手順でも大した苦にならないが、
サーバーの数が増えてくると、手順書を作ったり、実行したりするだけでもかなりの時間を食ってしまう。
目的
ansibleやchefのスレーブノードに、hostサーバーの公開鍵をコマンド1発で配布できるようにする。 (あと、手順書を簡略化する。)
バージョンとか
クライアントPC : Windows10
ansibleホスト・スレーブ : CentOS7.2
前提
クライアントから各スレーブノードにsshのパスワード認証ログインができる。
クライアントPCでbash,ssh,sshpassが使える。(sshpassはwindows Bashのapt-getでインストールできる。)
結論
sshリモートコマンドでばらまく。
Windows10からはコマンドプロンプトからbashが使えるので、下記のようなBashスクリプトを書いて実行する。
(ネットワーク設定が適切にされていれば、VirtualBox上のLinux仮想マシンでも同様のことができるはず。)
#!/bin/bash read key < id-rsa.pub while read ip user pass;do sshpass -p $pass ssh ${user}@${ip} "echo $key >> ~/.ssh/authorized_keys" < /dev/null done <<SERVER 192.168.XXX.XXX user1 pass1 192.168.YYY.YYY user2 pass2 SERVER
※ id-rsa.pub はプロビジョニングツールホストで作成した公開鍵。
ログインするユーザーと、スレーブノードのユーザーが異なる場合は、こんな感じ。 (ログインユーザーがnopassでsudo実行できる前提)
#!/bin/bash read key < id-rsa.pub while read ip loginuser loginpass user ;do sshpass -p $loginpass ssh ${loginuser}@${ip} "sudo -u $user bash -l" <<<"echo $key >> ~/.ssh/authorized_keys" done <<SERVER 192.168.XXX.XXX loginuser1 password1 user 192.168.YYY.YYY loginuser2 password2 user SERVER
やってること
key
変数に公開鍵情報を格納し、sshリモートコマンドで、authorized_keysに追記リダイレクトしている。
ログインユーザーとスレーブノードユーザーが異なる場合は、チルダ展開されるタイミングを、
sudo bash -l
で環境変数が初期化された後に調整する必要があるため、ヒアストリングでコマンドを渡すようにしている。
補足
踏み台サーバーを経由するような構成の場合は、少し変更が必要。
踏み台サーバーからスレーブノードへssh公開鍵認証が設定済みの場合
sshの公開鍵認証が通っているような構成であれば、sshpassを使わず、同様のコマンドで鍵をばらまくことができる。
#!/bin/bash read key < id-rsa.pub while read ip loginuser user ;do ssh ${loginuser}@${ip} "sudo -u $user bash" <<<"echo $key >> ~/.ssh/authorized_keys" done <<SERVER 192.168.56.101 loginuser1 user 192.168.56.102 loginuser2 user SERVER
踏み台サーバーからスレーブノードへパスワード認証でログインする構成で、かつ、rbashでコマンド制限されている場合。
この場合は、ターミナルのマクロを使って実行する必要がある。(後日githubで公開予定)
注意
authorized_keysへの追記リダイレクト>>
を、間違えて上書きリダイレクト>
にしてしまうと、
踏み台サーバーからログインできなくなって大変なことになるので、扱いは慎重に。
蛇足
書き込み完了ごとに、確認を入れたり、継続/終了を判断したい場合は、こちらの記事を参考にして、 プログラムを拡張してください。
bashのselect文を使ってwhile readの自動処理を半自動化する
目的
bash の while read 文を実行しているとき、決まった場所で処理を中断して、 続行か否かを判断する処理を挟めるようにする
動機
while read文で処理を回している最中、特定の場所で処理を中断して、目視による
確認手順を差し込みたい場合がある。
そういうときに、ループを回しながら、確認を求めてくる、「半自動化」みたいな処理が
できると便利だったりする。
結論
bash の select文を使って、こんな感じのコードにする
#!/bin/bash exec 254<&0 while read PARAM;do echo $PARAM # ループしたい処理 select answer in "continue" "exit";do case "$answer" in continue) break ;; exit) exit ;; esac done <&254 done <<PARAM param1 param2 param3 PARAM
上のコードは、echo文でparam1,param2,param3を順次標準出力に出力していくループの 中で、次のecho文を実行するか、中断するか、確認を求めてくれる。
解説(やってること)
while read 文でヒアドキュメントを読み込んで順次処理を実行している中で、select文を読み込んであげているだけなのだが、一つだけ注意しなければいけない点がある。
ファイルディスクリプタの複製を使う
大事なのが、2行目の exec 254<&0
と、11行目の done <&254
このふたつをやっておかないと、while read文の中でselect文を実行しても、キーボードからの入力を求めてくれないばかりか、ループ処理もparam1を処理しただけで終わってしまう。
これは、while read文のdo
からdone
までの間に実行されるプロセスの、ファイルディスクリプタの0番(標準入力)が、
12行目のdone <<PARAM
によって、ヒアドキュメントに切り替えられているために起きる現象である。(select文が、ヒアドキュメントの方を読み込みに行ってしまう)
なので、事前にキーボード入力のファイルディスクリプタを、別の番号(ここでは254番)に複製しておいてあげて、 select文を実行するときに、あらかじめ複製しておいたファイルディスクリプタを指定してあげれば、select文の実行時にキーボードから入力を求めてくれるようになる。
応用編
(いまどき使う場面があるか不明だが)たとえば↓みたいなコードを使えば、postgreでSQLを順次実行しながら、 コミットするかロールバックするか確認を取ってくれるようになる。
#!/bin/bash # 事前処理(オートコミットをOFFにする) prepare(){ echo "\set AUTOCOMIIT off";} # トランザクション開始 begin(){ echo "BEGIN;"; } # コミットして続行か、ロールバックか、任意のコマンドを実行するか選ばせる menu(){ select cmd in "continue" "rollback" "exit";do if [ -z "$cmd" ];then echo "$REPLY" continue fi case "$cmd" in continue) echo "COMMIT;" return 0 ;; rollback) echo "ROLLBACK;" echo "\q" return 1 ;; exit) echo "\q" return 1 ;; esac done <&254 } # update_tbl1.sql, update_tbl2.sql(同一トランザクション)を実行したのち、 # 確認を求めてからupdate_tbl3.sqlを実行する output_sql(){ prepare while read sqlfiles;do cat <(begin) $sqlfiles echo "done $sqlfiles !" >&2 menu || return 1 done <<FILES update_tbl1.sql update_tbl2.sql update_tbl3.sql FILES } # ファイルディスクリプタを複製する exec 254<&0 # 標準出力にSQLを出力する output_sql
上で作ったsample.shを、下記のように、psqlコマンドに渡してあげれば、psqlで順次SQLを実行してくれる。
$ bash sample.sh | psql -U USER -d DBNAME
下記のような選択肢が出てくるので、コミットして続行する場合は1を、ロールバックして終了する場合は2を選択する。
番号ではなく、例えばselect * from tbl1;
みたいなコマンドを入力すると、その通り実行してくれる。
1) continue 2) rollback 3) exit
psqlの標準出力と、select文のプロンプトが混ざって見づらい場合は、
多少めんどくさいが、psql側の標準出力をファイルにリダイレクトしてあげて、
別ターミナルからtail -f
コマンドなどでみるようにするといい
$ bash sample.sh | psql -U USER -d DBNAME > dbconsole 2>&1
$ tail -f dbconsole
蛇足
実行したSQLの記録を取りたい場合は、間にteeコマンドを挟んであげる
$ bash sample.sh | tee backup.sql | psql ...