読者です 読者をやめる 読者になる 読者になる

tech.guitarrapc.cóm

C#, PowerShell, Unity, Cloud, Serverless Technical Update and Features

PowerShell DSC Advent Calendar 2014 : Day 6 Configuration の記述

これは、PowerShell DSC Advent Calendar 2014 - Adventar 6日目の記事です。

5日目は、DSC のエンジンである LCM(Local Configuration Manager) について説明しました。

今日は、コンフィグレーション(Configuration)構文と記述について見ていきましょう。今日からは、コード沢山 + PowerShell DSCのベストプラクティスも交えていきますよ。

目次

事前知識

3日目の用語まとめでコンフィグレーションとは、「ロジックを呼び出してあるべき状態を宣言型構文で指示」するもの、Chefでいうレシピであると説明しました。

ざっくりいうと、「ノードのあるべき状態を記述したもの」と捉えて構わないでしょう。

標準リソース

コンフィグレーションを紹介するにあたり、PowerShell DSC において標準で利用できるリソースは次の通りです。

Resource 状態の対象
ファイル(File) ファイル
アーカイブ(Archive) Zip
エンバイロンメント(Environment) 環境変数
パッケージ(Package) MSIインストール
レジストリ(Registry) レジストリ
スクリプト(Script) PowerShellスクリプト実行で指定
サービス(Service) サービス状態
ユーザー(User) ローカルユーザー
グループ(Group) ローカルグループ
フィーチャー(WindowsFeature) Windowsの機能
プロセス(WindowsProcess) プロセスの起動
ログ(Log) Verboseストリームへのログ表示

今回はこれらを使ってみましょう。

まさかの展開

実は私、すでにコンフィグレーション構文について説明していた。という。はい。

BuildInsider にて、構文自体の説明は行っていたんですねー。なるべく丁寧に書いているので同じ内容に書くのは無駄な感じがします。

そこで、今回はもう少し実践的な話をしましょう。事前にBuildInsiderの記事にかかれているコンフィグレーション構文の基礎を知っている前提となります。

  1. PowerShell v5でのインテリセンス強化
  2. シンプルなコンフィグレーションサンプル
  3. コンフィグレーションの適用順序保証
  4. コンフィグレーション内でのPowerShell構文利用
  5. スクリプトリソースを使ったコンフィグレーション記述のポイント
  6. スクリプトリソースの問題点
  7. ネストされたコンフィグレーションの連動
  8. 異なるコンフィグレーションファイルのコンフィグレーションを連動
  9. コンフィグレーションによるNTPの設定/維持

PowerShell v5でのインテリセンス強化

PowerShell v4 でコンフィグレーションを書くのはインテリセンスやタブ補完が弱く「使いにくい」のが現実です。v5 では、インテリセンスもタブ補完もばっちり効き大幅に改善されたことが実感できます。ようやく捗ります。

  • インテリセンスはCtrl + Space で入力可能な内容がポップアップ表示されます

  • タブ補完はTabキーを押すことで入力補完されます

リソースの補完

v4 では、リソース以外の関係ないCmdlet なども表示されていましたが、v5 では利用可能なリソースだけインテリセンスに表示されます。

タブ補完もv4では壊れてましたが v5 で正しく機能します。

f:id:guitarrapc_tech:20141207065816p:plain

リソースの定義表示

v4 でもできましたが、リソース名で Ctrl + Spaceを押すと利用可能な定義が表示されます。一々定義を抽出する?人間のやることではありません。

f:id:guitarrapc_tech:20141207070005p:plain

リソースが利用可能なキーワードのインテリセンス表示

v4 までは、キーワードはタブ補完されても、インテリセンスが効きませんでした。v5 ではばっちりインテリセンスが効きます!やった!

f:id:guitarrapc_tech:20141207065903p:plain

リソースのキーワードが利用可能な値のタブ補完

リソースのキーワードによっては、利用できる値が決まっていることがあります。v4まではコンフィグレーションを実行してコンパイルするまでエラーが検出できませんでしたが、v5ではタブ補完が効きます。*1

