share facebook facebook2 twitter menu hatena pocket slack

2014.11.12 WED

ANSIBLEからWINDOWSを叩く

sebastian

WRITTEN BYsebastian

cloudpack の Sebastian です。

ansibleが何かはお勉強してね?
簡単に話すとsshを使ってremote serverの管理・構成を行うツールです。

Windowsでssh?

LinuxならSSH+shellでloginと操作ができるけれどWindowsにはsshdなんて入っていません。
じゃあ、どうやって繋ぐかと言うと公式documentには

Starting in version 1.7, Ansible also contains support for managing Windows machines. This uses native powershell remoting, rather than SSH.
(バージョン1.7からWindowsマシン管理のサポートを開始しました。これはSSHでは無くてWindows NativeのPowershellリモーティングを使います)
Ansible will still be run from a Linux control machine, and uses the “winrm” Python module to talk to remote hosts.
(Ansibleは未だにLinuxから実行され、リモートホストとの通信にwinrm python moduleを使用します。)

と書いてある
Windowsに対してはWinRMで繋いでPowershell 3.0を叩いて使うようだね

WinRMって?

Windows Remote Managementの事だね。
訳すとそのままWindows遠隔管理
読んで時の如く、Windowsを外から操作する為の管理システムだね
Windowsは2009年にWindows Vista / Windows Server 2003 R2以降にWindows Remote Management (WinRM) と Windows Remote Shell (WinRS) という機能が追加されて居ますね
どうやらAnsibleはこの機能に接続してWindowsを外から操作する事を試みているようです。
MicrosoftのWinRmの説明を見てみると

Windows リモート管理 (WinRM) サービスは、リモート管理用の WS-Management プロトコルを実装しています。WS-Management は、リモートのソフトウェアとハードウェアの管理に使用される標準の Web サービス プロトコルです。WinRM サービスは、ネットワーク上で WS-Management 要求をリッスンして処理します。WinRM サービスがネットワークをリッスンできるように、winrm.cmd コマンド ライン ツールを使用するかグループ ポリシーを介して、WinRM サービスにリスナを構成する必要があります。このサービスを使用することにより、WMI データへのアクセスとイベントの収集が可能になります。イベントの収集およびイベントに対するサブスクリプションを実行する場合、WinRM サービスが稼働している必要があります。WinRM メッセージは、HTTP および HTTPS をトランスポートとして使用します。WinRM サービスは IIS には依存しませんが、同じコンピュータ上で 1 つのポートを IIS と共有するよう事前に設定されています。このサービスでは URL のプレフィックスとして /wsman が予約されています。IIS との競合を避けるため、IIS 上のどの Web サイトでも /wsman が URL プレフィックスとして使用されていないことを確認する必要があります

HTTPとHTTPS上にてMessageの送受信を行うWS-Managementってプロトコルが在るわけですね。

先に書いておくとansibleからWinRMで幸せな通信を行うにはやることは6つ

  1. WinRMのserviceを起動せよ
  2. WinRM用にLisnerを作成せよ
  3. portを開ろ
  4. NetworkProfileのカテゴリをPrivateにしておこう
  5. HTTPからの受付の有効化
  6. Basic認証の許可

今更、Windows XPやWindows 2003は引っ張りだす事は少ないと思うけれど、もし古いマシンも管理に加えたいならここで更新してWindowsXPにWinRM入れておくと幸せになれるかも知れない。
  ↑
ごめん、WindowsXPやWindows2003はWinRM入ってもPowershell 3.0が入らないからansible使えないね。

WinRMの起動

Windows 2012 辺りだと最初から起動している事が多いようですね

さて、まずpowershellを立ち上げておもむろに

PS C:UsersAdministrator>winrm quickconfig

もしくは

PS C:UsersAdministrator>winrm qc

と打ち込んでみましょう
既にWinRMがserviceとして起動していると

WinRM service is already running on this machine.
WinRM is already set up for remote management on this computer.

起動していないと以下の様に起動するか聞かれます
と聞かれます
yを入力すると下記のメッセージが出てWinRMが起動します。

WinRM is not set up to receive requests on this machine.
The following changes must be mode;

Start the winRM service.

Make these changes [y/n]?

WinRM has been updated to receive requests.

WinRM service started.
WinRM is already set up for remote management on this computer.

日本語だとこんな感じになりますね。

WinRM は、管理用にこのコンピュータへのリモート アクセスを許可するように設定されていません。
次の変更を行う必要があります:

