Windows Server 2016のTechnical Preview 5(TP5)が公開されていたので、 TP4でバグに阻まれて挫折した、Windows ContainersでPcap4Jを使ってパケットキャプチャする試みにリトライした話。

OSセットアップ

TP4のときと同じ環境。

以降はWindows Server Containersのクイックスタートガイドに沿ってセットアップを進める。 TP4からは大分変わっていて、単一のPowershellスクリプトを実行する形式から、Powershellのコマンドレットを逐次手動実行する形式になっている。 面倒だけど何やってるかわかりやすくて好き。

コンテナ機能のインストール

  1. 管理者権限のパワーシェルウィンドウを開く

    コマンドプロンプトから以下のコマンドを実行。

    powershell start-process powershell -Verb runas
  2. コンテナ機能のインストール

    開いた青いパワーシェルウィンドウで以下のコマンドを実行するとコンテナ機能がインストールされる。

    Install-WindowsFeature containers

    数分で終わる。

    インストールされたのはHyper-V ContainersじゃなくてWindows Server Containersの方。 クイックスタートガイドをみると、前者がWindows 10向け、後者がWindows Server向けというように住み分けされているっぽい。TP4では両方ともWindows Serverで使えたんだけど。

  3. 再起動

    変更を有効にするために再起動が必要。

    Restart-Computer -Force

Dockerインストール

Dockerは、コンテナイメージの管理やコンテナの起動などもろもろの機能を提供するDockerデーモンと、その機能を利用するためのCLIを提供するDockerクライアントからなる。この節ではそれら両方をインストールする。

  1. Dockerインストールフォルダ作成

    管理者権限のパワーシェルウィンドウを開いて、以下のコマンドでDockerインストールフォルダを作成。

    New-Item -Type Directory -Path 'C:\Program Files\docker\'
  2. Dockerデーモンインストール

    まずはデーモンの方をインストール。

    Invoke-WebRequest https://aka.ms/tp5/b/dockerd -OutFile $env:ProgramFiles\docker\dockerd.exe -UseBasicParsing

    数分。

  3. Dockerクライアントインストール

    次にクライアント。

    Invoke-WebRequest https://aka.ms/tp5/b/docker -OutFile $env:ProgramFiles\docker\docker.exe -UseBasicParsing

    数十秒。

  4. パスの設定

    さっき作ったDockerインストールフォルダにパスを通す。

    [Environment]::SetEnvironmentVariable("Path", $env:Path + ";C:\Program Files\Docker", [EnvironmentVariableTarget]::Machine)
  5. Dockerデーモンをサービスに登録

    パスの設定を反映するためにいったんパワーシェルウィンドウとコマンドプロンプトを閉じて、 また管理者権限でパワーシェルウィンドウ開いて、以下のコマンドでDockerデーモンをサービスに登録する。

    dockerd --register-service
  6. Dockerデーモン起動

    Dockerデーモンは以下のコマンドで起動できる。

    Start-Service docker

    数秒で立ち上がる。 デフォルトではOS再起動時にはDockerデーモンは自動起動しないので、そのつどこのコマンドを実行する必要がある。


これでDockerインストール完了。 この時点ではコンテナイメージは何もない。

C:\Users\Administrator>docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE

因みにインストールされたDockerのバージョンは1.12開発版。現時点での最新版だ。

C:\Users\Administrator>docker -v
Docker version 1.12.0-dev, build 8e92415

コンテナイメージのインストール

次に、コンテナイメージをインストールする。

  1. コンテナイメージのパッケージプロバイダをインストール

    いまいち何なのかはよくわからないが、 コンテナイメージのパッケージプロバイダというのをインストールする。

    Install-PackageProvider ContainerImage -Force

    数十秒。

  2. Windows Server Coreのイメージをインストール

    Install-ContainerImage -Name WindowsServerCore

    9GB以上もあるファイルをダウンロードして処理するのでかなり時間がかかる。 50分くらいかかった。

  3. Dockerデーモン再起動

    Restart-Service docker