f:id:guitarrapc_tech:20141207070555p:plain

ここでTabキーを押せば自動補完されます。

f:id:guitarrapc_tech:20141207070611p:plain

シンプルなコンフィグレーションサンプル

PowerShell v4 にて標準で使えるリソースから、2つコンフィグレーションのサンプルを紹介しましょう。

サービスリソースの利用

「エラーなどで突然にWindowsサービスが停止!」は、当然ありえる話なのでサービスの起動保証がほしい思ったことは誰でもあるでしょう。DSCを使えば簡単にできます。

WinRMWindows Management Instrumentationサービスは、DSC の挙動の中心を支えるサービスです。このサービスの起動を保証するコンフィグレーションは次のように書けます。

どうですか?これだけシンプルなら、誰が見ても何をしたいのかおおよそ予測がつくのではないでしょうか。

たったこれだけで、WinRMWindows Management Instrumentationサービスが例えエラーを起こしても起動している状態を保証できるのです。つまりエラーからの自動的な状態復旧が満たされるのです。

DSCを使わない手はありません。*2

ユーザーリソースの利用

アクティブディレクトリ(ActiveDirectory、以降 ADと呼びます) を使えば、ユーザーなんてサーバーで共通で持てる?いいえ、ADは状況によっては使う選択肢から外す必要があります。たとえば、サーバーの台数が増減する スケーラブルな環境がその一例です。

ADは、 イミュータブルインフラストラクチャ(Immutable Infrastructure)、いつでも破棄可能(Disposable)なサーバーを構築する場合、とても重たい存在です。何しろいつでもサーバーを捨てる、再構築するということは、なるべく状態を持たないよう(Stateless)にしないといけません。にも関わらず、ADに参加したコンピュータはディレクトリサービスという状態を持ちます(State-full)。ステートレスにしたい環境でステートフルを強いる(しかも容易に代替可能な機能のためだけに!)を使うのはナンセンスです。

そんな時には、ユーザーやグループの存在が保証される必要がありますよね?そこでDSC を使うと、ADなんて参加せずともローカルコンピュータでユーザーとグループの状態が保証できます。

例えば、リモート用にユーザーとグループを作るならこうかけます。

コンフィグレーション実行時に、PSCredential を渡すことで、ユーザー名とパスワードを外から渡すことができます。そう、再利用すら可能なのです。

Remote -Credential (Get-Credential -Credential "RemoteUserName") -OutputPath Remote

コンフィグレーションの適用順序保証

コンフィグレーションの順序保証は DependsOn を使う

これは、PowerShell DSC のベストプラクティスです。

Chef でも散々言われていますが、DSC でも コンフィグレーションの適用順序は DependsOn で依存関係を保証します。

一見すると、上から順番に適用されいるように解釈してしまいそうですが、依存しているコンフィグレーションが実行されてから、依存するコンフィグレーションが実行される保証はありません。*3そのため、今回のようにグループに含めるユーザーが事前に存在していることを保証する場合は、DependsOn を使って適用順序を厳密に定めてください。

DependsOn は、次のように記述します。PowerShell v5 ではインテリセンスがばっちり聞いてタブで自動補完されるので大変はかどります。

リソース名1 コンフィグレーション名1
{
    # 何か素敵な定義
}
 
リソース名2 コンフィグレーション名2
{
    # 何か素敵な定義
    DependsOn = "[リソース名1]コンフィグレーション名1"
}

コンフィグレーション内でのPowerShell構文利用

ここまでのサンプルでは、冗長なコンフィグレーション記述をしていました。

Configuration は、PowerShellのキーワードにすぎません。つまり、Configuraion構文内部でもPowerShellの通常構文は使えるのです。

とはいえ、あまり使いすぎると読みにくくなります。せっかく宣言型構文を使っているのに、可読性が落ちるのは意味がないので可読性は優先する必要があります。