WinRM サービスの種類を遅延自動開始に設定します。
WinRM サービスを開始します。
このコンピュータ上のあらゆる IP への WS-Man 要求を受け付けるため、HTTP://* 上に WinRM リスナを作成します。

変更しますか [y/n]? y

WinRM はリモート管理用に更新されました。

WinRM サービスの種類を正しく変更できました。
WinRM サービスが開始されました。
このコンピュータ上のあらゆる IP への WS-Man 要求を受け付けるため、HTTP://* 上に WinRM リスナを作成しました。

因みにコマンドラインで操作しなくてもコンピュータの管理からサービスを見ると当たり前にWindows Remote Management(WS-Management)が居るのでこれを起動するだけで大丈夫です。

Listenerの作成

上記のwinrm qcコマンドは名前通りQuick Configなので実はこの時点でLisnerが作成されててPortも空いてます。
手動でListenerを作る際は下記のコマンドで作成します。

PS C:UsersAdministrator> winrm create winrm/config/listener?Address=*+Transport=HTTP

既にQuickConfigにて作成されているので下の様なエラーメッセージが出て既に在るからもう作れないと言われて終わるだけですけれどね

English
WSManFault
    Message
        ProviderFault
            WSManFault
                Message = The WS-Management service cannot perform the configuration operation. A listener with Address=* and Transport=HTTP configuration already exists. You have to delete the existing listener first in order to be able to create it with the same Address and Transport values.

Error number:  -2144108493 0x80338033
The WS-Management service cannot create the resource because it already exists.
日本語
WSManFault
    Message
        ProviderFault
            WSManFault
                Message = WS-Management サービスは構成操作を実行できません。アドレス=* およびトランスポート=HTTP の構成のリスナは は既に存在します。 同じアドレスおよびトランスポート値で作成するには、まず既存のリスナを 削除する必要があります。

エラー番号:  -2144108493 0x80338033
既に存在するため、WS-Management サービスはリソースを作成できません。

Portの解放

これもQuickConfigで開放されてるっぽいので特に必要無さそう
一応書いておくと以下のコマンドで通る

netsh firewall add portopening TCP 80 "Windows Remote Management" 

実はここまで殆どcommand一つで出来るって書いてから気がついた

Enable-PSRemoting -Force

これだけで

  1. WinRMを起動する
  2. WinRMサービスのスタートアップの種類を自動に
  3. どのIPアドレスからでも受け付けるリスナ作成
  4. Windows FirewallにWS-Management traffic (httpのみ)の例外を作成
  5. WinRMで接続した時に管理者権限で実行の設定

が全部終わるらしい・・・..
しかもWindos2012 R2は初期状態で実行された状態になってるらしいよorz

尚、無効にする時は以下のcommandで行けます。

Disable-PSRemoting

NetworkProfileのカテゴリを変更する

はい、気を取り直して次!
最近のWindowsがnetwork繋ぐときに聞いてくるパブリックネットワークかプライベートネットワークか社内ネットワークかってやつね
取り敢えず今どうなってるか確認しておこう

PS C:UsersAdministrator> Get-NetConnectionProfile -IPv4Connectivity Internet

上のcommandを実行するとこんな感じに返ってくる
この中で確認するべきはNetworkCategoryの欄

Name             : Network  2
InterfaceAlias   : Ethernet
InterfaceIndex   : 12
NetworkCategory  : Public
IPv4Connectivity : Internet
IPv6Connectivity : NoTraffic


Publicになっているので以下のcommandを入力してNetworkCategoryをPrivateに変更しておく

Set-NetConnectionProfile -InterfaceAlias (Get-NetConnectionProfile -IPv4Connectivity Internet).InterfaceAlias -NetworkCategory Private

Basic認証を許可しよう

Ansibleはパスワード投げて通信するので認証が許可されないと行けません。
そういう訳で認証の許可を行います。
WinRMのsetコマンドにて設定を書き換えます。

PS C:UsersAdministrator> winrm set winrm/config/service/auth '@{Basic="true"}'

以下の用に実行結果が返ります。
設定値auth以下の値が一覧で返ってきてBasictrueになっているのがわかります。

Auth
    Basic = true
    Kerberos = true
    Negotiate = true
    Certificate = false
    CredSSP = false
    CbtHardeningLevel = Relaxed

また、確認するだけならgetコマンドで同じ結果が取得できます。

PS C:UsersAdministrator> winrm get winrm/config/service/auth

HTTP通信からの接続を受け付ける

HTTPをトランスポートとして利用することを許可しないとAnsibleからの通信は通りません
そこで設定変えます

