はじめに言っておきます。相当環境特化しているのでマニアックです。
WindowsでもLinuxコマンドをPowerShellから実行できます。 Capistarno実行に一々 Linuxへログインしなくていいゆとりができるんだ記事です。 海外では案外と同じようなことしている人が多いようです。
Windowsに純正のSSH接続が出来るモジュールは、PowerShellにはありません。 しかし、chocolateyパッケージ管理モジュールをインストールして、パッケージとしてmsysgitをインストールすることでGitが利用できます。
gitパスにはsshがあるので、これを利用すればPowerShellコンソール上でSSHが出来るわけです。
所謂PoshGitも同様の仕組みな訳ですが、それでは面白くありません。今回は、以下の3点を実現します。
- Windows PowerShellにchocolateyをインストールしmsysgitをパッケージインストールしsshを入手する
- Windows PowerShellからSSH経由でLinuxにコマンドを飛ばして結果を取得する
- Windows PowerShellからSSH経由でCapistranoがインストールされたLinuxサーバーにCapコマンドを送信し、結果を取得する
これでワンポチWindowsからLinux Deployを目指します。 Linux大好きですが、Windowsからバッチ一発で実行できる方が嬉しいですよねー。ということです。
全体像
少し長いので先に全体像を。 利用者が実際に利用するのは2つだけです。
- Invoke-SshCommand
- Invoke-CapistranoDeploy
GitHubで公開しておきます。
guitarapc/PowerShellUtils - PS-SshConnection | GitHub
モジュール設置
モジュールに同梱されたinstall.batを実行すればmodule pathにモジュールがコピーされます。
後はPowerShellで利用できるはずです。 自分で好きなパスに起きたい場合は、各セッションでそのパスに移動してImport-Module PS-SshConnectionでモジュールをインポートしてください。
chocolatey や ssh のインストール
明示的なインストールをする必要はありません。 各ssh接続時に、 自動的にchocolateyとmsygitパッケージをインストールします。
Invoke-SshCommand
本コマンドで、 sshコマンドを対象のサーバーに送信します。 利用する際のサンプルフォーマットです。
Invoke-SshCommand -rsaKey "RSAキーパス" -user sshユーザー名 -hostip 接続先サーバーIP -command "ssh先で実行するコマンド" # Optionがある場合は 2つまで設定可能 Invoke-SshCommand -rsaKey "RSAキーパス" -user sshユーザー名 -hostip 接続先サーバーIP -command "ssh先で実行するコマンド" -option sshコマンドオプション # Optionがある場合は 2つまで設定可能 Invoke-SshCommand -rsaKey "RSAキーパス" -user sshユーザー名 -hostip 接続先サーバーIP -command "ssh先で実行するコマンド" -option sshコマンドオプション -option2 sshコマンドオプション2
利用する際は、例えばこのようにします。
Invoke-SshCommand -rsaKey c:\ssh\rsa -user lunuxuser -hostip 192.168.1.1 -command "hostname"
実行するとこのようなログと結果が出力されます。 (最終行のLinux-ServerNameが結果。他はwrite-hostコマンドレットで表示のみなので、変数で受けるとコマンド実行結果のLinux-ServerNameのみ格納されます。) Adding Git path for PowerShell Command. git path "C:\Program Files (x86)\Git\bin\" had been added to PATH. Adding UserProfilepath for ssh-Keygen. UserProfile path had already been added to HOME. nothing will do. Linux-ServerName 簡単ですね。
余談
なお、コマンドレットを解釈して実際実行されているのは、以下のsshコマンドです。 (めんどうなので限定していますが、広げれば各種sshコマンドが実行可能です。)
# オプションが2つ指定されている場合 ssh -i $rsaKey $user@$hostip -o $option -o $option2 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=quiet $command # オプションが1つ指定されている場合 ssh -i $rsaKey $user@$hostip -o $option -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=quiet $command # オプションがない場合 ssh -i $rsaKey $user@$hostip -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=quiet $command
上記のコマンドのうちハードコードされた3つ
StrictHostKeyChecking=no UserKnownHostsFile=/dev/null LogLevel=quiet
とssh -iは、known_hostにないホストへの接続でコマンド実行がとまることを回避するためのオプションです。
これがないとsshできないので大事です。
Invoke-CapistranoDeploy
sshコマンド経由で対象のCapistranoサーバーへcapコマンドを送信します。
これでWindowsからCapistrano Serverにcapコマンドを送ってdeployできます。
利用する際のサンプルフォーマットです。
Invoke-CapistranoDeploy ` -deploygroup $deploygroup ` -captask "deploy" ` -deploypath CapistranoFullPath ` -rsakey RSAKeyForSSH ` -user SSHUser ` -hostip IpAddress
実行するとCapistranoパスに移動してcapコマンドが送信されます。 実行されるのは、このようなフォーマットのcapコマンドです。
cap デプロイグループ デプロイタスク
続いて実行経過と結果が出力されます。(通常文は白字) failedなどcapistranoのエラーキーワードを赤字ハイライトしているので、 エラーが起こった場合も分かります。
前提
capistranoでのssh deployにおいて、bash_profileでssh-add keyを実行してssh-agentなどでrsaキーを読むようにしています。
Private管理を徹底して外部接続ができないからやっていますがセキュリティには注意です。
なお、 ssh実行中にssh-agent ~/.bashrcなどをするとbash実行空間が変わるため、送信したsshコマンドを実行できなくなります。
自分サーバの構築その14:ssh-agentでノンパスワードを実現
余談: Chocolateyでsshを入手する
ここからは余談となります。 興味のある方のみどうぞ。
Windows PowerShellにchocolateyをインストールしmsysgitをパッケージインストールしsshを入手する
まずは、 chocolateyとmsysgitをインストールする部分を見てみましょう。 とは言っても、インストーラーを明示的に実行することはありません。 sshコマンドの実行時にインストール状況を確認し、なければ自動的にインストールします。
Chocolatey とは
そもそもChocolateyとは何でしょうか?
簡単にいうと、 Windows PowerShellを利用したapt-getあるいはyumとでもいうべきものです。 Nugetをベースにしており、 Chocolatey Gallery | Packagesに公開されている膨大な数のパッケージをコマンド経由でインストールできます。 もちろんパッケージ管理システムなのでインストール時の依存関係や更新、アンインストールも管理できます。 Windows 8 x64やWindows Server 2012 x64でも動作が確認済みで、今後Nugetと共に主流になる可能性を秘めています。
Windowsアプリをコマンド一発で導入できるパッケージ管理システム「Chocolatey」
Chocolatey のインストール
以下のコマンドをPowerShell実行することでインストール可能です、なんて事は言いません。
@powershell -NoProfile -ExecutionPolicy unrestricted -Command "iex ((new-object net.webclient).DownloadString('https://chocolatey.org/install.ps1'))" && SET PATH=%PATH%;%systemdrive%\chocolatey\bin - See more at: http://chocolatey.org/#sthash.h1KBAENo.dpuf
今回公開するコードにchocolateyインストールも含まれているので忘れて大丈夫です。
function New-ChocolateryInstall { param( [bool]$ShowMan=$false ) Write-Host "Checking for chocolatery installation." try { Import-Module C:\Chocolatey\chocolateyinstall\helpers\chocolateyInstaller.psm1 } catch { Invoke-Expression ((new-object Net.Webclient).DownloadString("http://bit.ly/psChocInstall")) } if (!(Get-Module chocolateyInstaller)) { Invoke-Expression ((new-object Net.Webclient).DownloadString("http://bit.ly/psChocInstall")) } else { Write-Host "chocolatery had already been installed. nothing will do." -ForegroundColor Green } switch ($true){ $ShowMan {Get-ChocolateryInstructions} default{ Write-Host " - If you want to check simple chocolatery usage, add -ShowMan $true." -ForegroundColor Yellow} } }
今回紹介するsshモジュールの実行経過で、chocolateyがインストールされていない場合は自動的にインストールします。 経過でこのように表示されます。
The specified module 'C:\Chocolatey\chocolateyinstall\helpers\chocolateyInstaller.psm1' was not loaded because no valid module file was found in any module directory. Downloading http://chocolatey.org/api/v2/package/chocolatey/ to C:\Users\ADMINI~1\AppData\Local\Temp\2\chocolatey\chocInstall\chocolatey.zip Extracting C:\Users\ADMINI~1\AppData\Local\Temp\2\chocolatey\chocInstall\chocolatey.zip to ... Installing chocolatey on this machine Creating ChocolateyInstall as a User Environment variable and setting it to 'C:\Chocolatey' We are setting up the Chocolatey repository for NuGet packages that should be at the machine level. Think executables/application packages, not library packages. That is what Chocolatey NuGet goodness is for. The repository is set up at 'C:\Chocolatey'. The packages themselves go to 'C:\Chocolatey\lib' (i.e. C:\Chocolatey\lib\yourPackageName). A batch file for the command line goes to 'C:\Chocolatey\bin' and points to an executable in 'C:\Chocolatey\lib\yourPackageName'. Creating Chocolatey NuGet folders if they do not already exist. bin lib chocolateyinstall Copying the contents of 'C:\Users\Administrator\AppData\Local\Temp\2\chocolatey\chocInstall\tools\chocolateyInstall' to 'C:\Chocolatey'. Creating 'C:\Chocolatey\bin\chocolatey.bat' so you can call 'chocolatey' from anywhere. Creating 'C:\Chocolatey\bin\cinst.bat' so you can call 'chocolatey install' from a shortcut of 'cinst'. Creating 'C:\Chocolatey\bin\cinstm.bat' so you can call 'chocolatey installmissing' from a shortcut of 'cinstm'. Creating 'C:\Chocolatey\bin\cup.bat' so you can call 'chocolatey update' from a shortcut of 'cup'. Creating 'C:\Chocolatey\bin\clist.bat' so you can call 'chocolatey list' from a shortcut of 'clist'. Creating 'C:\Chocolatey\bin\cver.bat' so you can call 'chocolatey version' from a shortcut of 'cver'. Creating 'C:\Chocolatey\bin\cwebpi.bat' so you can call 'chocolatey webpi' from a shortcut of 'cwebpi'. Creating 'C:\Chocolatey\bin\cWindowsfeatures.bat' so you can call 'chocolatey windowsfeatures' from a shortcut of 'cwindowsfeatures'. Creating 'C:\Chocolatey\bin\ccygwin.bat' so you can call 'chocolatey Cygwin' from a shortcut of 'ccygwin'. Creating 'C:\Chocolatey\bin\cpython.bat' so you can call 'chocolatey Python' from a shortcut of 'cpython'. Creating 'C:\Chocolatey\bin\cgem.bat' so you can call 'chocolatey gem' from a shortcut of 'cgem'. Creating 'C:\Chocolatey\bin\cpack.bat' so you can call 'chocolatey pack' from a shortcut of 'cpack'. Creating 'C:\Chocolatey\bin\cpush.bat' so you can call 'chocolatey push' from a shortcut of 'cpush'. Creating 'C:\Chocolatey\bin\cuninst.bat' so you can call 'chocolatey uninstall' from a shortcut of 'cuninst'. User PATH already contains either 'C:\Chocolatey\bin' or '%DIR%..\bin' Processing ccygwin.bat to make it portable Processing cgem.bat to make it portable Processing chocolatey.bat to make it portable Processing cinst.bat to make it portable Processing cinstm.bat to make it portable Processing clist.bat to make it portable Processing cpack.bat to make it portable Processing cpush.bat to make it portable Processing cpython.bat to make it portable Processing cuninst.bat to make it portable Processing cup.bat to make it portable Processing cver.bat to make it portable Processing cwebpi.bat to make it portable Processing cwindowsfeatures.bat to make it portable Chocolatey is now ready. You can call chocolatey from anywhere, command line or powershell by typing chocolatey. Run chocolatey /? for a list of functions. You may need to shut down and restart powershell and/or consoles first prior to using chocolatey. If you are upgrading chocolatey from an older version (prior to 0.9.8.15) and don't use a custom chocolatey path, please find and delete the C:\NuGet folder after verifying that C:\Chocolatey has the same contents (minus chocolateyinstall of course). Ensuring chocolatey commands are on the path - If you want to check simple chocolatery usage, add -ShowMan True.
なお、chocolateyからモジュールをインストールする場合は、cinst モジュール名となります。
cinst モジュール名
msysgitをchocolatey経由でインストール
msysgitは、数あるGit clientの1つで、 chocolatey経由でインストールが可能です。
gitをインストールすることで、 同時にsshも入るのでこれでサクッとやってしまいましょう。
chocolateyからmsygitをインストールする場合はcinst msysgitを使います。
今回公開するコードにmsysgitインストールも含まれているので忘れて大丈夫です。
function New-ChocolateryMsysgitInstall { Write-Host "Checking for msysgit installation." if (!(Get-ChildItem -path "C:\Chocolatey\lib" -Recurse -Directory | ? {$_.Name -like "msysgit*"})) { cinst msysgit } else { Write-Host "msysgit had already been installed. nothing will do." -ForegroundColor Green } }
今回紹介するsshモジュールの実行経過で、msygitがインストールされていない場合は自動的にインストールします。
Chocolatey (v0.9.8.20) is installing msysgit and dependencies. By installing you accept the license for msysgit and each dependency you are installing. ______ Git.install v1.8.3 ______ Downloading git.install (http://msysgit.googlecode.com/files/Git-1.8.3-preview20130601.exe) to C:\Users\ADMINI~1\AppData\Local\Temp\2\chocolatey\Git.install\git.installInstall.exe Installing git.install... Elevating Permissions and running C:\Users\ADMINI~1\AppData\Local\Temp\2\chocolatey\Git.install\git.installInstall.exe /VERYSILENT . This may take awhile, depending on the statements. git.install has been installed. git.install has finished succesfully! The chocolatey gods have answered your request! git.install has finished succesfully! The chocolatey gods have answered your request! ______ git v1.8.3 ______ ______ msysgit v1.7.10.20120526 ______ Finished installing 'msysgit' and dependencies - if errors not shown in console, none detected. Check log for errors if unsure.
これでsshを使う準備が出来ました。 (といっても、ここまで一切入力する必要はありません、中で何をやっているか説明しただけですしやらなくても良きように計らいます)
ssh接続で準備しておくもの
これだけ準備してください。
- 接続するlinuxサーバーのssh key(RSA)
- ssh接続ユーザー名
- 接続先サーバーのip addressかhostname
※今回は、 ssh keyのパスワードを省略していますが、ちょちょいとさわればそれも出来ます。 (今回はやらないけど)
次はいよいよssh経由でコマンドを実行します。
Windows PowerShell から SSH 経由で Linux にコマンドを飛ばして結果を取得する
至って簡単でこのようなモジュールでsshコマンドをラップしています。
function Invoke-SshCommand{ [CmdletBinding()] param( [parameter( position = 0, mandatory = 1, ValueFromPipeLine = 1, ValueFromPipelineByPropertyName = 1)] [string] $rsaKey, [parameter( position = 1, mandatory = 1, ValueFromPipeLine = 1, ValueFromPipelineByPropertyName = 1)] [string] $user, [parameter( position = 2, mandatory = 1, ValueFromPipeLine = 1, ValueFromPipelineByPropertyName = 1)] [string] $hostip, [parameter( position = 3, mandatory = 1, ValueFromPipeLine = 1, ValueFromPipelineByPropertyName = 1)] [string] $command, [parameter( position = 4, mandatory = 0, ValueFromPipeLine = 1, ValueFromPipelineByPropertyName = 1)] [string] $option="", [parameter( position = 5, mandatory = 0, ValueFromPipeLine = 1, ValueFromPipelineByPropertyName = 1)] [string] $option2="" ) begin { Write-Verbose "Check Log Folder is exist or not" New-PSSshLogFolder Write-Verbose "Check Ssh Status" Test-SshInstallationStatus Write-Verbose "Set GitPath" Set-EnvGitPath Write-Verbose "Set User Profile Path" Set-EnvUserProfilePath Write-Verbose "prefix command to avoid trusted option" $prefixoption1 = "StrictHostKeyChecking=no" $prefixoption2 = "UserKnownHostsFile=/dev/null" $prefixoption3 = "LogLevel=quiet" } process { if(($option -ne "") -and ($option2 -ne "")) { Write-Verbose "実行コマンド : ssh -i $rsaKey $user@$hostip -o $option -o $option2 -o $prefixoption1 -o $prefixoption2 -o $prefixoption3 $command" ssh -i $rsaKey $user@$hostip -o $option -o $option2 -o $prefixoption1 -o $prefixoption2 -o $prefixoption3 $command } elseif ($option -ne "") { Write-Verbose "実行コマンド : ssh -i $rsaKey $user@$hostip -o $option -o $prefixoption1 -o$prefixoption2 -o $prefixoption3 $command" ssh -i $rsaKey $user@$hostip -o $option -o $prefixoption1 -o $prefixoption2 -o $prefixoption3 $command } else { Write-Verbose "実行コマンド : ssh -i $rsaKey $user@$hostip -o $prefixoption1 -o $prefixoption2 -o $prefixoption3 $command" ssh -i $rsaKey $user@$hostip -o $prefixoption1 -o $prefixoption2 -o $prefixoption3 $command } } end { } }
chocolateyとmsygitがインストールされていればGitパスが定まるので、環境変数に入れたりもしています。 全てmoduleに入っているのでよろしければご覧ください。 利用する際のサンプルフォーマットです。
Invoke-SshCommand -rsaKey "RSAキーパス" -user sshユーザー名 -hostip 接続先サーバーIP -command "ssh先で実行するコマンド" # Optionがある場合は 2つまで設定可能 Invoke-SshCommand -rsaKey "RSAキーパス" -user sshユーザー名 -hostip 接続先サーバーIP -command "ssh先で実行するコマンド" -option sshコマンドオプション # Optionがある場合は 2つまで設定可能 Invoke-SshCommand -rsaKey "RSAキーパス" -user sshユーザー名 -hostip 接続先サーバーIP -command "ssh先で実行するコマンド" -option sshコマンドオプション -option2 sshコマンドオプション2
利用する際は、例えばこのようにします。
Invoke-SshCommand -rsaKey c:\ssh\rsa -user lunuxuser -hostip 192.168.1.1 -command "hostname"
簡単ですね。
Windows PowerShell から SSH 経由で Capistrano がインストールされたLinux サーバーに Cap コマンドを送信し、結果を取得する
先ほどのssh接続、コマンド実行のコマンドレットを利用して、 利用するcapistrano deployコマンドを送っています。
function Invoke-CapistranoDeploy{ [CmdletBinding()] param( [parameter( position = 0, mandatory = 1 )] [string] $deploygroup, [parameter( position = 1, mandatory = 1 )] [string] $captask, [parameter( position = 2, mandatory = 1 )] [string] $deploypath, [parameter( position = 3, mandatory = 1 )] [string] $rsakey, [parameter( position = 4, mandatory = 1 )] [string] $user, [parameter( position = 5, mandatory = 1 )] [string] $hostip ) begin { # define cap command Write-Verbose "define basecommand : source .bash_profile; cd $deploypath;" $basecommand = "source .bash_profile; cd $deploypath;" Write-Verbose "define cap command : cap $deploygroup $captask;" $capcommand = "cap $deploygroup $captask;" Write-Verbose "define ssh command : $basecommand + $capcommand" $command = $basecommand + $capcommand # define splating for ssh command $sshparam = @{ rsakey = $rsakey user = $user hostip = $hostip command = $command } # Show define result Write-Warning "Set -Verbose switch to check ssh command variables and Detail" Write-Verbose "rsakey : $rsakey" Write-Verbose "user : $user" Write-Verbose "hostip : $hostip" Write-Verbose "command : $command" } process { # runcommand Invoke-SshCommand @sshparam -ErrorAction Continue 2>&1 | %{ # Host Display if (($_ -like "*error*") -or ($_ -like "*failed*") -or ($_ -like "*fatal*")) { Write-Host $_ -ForegroundColor Red } elseif ($_ -like "*the task * does not exist*") { Write-Host $_ -ForegroundColor Yellow } else { Write-Host $_ } # Log Output if ($_.Message -eq $null) { $_ | Out-File -FilePath $psssh.Log.path -Encoding utf8 -Append } else { $_.Message | Out-File -FilePath $psssh.Log.path -Encoding utf8 -Append } } } end { } }
利用する際のサンプルフォーマットです。
Invoke-CapistranoDeploy ` -deploygroup $deploygroup ` -captask "deploy" ` -deploypath CapistranoFullPath ` -rsakey RSAKeyForSSH ` -user SSHUser ` -hostip IpAddress
実行するとCapistranoの実行結果が出力されます。 failedなどcapistranoのエラーキーワードを赤地ハイライトしているのでエラーが起こった場合も分かります。
実は、 Windows外部コマンドやsshなどをPowerShellから実行すると、 Native Command ErrorやRemoteExceptionという例外になることがあります。 例えばwgetコマンドを送った場合がそうです。
Why am I getting a "Native Command Error"
おおよそPowerShellのバグと言い切っていいのですが、 通常の $ErrorPreferenceはcontinueなので継続実行されます。 が、 エラーとして全て赤字で出力されてしまい、正常実行かどうか判断できません。そこで、capistrano実行部分でほにょってます。
Invoke-SshCommand @sshparam -ErrorAction Continue 2>&1 | %{ # Host Display if (($_ -like "*error*") -or ($_ -like "*failed*") -or ($_ -like "*fatal*")) { Write-Host $_ -ForegroundColor Red } elseif ($_ -like "*the task * does not exist*") { Write-Host $_ -ForegroundColor Yellow } else { Write-Host $_ }
原因と、問題ないことが確認できているので、ここで例外を捉えて(各capistranoコマンド出力1回毎にErrorがでる)通常の表示をエラーの赤字から白字に変換しています。 かつ、特定の文字を含む場合は赤字や黄色字としています。 意外とこのやり方はStackOverflowでもリクエストがありますが、回答が乏しいので参考になれば幸いです。
まとめ
「ssh認証でrsaキーのパスワード指定をしていない」「capistrano ssh認証にbash_profileでの自動読み込みを利用している」など、特異な設定でのコマンドにはなっています。
上手く利用すればWindowsからPowerShellでAutomationに組み込める事が分かります。 参考程度に見ていただければ幸いです。