無事Windows Server Coreイメージがインストールされた。

PS C:\Users\Administrator> docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
windowsservercore   10.0.14300.1000     5bc36a335344        8 weeks ago         9.354 GB

Pcap4Jコンテナイメージのビルド

以下をC:\Users\Administrator\Desktop\pcap4j\Dockerfileに書いて、cd C:\Users\Administrator\Desktop\pcap4jして、docker build -t pcap4j .を実行。 (Notepad使ったので、拡張子を表示する設定にしてDockerfile.txtを消さないといけない罠があった。)

#
# Dockerfile for Pcap4J on Windows
#

FROM windowsservercore:10.0.14300.1000
MAINTAINER Kaito Yamada <[email protected]>

# Install Chocolatey.
RUN mkdir C:\pcap4j
WORKDIR c:\\pcap4j
ADD https://chocolatey.org/install.ps1 install.ps1
RUN powershell .\install.ps1

# Install dependencies.
RUN choco install -y nmap jdk7 && \
    choco install -y maven -version 3.2.5

# Build Pcap4J.
RUN powershell -Command Invoke-WebRequest https://github.com/kaitoy/pcap4j/archive/v1.zip -OutFile pcap4j.zip && \
    powershell -Command Expand-Archive -Path pcap4j.zip -DestinationPath .
WORKDIR pcap4j-1
RUN powershell -Command "mvn -P distribution-assembly install 2>&1 | Add-Content -Path build.log -PassThru"

# Collect libraries.
RUN mkdir bin && \
    cd pcap4j-packetfactory-static && \
    mvn -DoutputDirectory=..\bin -Dmdep.stripVersion=true -DincludeScope=compile dependency:copy-dependencies && \
    mvn -DoutputDirectory=..\bin -Dmdep.stripVersion=true -DincludeGroupIds=ch.qos.logback dependency:copy-dependencies && \
    cd ../pcap4j-distribution && \
    mvn -DoutputDirectory=..\bin -Dmdep.stripVersion=true -DincludeArtifactIds=pcap4j-packetfactory-static,pcap4j-sample dependency:copy-dependencies

# Generate sample script. (C:\pcap4j\pcap4j-1\bin\capture.bat)
RUN echo @echo off > bin\capture.bat && \
    echo "%JAVA_HOME%\bin\java" -cp C:\pcap4j\pcap4j-1\bin\pcap4j-core.jar;C:\pcap4j\pcap4j-1\bin\pcap4j-packetfactory-static.jar;C:\pcap4j\pcap4j-1\bin\pcap4j-sample.jar;C:\pcap4j\pcap4j-1\bin\jna.jar;C:\pcap4j\pcap4j-1\bin\slf4j-api.jar;C:\pcap4j\pcap4j-1\bin\logback-classic.jar;C:\pcap4j\pcap4j-1\bin\logback-core.jar org.pcap4j.sample.GetNextPacketEx >> bin\capture.bat


Dockerfileに書いた処理内容はTP4のときとだいたい同じ。 以下、Dockerfile書いているときに気付いたこと。

TP4からのアップデート

WORKDIR や ENV や COPY でパスの区切りは \ 一つだと消えちゃうので \ か / を使わないといけない。

引用元: TP4のときのエントリ

このページの各コマンドのWindows Considerationsに、WORKDIRのパスの区切りのバックスラッシュはエスケープしないといけないとか、ADDのパスの区切りはスラッシュじゃないといけないとか書いてある。 TP4のときはなかったような。


WORKDIR や COPY のコンテナ内のパスに絶対パスを指定したい場合、C:\hoge、C:/hoge、C:\hoge、いずれもダメ。 以下の様なエラーが出る。

引用元: TP4のときのエントリ

これは直った。WORKDIR c:\\pcap4jで行ける。


install.ps1の中でChocolateyのインストーラをHTTPSで取ってこようとしてエラー