PS C:UsersAdministrator> winrm set winrm/config/service '@{AllowUnencrypted="true"}'
Service
    RootSDDL = O:NSG:BAD:P(A;;GA;;;BA)(A;;GR;;;IU)S:P(AU;FA;GA;;;WD)(AU;SA;GXGW;;;WD)
    MaxConcurrentOperations = 4294967295
    MaxConcurrentOperationsPerUser = 1500
    EnumerationTimeoutms = 240000
    MaxConnections = 300
    MaxPacketRetrievalTimeSeconds = 120
    AllowUnencrypted = true
    Auth
        Basic = true
        Kerberos = true
        Negotiate = true
        Certificate = false
        CredSSP = false
        CbtHardeningLevel = Relaxed
    DefaultPorts
        HTTP = 5985
        HTTPS = 5986
    IPv4Filter = *
    IPv6Filter = *
    EnableCompatibilityHttpListener = false
    EnableCompatibilityHttpsListener = false
    CertificateThumbprint
    AllowRemoteAccess = true

PS C:UsersAdministrator> winrm get winrm/config/service

これでUndecryptedつまり暗号化されていないHTTPも受け付けると言う設定です。

最終的には以下の設定になればOKです

PS C:UsersAdministrator> winrm get winrm/config
Config
    MaxEnvelopeSizekb = 500
    MaxTimeoutms = 60000
    MaxBatchItems = 32000
    MaxProviderRequests = 4294967295
    Client
        NetworkDelayms = 5000
        URLPrefix = wsman
        AllowUnencrypted = false
        Auth
            Basic = true
            Digest = true
            Kerberos = true
            Negotiate = true
            Certificate = true
            CredSSP = false
        DefaultPorts
            HTTP = 5985
            HTTPS = 5986
        TrustedHosts = *
    Service
        RootSDDL = O:NSG:BAD:P(A;;GA;;;BA)(A;;GR;;;IU)S:P(AU;FA;GA;;;WD)(AU;SA;GXGW;;;WD)
        MaxConcurrentOperations = 4294967295
        MaxConcurrentOperationsPerUser = 1500
        EnumerationTimeoutms = 240000
        MaxConnections = 300
        MaxPacketRetrievalTimeSeconds = 120
        AllowUnencrypted = true
        Auth
            Basic = true
            Kerberos = true
            Negotiate = true
            Certificate = false
            CredSSP = false
            CbtHardeningLevel = Relaxed
        DefaultPorts
            HTTP = 5985
            HTTPS = 5986
        IPv4Filter = *
        IPv6Filter = *
        EnableCompatibilityHttpListener = false
        EnableCompatibilityHttpsListener = false
        CertificateThumbprint
        AllowRemoteAccess = true
    Winrs
        AllowRemoteShellAccess = true
        IdleTimeout = 7200000
        MaxConcurrentUsers = 10
        MaxShellRunTime = 2147483647
        MaxProcessesPerShell = 25
        MaxMemoryPerShellMB = 1024
        MaxShellsPerUser = 30

Powershell ?

公式Documentを見ると

Powershell 3.0 or higher is needed for most provided Ansible modules for Windows
(Windows用AnsibleモジュールはPowershell 3.0以上が必要です。)

現時点では最新環境は4.0でAnsibleが求めているのは3.0
WikipediaにあるWindowsとの対比表が以下の通り

「◎」標準搭載、「○」インストールして利用可能、「→」上位バージョン標準搭載、「×」利用不可

項目 1.0 2.0 3.0 4.0
Windows Server 2003 × ×
Windows Server 2003 R2 × ×
Windows Server 2008 ×
Windows Server 2008 R2
Windows Server 2012
Windows Server 2012 R2
Windows XP x64 × × ×
Windows XP × ×
Windows Vista × ×
Windows 7
Windows 8 ×
Windows 8.1

この一覧からするとServerサイドはWindows 2008以上
一般的なWindowsだとWindows 7以上が必要条件になりそうである。
まぁ、XPはサポートの終了したOSなのでそもそも推奨するものではないので良いだろう

Windows2008などは標準でPowershell 2.0なのでバージョンアップが必要なのでMicrosoftからDownloadしてinstallしましょう

公式Documentに以下の様に記載しているのでここで指定されているscriptファイルを使っても良いかもしれない。
内容は単に、Powershellをdownloadして実行しているだけのようだし

Looking at an ansible checkout, copy the examples/scripts/upgrade_to_ps3.ps1 script onto the remote host and run a powershell console as an administrator. You will now be running Powershell 3 and can try connectivity again using the win_ping technique referenced above.

Ansible側の設定

WinRM対応

