2>&1

インフラ寄りのメモ。

PowerShell DSCのConifgurationを引いて監視を効率化する

第3回PoewrShell勉強会でLTやってきたのでスライドとコードを掲載。

始めてのLTだったので優しくしてください><


技術評論社のChef本に、Nodeオブジェクト上に監視用のattributeを起こして、監視を自動化する実装が書いてあって、それにインスパイアを受けました。PowerShell DSCでも似たようなことができないかと思い、挑戦してみました。

コンフィグ実行時に-ConfigurationDataで渡すオブジェクトを利用すれば、同じようなことができます。

[Configuration] -ConfigurationData $ConfigurationData

こんな書き方をしますが、この$ConfigurationDataオブジェクトがChefでいうNodeオブジェクトにあたるので、よしなに必要な情報をリストに入れてあげて(オブジェクト名や中の要素等、すべてが自由書式)、このオブジェクトを流用する形になりますね。

監視用のモジュールというか関数というか別スクリプトを起こす気がなかったのと、二番煎じになるので今回は別のやり方にしました。(近いうちに書いてみます)


PowerShell DSCとChefで大きく違う点は、Nodeオブジェクトもレシピも一緒くたに書けてしまう点です。そのため、複数名で運用する想定なら、ある程度書き方の規約を決める必要があると思います。…とりあえずChefのファイル郡構成を模す形にするのがいいかもですね。


話がそれました。今回は$ConfigurationDataを作り込んで監視情報を引くというより、その情報自体がDSCサーバーに記録されて引けるので、そちらを利用するものを書きました。

[ノード名].mofファイルのbasenameや、Service Resourceの記述が引ければ、それだけでも手堅い監視ができちゃいますよね。

ということで下記が監視スクリプトです。

$ConfPath = ".mofファイル格納ディレクトリのパス"

(Get-ChildItem $ConfPath).Fullname | ForEach-Object {
	$NodeName = (Split-Path $_ -Leaf).Replace(".mof","")
	$Session = New-CimSession -ComputerName $NodeName
	$DscConfig = Get-DscConfiguration -CimSession $Session

	if (!$?){
		if(!(Test-Connection $NodeName)) {
			. Error-Action; echo "$NodeName is Node Down!"
		}
		continue
	}

	$DscConfig | Where-Object {$_.CimClass.CimClassName -eq "MSFT_ServiceResource" } | ForEach-Object {
		$ServiceName = $_.Name
		if ($_.State -eq "Stopped") {
			. Error-Action; echo "$NodeName : $ServiceName is Stopping!"
		}
	}
}

function Error-Action {
	echo "Danger!!"
}

Cimの認証(-credentialオプション)は省略してます。よしなに実装です。

エラー時のアクションは例示です。メール送信とか書く気力がなかったんです。こんな書き方をした奴の頭がそもそもデンジャー。


DSCサーバーから実行することを想定しています。DSCサーバーの外から実行する場合は.mofファイルを嘗める実装になります。

PULLする構成の場合、.mofはPULLノードから引ける必要があるので、監視サーバーもPULLノードにして、SMBなりで見に行けばスマートですかね。

.mofファイルを嘗める実装、スライドに書いていませんでしたが、実は書いてありました。(むしろ最初は.mofファイルを嘗めて「ハイ、完成」でしたが、Get-DscConfigurationが現状のステータスを拾ってくれるのに気付いて書き直した次第。)

で、下記の通りです。

# Param
$ConfPath = ".mofファイル格納ディレクトリのパス"
[bool]$Flag = $False
$AllNodes = @()

# Get .mof file information
(Get-ChildItem $ConfPath).Fullname | ForEach-Object {
	$NodeName = (Split-Path $_ -Leaf).Replace(".mof","")
	$Services = @()

	get-content $_ | ForEach-Object {
		if ($_ -like '*`[Service`]*') {
			$Flag = $True
		}
		if ($_ -like ' Name =*' -and $Flag ) {
			$Flag = $False
			$Services += $_.split('`"')[1]
		}
	}
	
	$Node = @(@{"NodeName"=$NodeName;"Services"=$Services})
	$AllNodes += $Node
}

# Monitoring
$AllNodes | ForEach-Object {
		$NodeName = $_.NodeName
		if(Test-Connection $NodeName) {
			$_.Services | ForEach-Object {
				if ((Get-Service -ComputerName $NodeName -Name $_).Status -ne "Running") {
					. Error-Action; echo "$NodeName : $_ is Stopping!"
				}
			}
		} else { . Error-Action; echo "$NodeName is Node Down!" }
}

# Alert or Start-DSCConfiguration.
function Error-Action {
	echo "Danger!!"
}

あとは例示で出したコンフィグを記念に。SNMPとBitLockerの機能を入れて、関連サービスを「自動起動」にして、開始するだけのものです。実際にこんなConfiguration書く人はおらんでしょう。どないサーバーやねん、て。

$ConfigurationData = @{
  AllNodes = @(
    @{NodeName = "192.168.100.150"},
    @{NodeName = "192.168.100.151"}
  )
}

Configuration EXConfig2 {
	Node  $AllNodes.NodeName {
		WindowsFeature SNMP {
			Ensure = "Present"
			Name = "SNMP-Service"
		}
		WindowsFeature BL {
			Ensure = "Present"
			Name = "BitLocker"
		}
		Service SNMP {
			Name = "SNMP"
			StartupType = "Automatic"
			State = "Running"
		} 
		Service SNMPTRAP {
			Name = "SNMPTRAP"
			StartupType = "Automatic"
			State = "Running"
		} 
		Service BDESVC {
			Name = "BDESVC"
			StartupType = "Automatic"
			State = "Running"
		} 
	}
}

EXConfig2 -ConfigurationData $ConfigurationData


近日中にSyntaxHighLight処理しときます。