引用元: TP4のときのエントリ

普通にchoco installできたので、HTTPSが使えない制限は消えた模様。


ビルドしてみると、各ステップの実行(多分レイヤの作成)がすごく遅い。

引用元: TP4のときのエントリ

各ステップの実行は相変わらず重い。特にファイル変更が多いときはすごく重い。


コンテナの起動は非常に遅い。30秒以上かかる。

引用元: TP4のときのエントリ

コンテナ起動は早くなったけどまだ5秒くらいかかる。


WORKDIR や ENV で環境変数が展開されない。

引用元: TP4のときのエントリ

これはまだ直っていない。%tmp%%TMP%$TMP${TMP}、どれもだめ。


コンテナ内で C:\ 直下に . で始まる名前のフォルダ作ると次のステップで消えてる。

引用元: TP4のときのエントリ

これは再現しなかった。以前のも勘違いだったのかもしれない。 なんにせよデフォルトの.m2フォルダのパスがC:\Users\ContainerAdministrator\.m2になったので気にしなくてよくなった。

ビルドエラー: hcsshim::ImportLayer failed in Win32: The filename or extension is too long. (0xce)

choco installの後で以下のエラーが出た。

re-exec error: exit status 1: output: time="2016-07-09T19:57:22-07:00" level=error msg="hcsshim::ImportLayer failed in Win32: The filename or extension is too long. (0xce) layerId=\\\\?\\C:\\ProgramData\\docker\\windowsfilter\\103de6bf1358c506510ad67990f09ec3e2f10f9e866e846df5a88c04f5edf7aa flavour=1 folder=C:\\Windows\\TEMP\\hcs719016711"
hcsshim::ImportLayer failed in Win32: The filename or extension is too long. (0xce) layerId=\\?\C:\ProgramData\docker\windowsfilter\103de6bf1358c506510ad67990f09ec3e2f10f9e866e846df5a88c04f5edf7aa flavour=1 folder=C:\Windows\TEMP\hcs719016711

調べたらDockerのGitHub Issuesに登録されていた。 ここのコメントを参考に以下のコマンドでコンテナホストのアップデートをしたら発生しなくなった。

Invoke-WebRequest https://aka.ms/tp5/Update-Container-Host -OutFile update-containerhost.ps1
.\update-containerhost.ps1
Restart-Computer -Force

git cloneできない

Pcap4Jのソースをダウンロードしたかったんだけど、なぜかgit cloneがHTTPSでもGITプロトコルでもエラーを返す。 原因を調べるのが面倒で結局zipでダウンロードするようにした。

未実装の機能

Dockerfileのリファレンスに載っていて、Windows向けのサンプルも書いてあるのに、escapeディレクティブSHELLコマンド が使えなかった。

コンテナ起動

とりあえず上記DockerfileでPcap4Jコンテナイメージのビルドはできた。

以下のコマンドでそのイメージからコンテナを起動。

C:\Users\Administrator>docker run -it pcap4j cmd

コンテナ内でipconfigするとvEthernet (Temp Nic Name)という名のネットワークインターフェースがあることがわかる。

C:\pcap4j\pcap4j-1\bin>ipconfig

Windows IP Configuration


Ethernet adapter vEthernet (Temp Nic Name):

   Connection-specific DNS Suffix  . : localdomain
   Link-local IPv6 Address . . . . . : fe80::59cf:1491:6f8e:30c8%18
   IPv4 Address. . . . . . . . . . . : 172.23.71.6
   Subnet Mask . . . . . . . . . . . : 255.240.0.0
   Default Gateway . . . . . . . . . : 172.16.0.1

けどPcap4Jからは見えなかった。

C:\pcap4j\pcap4j-1\bin>capture.bat
org.pcap4j.sample.GetNextPacketEx.count: 5
org.pcap4j.sample.GetNextPacketEx.readTimeout: 10
org.pcap4j.sample.GetNextPacketEx.snaplen: 65536