ansible側ではpython経由でWinRMに接続できるようにする必要があります。
PythonにはWinRM接続用のモジュールがリリースされておりこれを導入すればOKです。
以下のcommandを入力しpythonにpywinrmモジュールを追加して下さい。

pip install http://github.com/diyan/pywinrm/archive/master.zip#egg=pywinrm

BugFix

実はwinrmモジュールにはバグがあります

winrm.py: protocol.send_message(“) crashes with https

1行だけなのでLinuxの場合は以下のcommandで対処できます。

sed -i.bak '90s/exc.args[0]/exc/' /usr/lib/python2.7/site-packages/ansible/runner/connection_plugins/winrm.py

OSXでAnsibleを動作させている場合はLibraryのpathが異なるので以下の様になります。

sed -i.bak '90s/exc.args[0]/exc/' /Library/Python/2.7/site-packages/ansible/runner/connection_plugins/winrm.py

実際にやっていることはwinrm.pyの90行目にて.args[0]と言う文字列を削除しているだけなので手で消しても問題は無い。

90c90
<                 err_msg = str(exc.args[0])
---
>                 err_msg = str(exc)

また、これらのcommandはpathがPythonのバージョンで異なるので任意のバージョンにて2.7の文字列を差し替えて考える。

Inventoryの指定

はい、じゃあAmazon ec2でWindows 2012 R2を起動して上の作業をサクサク進めます。
Windows2012 R2はPowershellは3.0以上でWinRMも最初から入っているのでNetwork Profileのcategory変えて認証を通して上げてHTTP通信許可すればそれで通ります。

ansible側のinventoryの設定は以下のように指定します。

[windows]
ec2_windows     ansible_ssh_host=54.64.xxx.xxx  ansible_ssh_user=Administrator  ansible_ssh_pass=XXXXXXXXX    ansible_ssh_port=5985   ansible_connection=winrm

remote hostがLinuxの場合と異なる点は ansible_connectionの値がwinrmである点とansible_ssh_portの値が5985となっている点です。

実行してみる

試しにWindows用のpingモジュールを使ってみる
一応、Debug出力も指定(-vvvv)して何が返るかを確認してみよう

