JenkinsでPowerShellを使う際の注意事項
Windows PowerShell、慣れると便利なんですけど、バージョンアップのたびに前提が変わったりするので、複数の環境が混在していたりするとハマります。
ネットで検索しても、バージョンによって異なる情報が出てきて混乱するし。
あと、ドメイン環境と非ドメイン環境、Jenkinsとかサービスから実行する場合などでも動作が変わるので注意が必要です。
(基本的には実行ユーザの問題と、マシン間の信頼関係の問題なんですが、未だに理解しきれません。間違ってるところあればコメント願います。。。)
ここでは、JenkinsからPowerShellで複数のマシンをコントロールする際にハマりやすいところを中心にメモしておきます。
なお、以降でJenkins側と言ってるのはあくまでPowerShellを実行するマシンのことです。Jenkins Slave使って更に他のマシンからPowerShellを実行する場合は、そこがJenkins側にあたることになります。
なお、以降PoweShellの設定を行うコマンドは、基本的に全て管理者権限のある状態で実行する必要があります。
ソフト等 | バージョン | 備考 |
---|---|---|
Windows Server 2012R2 | ||
PowerShell | v4.0 | 2012R2標準 |
Jenkins | v1.647 | |
PowerShell plugin | v1.3 |
リモート側は以下の通り。
ソフト等 | バージョン | 備考 |
---|---|---|
Windows 10 | ||
PowerShell | v5.0 | Windows10標準 |
Jenkins側の設定
PowerShell plugin
何は無くとも、PowerShell pluginを入れます。
ネットにつながっていれば、プラグインマネージャーからチェックしてインストール。
つながってなければ、つながっているマシンで(archives) とかから辿って、powershell.hpiファイルを入手してください。
プラグインマネージャの高度な設定から、プラグインのアップロードでインストールします。
Jenkinsサービスのユーザ変更
Windowsのサービスから「Jenkins」のプロパティを表示すると、ログオンタブにJenkinsサービスを実行するユーザが設定されていますが、デフォルトではマシンのローカルシステムアカウントなどになっていると思います。
これでもPowerShellを実行することはできますが、他のマシンでのリモート実行を行うことが出来ません。
リモート実行を行う場合は、必要に応じて「Administrator」などのアカウントを設定します。(要Jenkinsサービスの再起動)
なお、ドメイン環境ならぜひAdministrator権限を持つドメインユーザの設定をお勧めします。リモート接続が簡単になります。
Jenkins側(リモート操作する側)と、操作される側に共通の設定
PowerShell実行ポリシー
PowerShellのスクリプトファイル(.ps1ファイル)の実行は、PowerShellのバージョンによってデフォルトの実行ポリシーが違うので、そのままでは動かない場合があります。(まだ動かない環境の方が多いでしょう。)
PowerShellを実行し、次のコマンドで確認できます。(意味はこことか参照)
Get-ExecutionPolicy
Windows Server 2012(PowerShell v4.0)以降などは、初めから「RemoteSigned」なので変更不要ですが、「Restricted」などの場合は次のようなコマンドを入力し適切な設定が必要です。
Set-ExecutionPolicy RemoteSigned
Jenkins側(リモート操作する側)の設定
WinRmサービスの構成
WinRm(リモート管理サービス)は、細かい違いが多くて自分の中でも整理できていませんが、とりあえず次のコマンドで普通は設定できます。(プロンプトは全部「y」で大丈夫)
WinRm QuickConfig
信頼できるマシンの設定
非ドメイン環境では、事前にリモート接続する相手のマシンを登録しておく必要があります。
(ドメイン環境ならやる必要なし。初めての非ドメイン環境は、これでハマった)
Set-Item WSMan:\localhost\Client\TrustedHosts <相手マシン名>
(相手マシンが多いとか、安全な環境なら、<相手マシン名>は「*」でもOK。)
操作される側の設定
リモート接続の構成
次のコマンドで、ポートとかのリモート接続の受け側の設定が行われます。
(これにはWinRmサービスの構成も含まれています。)
Enable-PSRemoting
リモート接続するユーザの設定
Jenkinsを実行しているユーザと同じ、またはAdministrator権限を持つユーザが有効になっている必要があります。
ドメイン環境なら何もせずとも問題ない場合が多いのですが、非ドメイン環境の場合、特にAdministratorがデフォルトで無効になっているクライアントOS(Windows10とか)の場合、注意が必要です。
一番無難なのは、JenkinsをAdministratorで動かし、操作される側もAdministratorを有効にした状態です。
Jenkinsからリモートでコマンドを実行してみる
Jenkinsでビルドを作り、ビルド手順の追加で「Windows PowerShell」を選択し、以下のソースを貼り付けます。
### PowerShellソース
リモートでコマンドを実行する先のマシン名は、初めに配列で記述してあります。
なお、リモート側で実行するコマンドの中身は、PowerShellからMongoDBにアクセスする。と同じものなのでそちらを参考にしてください。
##### 指定マシンの情報をリモートジョブで取得 #####
# 対象のマシン名
$REMOTE = @( 'V1', 'V2', 'V3' );
# 開始日時を表示
$date = Get-Date;
"##### $date #####";
# 対象のマシンで同時に(AsJob)ジョブ実行
ForEach($computername in $REMOTE) {
Invoke-Command -ComputerName $computername -AsJob -ScriptBlock {
@{
"Processor_per" = [int](Get-Counter -Counter "\Processor(_Total)\% Processor Time").CounterSamples.CookedValue;
"Network_byte" = [int]((Get-Counter -Counter "\Network Interface(*)\Bytes Total/sec").CounterSamples.CookedValue | Measure-Object -Maximum).Maximum;
"Disk_byte" = [int](Get-Counter -Counter "\LogicalDisk(_Total)\Disk Bytes/sec").CounterSamples.CookedValue;
}
}
};
# ジョブが無くなるまで繰り返し
While((Get-Job) -ne $null) {
# 1秒待ち、日時とジョブの状況を表示
Start-Sleep -Second 1;
$date = Get-Date;
"##### $date #####";
Get-Job;
# 完了(Completed)したジョブを全て受け取り、削除
ForEach($job in (Get-job -State "Completed")) {
# 完了したジョブの見出しを表示
$Id = $job.Id;
$Location = $job.Location;
"##### Id = $Id / Location = $Location #####";
# 結果をjsonで表示し、ジョブを削除
Receive-job -Id $Id | ConvertTo-json;
Remove-job -Id $Id;
};
};
ここでは、Invoke-Commandに-AsJobを指定して、最初の配列に定義した、V1,V2,V3の3台のマシンに同時にジョブを実行させています。
なお、ForEach($a in $b) {$a}は、$b.ForEach({$_})と書きたいのですが、(Get-job -State "Completed").ForEach()したところで該当のジョブが1つしかないとエラーとなってしまったので、この書き方にしています。
(たぶんPowerShellが、Jenkins側のV4.0と、リモート側のv5.0で違う影響かと思いますが、詳細不明。。。)
実行結果
このビルドを実行したところ、次のような結果となりました。
ユーザーanonymousが実行
ビルドします。 ワークスペース: C:\Jenkins\workspace\VM_Loggong
[VM_Loggong] $ powershell.exe -NonInteractive -ExecutionPolicy ByPass "& 'C:\Users\ADMINI~1\AppData\Local\Temp\hudson2286994124897927906.ps1'"
##### 02/07/2016 11:25:13 #####
Id Name PSJobTypeName State HasMoreData Location Command
-- ---- ------------- ----- ----------- -------- -------
1 Job1 RemoteJob Running True V1 ...
3 Job3 RemoteJob Running True V2 ...
5 Job5 RemoteJob Running True V3 ...
##### 02/07/2016 11:25:14 #####
1 Job1 RemoteJob Running True V1 ...
3 Job3 RemoteJob Running True V2 ...
5 Job5 RemoteJob Running True V3 ...
##### 02/07/2016 11:25:15 #####
1 Job1 RemoteJob Running True V1 ...
3 Job3 RemoteJob Running True V2 ...
5 Job5 RemoteJob Running True V3 ...
##### 02/07/2016 11:25:16 #####
1 Job1 RemoteJob Running True V1 ...
3 Job3 RemoteJob Running True V2 ...
5 Job5 RemoteJob Running True V3 ...
##### 02/07/2016 11:25:17 #####
1 Job1 RemoteJob Running True V1 ...
3 Job3 RemoteJob Running True V2 ...
5 Job5 RemoteJob Running True V3 ...
##### 02/07/2016 11:25:18 #####
1 Job1 RemoteJob Running True V1 ...
3 Job3 RemoteJob Completed True V2 ...
5 Job5 RemoteJob Completed True V3 ...
##### Id = 3 / Location = V2 #####
{
"Disk_byte": 40930,
"Network_byte": 41749,
"Processor_per": 45,
"PSComputerName": "V2",
"RunspaceId": "ccf5b5e1-e871-4751-8b00-5484f71e960f",
"PSShowComputerName": true
}
##### Id = 5 / Location = V3 #####
{
"Disk_byte": 0,
"Network_byte": 60,
"Processor_per": 0,
"PSComputerName": "V3",
"RunspaceId": "ebd12ad4-f4d2-48e6-aa21-f6b81cd28aaf",
"PSShowComputerName": true
}
##### 02/07/2016 11:25:19 #####
1 Job1 RemoteJob Completed True V1 ...
##### Id = 1 / Location = V1 #####
{
"Disk_byte": 0,
"Network_byte": 520745,
"Processor_per": 25,
"PSComputerName": "V1",
"RunspaceId": "96a08f24-1f44-4cf8-b1e0-ceb1ecceb90b",
"PSShowComputerName": true
}
Finished: SUCCESS
V1,V2,V3のジョブが同時に投入され、V2とV3は5秒後に、V1が6秒後に終わりました。
-AsJobで同時に実行しないとすれば、6+5+5で16秒かかる処理が、6秒で終わりました。
リモートでコマンドを実行するマシンが増えるにつれ、-AsJobの効果が大きくなります。