そこで、可読性、再利用性、冗長性の排除からバランスを考えると、使っても以下の3つ程度に抑えるべきでしょう。

  1. 静的なprivate変数
  2. foreachなどループ構文の利用
  3. Cmdletでの外部リソースの利用

サービスに関するコンフィグレーションは、PowerShell構文を使うことでこのように書けます。

元のがこうです。

保証を担保したいサービスが増えれば増えるほど、ループで回したくなりますよね。

著しく可読性を損なわない限り、冗長な記述をPowershell構文を使ってシンプルにするのはありだと思います。むしろ使わないのはありえない。*4

スクリプトリソースを使ったコンフィグレーション記述のポイント

スクリプトリソースは、なんでもできます。でもあまりどう書けばいいのか、何が問題なのかは知られていないように思います。

そこでまず、スクリプトリソース利用時の基本構文について典型をお伝えしましょう。

スクリプトリソースの基本構文

必須なのは、SetScriptTestScriptGetScriptです。

そしてGetScriptセクションでは、SetScriptTestScriptGetScriptResultを定義します。

たとえば、ネットワークアダプタの 6to4 を無効するにはこう書きます。

TestScript

対象ノードの状態をテストするロジックを書きます。返却値は[bool]である必要があります。

SetScript

対象ノードの状態を設定するロジックを書きます。テストが false、つまりあるべき状態でなかった場合にのみ実行されます。

GetScript

後日説明しますが、Get-DSCConfiguration にてノードの状態を取得する時に必要になります。

  • SetScript には、$SetScript を当ててください。これは典型パターンです。
  • TestScript には、$TestScript を当ててください。これは典型パターンです。
  • GetScript には、$GetScript を当ててください。これは典型パターンです。
  • Resultには、TestScriptで設定したのと同じロジックを入れます。

典型が多いのはいいのですが、冗長この上ないですね。必要悪とはこのことなのか....

スクリプトリソースの問題点

スクリプトリソースの利用には、たくさん問題があります。

  1. ロジックをコンフィグレーションに直接定義
  2. 変数の参照は$using:スコープ
  3. ポジショナルパラメータの利用
  4. 型の明示
  5. MOFの可読性低下
ロジックをコンフィグレーションに直接定義

これは、リソースコンセプトの破壊につながりかねないことです。極力やめましょう。

ではどうするのか?再利用する場合は、リソースにするのです。コンポジットリソース(Composite Resource) など コンフィグレーションをリソースにして再利用する方法や、リソースを自分で定義してもいいでしょう。*5

ただ、その場限りで再利用もなく、シンプルな場合は許容できると思います。

スクリプトリソースを使ってコンフィグレーションを書きたくなったら、再利用する場合はリソースにしましょう

変数の参照は$using:スコープ

Configuration構文内部でも、PowerShell構文は使えることを説明しました。変数が一番使いやすいでしょう。しかし、スクリプトリソース で変数を参照する時は注意が必要です。

スクリプトリソースの実態は、リモートで利用することが前提のスクリプトブロックです。この場合、ローカルとはコンフィグレーションを実行するDSCサーバー、リモートとはMOFを読み込むノードです。

そして、リモートで利用するスクリプトブロックでローカルの変数を参照するには、$using:スコープでの参照が必要なのが PowerShellのルールです。もちろん、スクリプトブロック{}内部での変数は$using: スコープは不要ですよ?

例えば、先ほどのスクリプトリソースでローカル変数を、コンフィグレーションで参照する場合はこう書きます。

ポジショナルパラメータの利用

これはリモートスクリプトブロックの制限ですが、ポジショナルパラメータの利用はできません。

先ほどの例なら、これはダメということです。

-State パラメータを明示する必要があります。

型の明示

型は明示してください。暗黙の型変換はダメです。

たとえば、[String]を受け取るパラメータには、.ToString()""で明示的にキャストが必要です。まぁ、型をあいまいにするなんて避けますよねーふつー(棒)

MOFの可読性低下