ansible -m win_ping ec2_windows -vvvv
01   <54.64.xxx.xxx> ESTABLISH WINRM CONNECTION FOR USER: Administrator on PORT 5985 TO 54.64.xxx.xxx
02   <54.64.xxx.xxx> WINRM CONNECT: transport=plaintext endpoint=http://54.64.xxx.xxx:5985/wsman
03   <54.64.xxx.xxx> REMOTE_MODULE win_ping
04   <54.64.xxx.xxx> EXEC (New-Item -Type Directory -Path $env:temp -Name "ansible-tmp-1414376293.75-207546716604835").FullName | Write-Host -Separator '';
05   <54.64.xxx.xxx> WINRM EXEC 'PowerShell' ['-NoProfile', '-NonInteractive', '-EncodedCommand','KABOAGUAdwAtAEkAdABlAG0AIAAtAFQAeQBwAGUAIABEAGkAcgBlAGMAdABvAHIAeQAgAC0AUABhAHQAaAAgACQAZQBuAHYAOgB0AGUAbQBwACAALQBOAGEAbQBlACAAIgBhAG4AcwBpAGIAbABlAC0AdABtAHAALQAxADQAMQA0ADMANwA2ADIAOQAzAC4ANwA1AC0AMgAwADcANQA0ADYANwAxADYANgAwADQAOAAzADUAIgApAC4ARgB1AGwAbABOAGEAbQBlACAAfAAgAFcAcgBpAHQAZQAtAEgAbwBzAHQAIAAtAFMAZQBwAGEAcgBhAHQAbwByACAAJwAnADsA']
06   <54.64.xxx.xxx> WINRM RESULT <Response code 0, out "C:UsersAdministrat", err "">
07   <54.64.xxx.xxx> PUT /tmp/tmpKkQpAi TO C:UsersAdministratorAppDataLocalTempansible-tmp-1414376293.75-207546716604835\win_ping
08   <54.64.xxx.xxx> WINRM PUT /tmp/tmpKkQpAi to C:UsersAdministratorAppDataLocalTempansible-tmp-1414376293.75-207546716604835\win_ping.ps1 (offset=0 size=2030)
09   <54.64.xxx.xxx> WINRM PUT /tmp/tmpKkQpAi to C:UsersAdministratorAppDataLocalTempansible-tmp-1414376293.75-207546716604835\win_ping.ps1 (offset=2030 size=2030)
10   <54.64.xxx.xxx> WINRM PUT /tmp/tmpKkQpAi to C:UsersAdministratorAppDataLocalTempansible-tmp-1414376293.75-207546716604835\win_ping.ps1 (offset=4060 size=1425)
11   <54.64.xxx.xxx> PUT /tmp/tmpC6QNR7 TO C:UsersAdministratorAppDataLocalTempansible-tmp-1414376293.75-207546716604835\arguments
12   <54.64.xxx.xxx> WINRM PUT /tmp/tmpC6QNR7 to C:UsersAdministratorAppDataLocalTempansible-tmp-1414376293.75-207546716604835\arguments (offset=0 size=2)
13   <54.64.xxx.xxx> EXEC PowerShell -NoProfile -NonInteractive -ExecutionPolicy Unrestricted -File "C:UsersAdministratorAppDataLocalTempansible-tmp-1414376293.75-207546716604835\win_ping.ps1" "C:UsersAdministratorAppDataLocalTempansible-tmp-1414376293.75-207546716604835\arguments"; Remove-Item "C:UsersAdministratorAppDataLocalTempansible-tmp-1414376293.75-207546716604835" -Force -Recurse;
14   <54.64.xxx.xxx> WINRM EXEC 'PowerShell' ['-NoProfile', '-NonInteractive', '-EncodedCommand','UABvAHcAZQByAFMAaABlAGwAbAAgAC0ATgBvAFAAcgBvAGYAaQBsAGUAIAAtAE4AbwBuAEkAbgB0AGUAcgBhAGMAdABpAHYAZQAgAC0ARQB4AGUAYwB1AHQAaQBvAG4AUABvAGwAaQBjAHkAIABVAG4AcgBlAHMAdAByAGkAYwB0AGUAZAAgAC0ARgBpAGwAZQAgACIAQwA6AFwAVQBzAGUAcgBzAFwAQQBkAG0AaQBuAGkAcwB0AHIAYQB0AG8AcgBcAEEAcABwAEQAYQB0AGEAXABMAG8AYwBhAGwAXABUAGUAbQBwAFwAYQBuAHMAaQBiAGwAZQAtAHQAbQBwAC0AMQA0ADEANAAzADcANgAyADkAMwAuADcANQAtADIAMAA3ADUANAA2ADcAMQA2ADYAMAA0ADgAMwA1AFwAXAB3AGkAbgBfAHAAaQBuAGcALgBwAHMAMQAiACAAIgBDADoAXABVAHMAZQByAHMAXABBAGQAbQBpAG4AaQBzAHQAcgBhAHQAbwByAFwAQQBwAHAARABhAHQAYQBcAEwAbwBjAGEAbABcAFQAZQBtAHAAXABhAG4AcwBpAGIAbABlAC0AdABtAHAALQAxADQAMQA0ADMANwA2ADIAOQAzAC4ANwA1AC0AMgAwADcANQA0ADYANwAxADYANgAwADQAOAAzADUAXABcAGEAcgBnAHUAbQBlAG4AdABzACIAOwAgAFIAZQBtAG8AdgBlAC0ASQB0AGUAbQAgACIAQwA6AFwAVQBzAGUAcgBzAFwAQQBkAG0AaQBuAGkAcwB0AHIAYQB0AG8AcgBcAEEAcABwAEQAYQB0AGEAXABMAG8AYwBhAGwAXABUAGUAbQBwAFwAYQBuAHMAaQBiAGwAZQAtAHQAbQBwAC0AMQA0ADEANAAzADcANgAyADkAMwAuADcANQAtADIAMAA3ADUANAA2ADcAMQA2ADYAMAA0ADgAMwA1AFwAIgAgAC0ARgBvAHIAYwBlACAALQBSAGUAYwB1AHIAcwBlADsA']
15   <54.64.xxx.xxx> WINRM RESULT <Response code 0, out "{ "changed": f", err "">
ec2_windows | success >> {
    "changed": false,
    "ping": "pong"
}

見ていると、responseの4行目でremoteのtemporaryにansible-tmp-1414376293.75-207546716604835"ディレクトリを作成しているようだ
どうやらlinux用の設定のためかansible.cfgのremote_tmpは無視されている。
5行目は暗号化されているcommandをPowershellにて実行して6行目はそのresult
7行目以降はansibleのresponse用の一時ファイルを転送しているのでここでまずC:\Users\ユーザー名\AppData\Local\Temp\にansible用にresponerのPowershellを転送している。
14行目で実際にpingを実行して15行目がresponse結果である。

元記事はこちらです。
ANSIBLEからWINDOWSを叩く

sebastian

sebastian