18:49:00.582 [main] INFO  org.pcap4j.core.Pcaps - No NIF was found.
java.io.IOException: No NIF to capture.
        at org.pcap4j.sample.GetNextPacketEx.main(GetNextPacketEx.java:45)java:44)


コンテナにはContainerAdministratorというユーザでログインしていて、これの権限が弱いせいなんじゃないかと。 コンテナ内にもAdministratorというユーザがあるようだったので、こっちでコマンド実行するよう奮闘した。

コンテナ内でAdministratorでコマンド実行したい

USERコマンド

DockerfileのコマンドにUSERというのがあるので、USER AdministratorをDockerfileの末尾に追加してみたら以下のエラー。

The daemon on this platform does not support the command 'user'

–userオプション

docker runコマンドに–userというオプションがあるので以下のように試してみたところ、オプションは無視されてContainerAdministratorでコンテナに入った。

docker run -it --user Administrator pcap4j cmd

runas

ちょっと発想の転換をして、ContainerAdministratorでコンテナに入った後sudoみたいなことをすればいいかと思い、runasコマンドを試したけどだめだった。 よく分からないエラーがでるし、そもそもAdministratorのパスワードがわからない。

C:\pcap4j\pcap4j-1\bin>runas /user:Administrator cmd
Enter the password for Administrator:
Attempting to start cmd as user "92EC7B3B09B4\Administrator" ...
RUNAS ERROR: Unable to run - cmd
1326: The user name or password is incorrect.
C:\pcap4j\pcap4j-1\bin>runas /user:"User Manager\Administrator" capture.bat
Enter the password for User Manager\Administrator:
RUNAS ERROR: Unable to acquire user password

Enter-PSSession

フォーラムに行ったらEnter-PSSessionを使う方法が書いてあった。

Enter-PSSessionはリモートシステムに接続するコマンドレットで、-ContainerNameオプションを使えばコンテナにも接続できる。

試したら、コンテナが見つからないというエラーが出た。

C:\Users\Administrator>powershell -command Enter-PSSession -ContainerName amazing_archimedes -RunAsAdministrator
Enter-PSSession : The input ContainerName amazing_archimedes does not exist, or the corresponding container is not running.
At line:1 char:1
+ Enter-PSSession -ContainerName amazing_archimedes -RunAsAdministrator
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (:) [Enter-PSSession], PSInvalidOperationException
    + FullyQualifiedErrorId : CreateRemoteRunspaceForContainerFailed,Microsoft.PowerShell.Commands.EnterPSSessionCommand

Invoke-Commandもコンテナをターゲットにできるので試してみたけど、同様のエラー。

どうもパワーシェルで扱うコンテナやコンテナイメージが、dockerコマンドが扱うものとは別になっているせいっぽい。 そんなことがTP4のときに見たドキュメントに書いてあったのを思い出した。(このドキュメントは消えてた。)

実際、docker psでは見えているコンテナが、

C:\Users\Administrator>docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
a711497f29d8        pcap4j              "cmd"               13 minutes ago      Up 12 minutes                           amazing_archimedes

コマンドレットからだと見えない。

C:\Users\Administrator>powershell -command Get-Container
WARNING: Based on customer feedback, we are updating the Containers PowerShell module to better align with Docker. As part of that some cmdlet and parameter names may change in future releases. To learn more about these changes as well as to join in the design process or provide usage feedback please refer to http://aka.ms/windowscontainers/powershell

そうなると、パワーシェルのコマンドレットにはdocker buildにあたるものがないのでもうどうしようもない。

そもそも、TP4の頃のコマンドレットは廃止になって新しいコマンドレットを開発中らしい。やはりdockerコマンドとコマンドレットでコンテナの相互運用ができない仕様にユーザから相当つっこみがあったようだ。

Enter-PSSessionInvoke-Command-ContainerNameオプションもその内修正されるであろう。 それまで待つか。