コンフィグレーションを実行した時にMOFファイルがコンパイル生成されます。MOFを読む機会は余りありませんが、ある時はあるのです。

先ほどのスクリプトリソースで生成される MOF はこうです。読みにくくて仕方ありませんね。

ネストされたコンフィグレーションの連動

LCM には、コンフィグレーションを1つだけ持てます。

ではコンフィグレーション1つに全部の定義を書かないといけないのかというとそうではありません。ネスト(組み合わせ)が可能です。

コンフィグレーション構文の中で、Nodeセクションは一回しか書けません。が、省略は可能です。(省略してMOFを生成すると localhost がデフォルトで入ります。)

そこで次のように、ネストしたコンフィグレーションで Nodeセクションを書きます。

簡単ですね!ネスト対象の子コンフィグレーションにもパラメータが渡せます。

定義の表示も効きます!が、インテリセンスとタブ補完は v5 でも効きません。しょぼーん。

f:id:guitarrapc_tech:20141207083848p:plain

ネストコンフィグレーションでのDependsOn

v4 では、ネストされたコンフィグレーションでは コンフィグレーションを跨いだDependsOnが使えず依存関係の保証ができない制限があります。

これは v5 でコンフィグレーションを跨いで DependsOnできるようになっているので、是非使ってください。

異なるコンフィグレーションファイルのコンフィグレーションを連動

PowerShell には、読み込んだファイルの実行を現在のスコープで実施するために.(ドットソース - DotSource)というオペレータがあります。

これを使うことで、別ファイルに記述されたコンフィグレーションを、読み込み先のスコープで利用できます。

で、こうかと思うじゃないですか?

これはだめです。Remoteコンフィグレーションのパラメータ渡しで InvalidOperationException が発生します。

DependsOn をはずせば問題ありません。このため、依存関係のあるコンフィグレーションは一つにまとめておく必要があります。

子コンフィグレーションのファイルが異なる場合、親であるネストしたコンフィグレーションからはパラメータが渡せません。

同一ファイルに記述された子コンフィグレーションなら渡せるのですが....

これに対して、PowerShell は 一度定義したコンフィグレーションをリソースとして利用できる仕組みを持っています。それがコンポジットリソースです。詳細は明日紹介しましょう。

コンフィグレーションによるNTPの設定/維持

Windows に限らず Linux などでも大事なものがNTPによる時刻同期です。特にAWS や Azure など仮想環境ではほっておくと時刻がどんどんずれます。

謎社では、数多くのWebサーバーを用いて一つのサービスを提供しています。大量のサーバーがあっても「時間が同期されていることを保証」することは正常なサービス提供の要です。

DSCがないころは、スクリプトでNTPの維持していましたが正直二度とやりたくないです。*6

今回、NTPをDSC でチューニングから維持まで行うコンフィグレーションを公開しましょう。*7かなり長いですが、本番でも利用しているフェイルオーバーNTP まで定義した信頼のあるものです。かなり使えますよ、というかないと話になりません。

まとめ

おおよそコンフィグレーションで抑えるべきポイントは網羅したと思います。基本的には罠といえる罠はそんなにありません。

v4では「インテリセンス」、「タブ補完」、「DependsOn」、「パラメータ参照」などの機能が足りないのは事実です。これもv5 でほとんど改善されるので是非心待ちにしましょう。

明日は、MOFの生成について説明します。

*1:インテリセンスは効かない残念さ。リソースの定義で見てね。

*2:DSCではない場合、Start-Serviceやnetsh やタスクスケジューラを組み合わせる悲惨な状況になるでしょう

*3:上から順にコンフィグレーションは実行されますけどね!

*4:PowerShell は簡単っていいますしね!ワタシパラーセルワカラナイ

*5:リソースの書き方はDay 7で説明します。

*6:valentiaは、PUSHと同様にサーバー主導で非同期に大量のサーバーへコマンド実行が可能ですが、あるべき状態の維持は機能外です。

*7:利用しているNTPは違います