tech.guitarrapc.cóm

Technical updates

Windows 10 Home を Windows 2019 Server on Azure で Nested Hyper-V にインストールする

Windows 10 Home の WSL2 + Docker を検証するための環境を考えた時、Windows 10 Pro の Hyper-V にいれるのが手っ取り早いのですが、CPU仮想化支援の有効化コマンドレットに気づかず Windows Server 2019 上の Windows 10 Home じゃないとダメだっけと思ってさまよった時のメモ。

結果は VMのCPU仮想化支援を有効にすればどちらも変わらず動作する。

前の記事の前段となる個人的なメモです。ローカル環境が Windows 10 Home の人が環境を汚さずやるにはいいのでは。

tech.guitarrapc.com

目次

[:contens]

TL;DR

  • Windows 10 Home を Nested Hyper-V で動かす。
  • Windows 10 Home on Hyper-V と変わらなず wsl2 + Docker Edge は動作する (Hyper-V の Windows 10 Home VMのの CPU 仮想化支援を有効にしよう)
  • Insiderビルドが長すぎるのでWindows 2004、あらためWindows 10 May 2020 Update はよ、ふたたび。

前提

  • Dv3 or Ev3 が必要

Azure Virtual Machines で入れ子になった仮想化を有効にする方法 | Microsoft Docs

VMの構成

久々に terraform を使わず Azure Portal を確認してみます。 いつの間にか スポットインスタンスができるようになってて感動した。

適当に Resource Group を作っておきます。

f:id:guitarrapc_tech:20200421040931p:plain

  • Windows Server Gen2 Preview のイメージで Windows Server 2019 とする。
  • Spot Instance
  • D4s_v3
  • Standard SSD

f:id:guitarrapc_tech:20200421040323p:plain f:id:guitarrapc_tech:20200421040415p:plain f:id:guitarrapc_tech:20200421040422p:plain f:id:guitarrapc_tech:20200421040430p:plain f:id:guitarrapc_tech:20200421040438p:plain

全部できるとリソースこんな感じ。

f:id:guitarrapc_tech:20200421041006p:plain

Nested Hyper-V の準備

Install-WindowsFeature -Name Hyper-V -IncludeManagementTools -Restart

再起動後に VM Swtich の構成

New-VMSwitch -Name "InternalNAT" -SwitchType Internal
$ifIndex = (Get-NetAdapter |where Name -match InternalNAT).ifIndex
New-NetIPAddress -IPAddress 192.168.0.1 -PrefixLength 24 -InterfaceIndex $ifIndex

NAT Network の構成

New-NetNat -Name "InternalNat" -InternalIPInterfaceAddressPrefix 192.168.0.0/24

Hyper-V を開いてVMの構築。この時Network は Internal NAT を向ける

f:id:guitarrapc_tech:20200421040527p:plain
Hyper-V はサクッと。NAT 指定だけ忘れずに

Windows 10 Home ISO を用意

MediaCreationTool1909 を入れる。IE がアレなので curl で落とす。

https://www.microsoft.com/ja-jp/software-download/windows10

curl -L  https://go.microsoft.com/fwlink/?LinkId=691209 -o MediaCreationTool1909.exe

f:id:guitarrapc_tech:20200421040554p:plain

f:id:guitarrapc_tech:20200421040602p:plain

ISO を設定

f:id:guitarrapc_tech:20200421040609p:plain

Boot Order を修正して ISO 起動できるようにする

f:id:guitarrapc_tech:20200421040616p:plain

あとは Windows 10 Home を入れる。

f:id:guitarrapc_tech:20200421040623p:plain

DHCP Server を Azure VM に用意

Azure VM 上の Nested Hyper-V ということは DHCP ないので、適当に Windows Server 2019 から DHCPを Internal NAT に配布します。

Install-WindowsFeature -Name DHCP -IncludeManagementTools

DHCP ツールを起動して、IPv4に新規スコープを追加し、 192.168.0.100-200/24 を設定

f:id:guitarrapc_tech:20200421040704p:plain

デフォルトゲートウェイに InternalNAT の 192.168,0.1 を指定

f:id:guitarrapc_tech:20200421040712p:plain

これで Hyper-V の VM を起動したり、ネットワークアダプターの無効/有効でIPが取得される。

f:id:guitarrapc_tech:20200421040719p:plain

残り

Windows 10 Home on Hyper-V (Windows 10 Pro) と同じ。

tech.guitarrapc.com

用が済んだら Resource Group ごとぐっぱい。Terraform 以外で作るの久々で相変わらずめんどくさかった。

f:id:guitarrapc_tech:20200421041044p:plain

Windows 10 Home を Hyper-V にインストールする

Windows 10 Home の WSL2 + Docker を検証するための環境を考えた時、Windows 10 Pro の Hyper-V にいれるのが手っ取り早いのですが久々に Hyper-V 触ったので改めてメモ。

前の記事の前段となる個人的なメモです。ほしい内容は自分で書くしかない。

tech.guitarrapc.com

目次

[:contens]

TL;DR

  • Windows 10 Home を Hyper-V で動かす。
  • Windows 10 Home on Hyper-V でも wsl2 + Docker Edge は動作する (Hyper-V の Windows 10 Home VMのの CPU 仮想化支援を有効にしよう)
  • Insiderビルドが長すぎるのでWindows 2004、あらためWindows 10 May 2020 Update はよ。

Windows 10 のISO を入手する

Media Creation Tool で Windows 10 の ISO を生成する。

Windows 10 のダウンロード

2020/4/17 時点では、Windows10 1909 の生成ができる。

f:id:guitarrapc_tech:20200421034554p:plain
この記事では最新のMediaCreationTool1909 を用います

f:id:guitarrapc_tech:20200421034759p:plain

f:id:guitarrapc_tech:20200421034807p:plain
Hyper-V でマウントするためISO を作る

Single Language Windows でISOが作成されるので、Language を選んでおく。 今回は English (United Kingdom)

f:id:guitarrapc_tech:20200421034833p:plain
日本語でもなんでもお好きに

これでISOが生成される。

f:id:guitarrapc_tech:20200421034851p:plain

Windows 10 を Hyper-V にインストール

Hyper-V で新規VM を作成する

f:id:guitarrapc_tech:20200421034857p:plain
この辺りはコマンドでもいいけど GUI でいいや気分

先ほどのISO を指定して起動する

f:id:guitarrapc_tech:20200421034922p:plain
Local Installation source から Change Install source で ISO を指定

VM名を付けて生成したら起動する。

Windows 10 Home を選ぶ

f:id:guitarrapc_tech:20200421034949p:plain

ライセンスを入れる。

ここでライセンスを入れないとエラーになるので注意。

f:id:guitarrapc_tech:20200421035034p:plain
ライセンスを skip しようとするとエラー

Custom でインストールする。

f:id:guitarrapc_tech:20200421035053p:plain
Upgrade でも Custom でもどちらでもいい

f:id:guitarrapc_tech:20200421035112p:plain
Custom だとパーティションが設定できるけど Hyper-V なのでどうでもよし

インストールが実行される

f:id:guitarrapc_tech:20200421035132p:plain

何度か再起動がかかる

f:id:guitarrapc_tech:20200421035141p:plain

起動してきたら Region を選択

f:id:guitarrapc_tech:20200421035148p:plain

Keyboard を選択

f:id:guitarrapc_tech:20200421035155p:plain

Second Keyboard は不要なので skip

起動を待つ

f:id:guitarrapc_tech:20200421035202p:plain

再起動が走り、ユーザー選択になる。

f:id:guitarrapc_tech:20200421035238p:plain

Local User でのセットアップはできず、起動後に削除しろと言われる。

f:id:guitarrapc_tech:20200421035247p:plain

ローカルユーザーを作成するには、ネットワークを切って新規でユーザーを作ろうとすることで作成できる。(分かりにくい)

まずネットワークを切ろう。

f:id:guitarrapc_tech:20200421035307p:plain
VM の Settings から Network Adator を開く

Virtual switch を Not Connected に変更する。

f:id:guitarrapc_tech:20200421035335p:plain

Create account から ユーザーを適当に作ろうとすると Womething went wrong となる。

f:id:guitarrapc_tech:20200421035343p:plain

ここで Skip を選択すると ローカルアカウントで作成できる。

f:id:guitarrapc_tech:20200421035349p:plain
Insider Preview でどのみち Microsoft アカウントが必須なのでローカルアカウントの意味は薄い

あとはセットアップをしていく

f:id:guitarrapc_tech:20200421035427p:plain

f:id:guitarrapc_tech:20200421035434p:plain

f:id:guitarrapc_tech:20200421035440p:plain

f:id:guitarrapc_tech:20200421035446p:plain

f:id:guitarrapc_tech:20200421035538p:plain

f:id:guitarrapc_tech:20200421035451p:plain

f:id:guitarrapc_tech:20200421035457p:plain

これで起動する。

f:id:guitarrapc_tech:20200421035503p:plain

起動後の設定

  • ネットワークを戻す
  • Language で日本語を入れておく
  • システムロケールの日本語設定とUTF8化
  • Image の Checkpoint を作っておく
  • ホストマシンからVMのCPU 仮想化支援機能を有効にする

ネットワークを戻す

f:id:guitarrapc_tech:20200421035554p:plain

日本語を入れる

OSは英語だけど日本語ほしい人はストアからどうぞ

f:id:guitarrapc_tech:20200421035611p:plain

f:id:guitarrapc_tech:20200421035618p:plain

f:id:guitarrapc_tech:20200421035626p:plain

f:id:guitarrapc_tech:20200421035640p:plain

表示を日本語に切り替える

f:id:guitarrapc_tech:20200421035646p:plain

サインアウトして完了

f:id:guitarrapc_tech:20200421035652p:plain

f:id:guitarrapc_tech:20200421035700p:plain

システムロケールの日本語設定とUTF8化

f:id:guitarrapc_tech:20200421035709p:plain

Hyper-V Image の Checkpoint を作る

後でやり直せるようにしておく。

f:id:guitarrapc_tech:20200421035725p:plain

ホストマシンからVMのCPU 仮想化支援機能を有効にする

Hyper-V ホストマシンで Set-VMProcessor コマンドレットを使って、ExposeVirtualizationExtensions を $true にする。 これで、Hyper-V 上の対象VM で wsl2 + Docker Desktop for Windows が利用可能になる。

Set-VMProcessor -VMName "VMName" -ExposeVirtualizationExtensions $true

Windows 10 Home で WSL2 と Docker Desktop for Windows を動かす

開発環境では Docker で DB や各種バックエンドを動かすことが多いのですが、WFH が広がる中で自宅が Windows 10 Home で Docker Desktop for Windows 起動できないんだけど、何か手がないかと相談があったりなかったり。

以前試したときは問題なかったのですが、改めて最新の状況で試します。 また、wsl2 の仕組みから言って Hyper-V でホストしたWindows 10 Home でも動作するはずなのでそこも確認です。

Hyper-V に Windows 10 Homeをインストールしているのでその検証結果も知りたい人にも向けていいのではということで。

目次

更新履歴

  • 2020/5/17: Docker Stable に WSL2 対応がきたのを確認したのでEdge限定の記述を修正

TL;DR

  • Windows 10 Version 2004 (2020 May Update)がGA されるまでは Insider Preview (Slow Ring でok) が必要
  • Docker Desktop fo Windows は Stableでok Edge が必要(Stableはよ)
  • wsl2 上の Linux ディストロで Docker を動かすのはまた別に

WSL2について

Docker Desktop for Windows をインストールする

  • Insider Preview を有効にする (Slow Ring)
  • Windows Update で Windows 10 2004 にあげる
  • Windows Subsystem for Linux の有効化をしてWSL2を使う
  • Docker Desktop for Windows をインストール (Edge)

この時点で Windows バージョンは 1909

f:id:guitarrapc_tech:20200421032754p:plain
Insider Preview 前の Windows 10 バージョンは 1909

Insider Preview を Slowring で有効にする

f:id:guitarrapc_tech:20200421032816p:plain
Insider PreviewをSlow Ring で有効化

f:id:guitarrapc_tech:20200421032835p:plain
適当に Microsoft アカウントを作成

f:id:guitarrapc_tech:20200421032855p:plain
Insider Preview の有効化 (ここでずっと変わらない場合がある)

f:id:guitarrapc_tech:20200421032918p:plain
Slow Ring を選択

Windows Update で Windows 10 2004 にあげる

Docker Desktop を Windows Home に入れるには、19018 以上である必要があるのでWindows Update をかける。 2時間近く時間がかかるので注意。

f:id:guitarrapc_tech:20200421032936p:plain
Windows Update を実行して Windows 10 Version 2004 にアップデート

これで Windows 10 Version 2004 となる。

f:id:guitarrapc_tech:20200421033005p:plain
Windows 10 バージョン 2004 を確認

Windows Subsystem for Linux の有効化をしてWSL2を使う

Docker for Desktop で実施されるので実行する必要がありません。もしもDocker Desktop for Windows をインストール後に間違って無効にした場合にぐらいしか使わない。

Windows Subsystem for Linux と Vitrual Machine Platformを有効にするため、Windows PowerShell を管理者で起動して、コマンドを実行。

Get-WindowsOptionalFeature -Online | where FeatureName -match linux | Enable-WindowsOptionalFeature -Online -NoRestart
Get-WindowsOptionalFeature -Online | where FeatureName -match virtual | Enable-WindowsOptionalFeature -Online

f:id:guitarrapc_tech:20200421033027p:plain
Windows 10 の機能を有効化

再起動される。

f:id:guitarrapc_tech:20200421033130p:plain
再起動される

wslコマンドが利用可能になっている。ディストロはインストール不要。

$ wsl -l
Linux 用 Windows サブシステムには、ディストリビューションがインストールされていません。
ディストリビューションは Microsoft Store にアクセスしてインストールすることができます:
https://aka.ms/wslstore

Docker Desktop for Windows をインストール

https://hub.docker.com/editions/community/docker-ce-desktop-windows/

記事公開時点は Edgeが必須でしたが、Stable にきたので Stableでokです。

f:id:guitarrapc_tech:20200421033325p:plain
リンクからDocker アカウントなしでEdgeをダウンロード

f:id:guitarrapc_tech:20200421033348p:plain
Edge なら Windows 10 Home でも 19018+ でインストール可能

もしWindows Update をかけていないと、Docker Desktop のインストールがコケる。

Docker Desktop requires Windows 10 Pro/Enterprise (15063+) or Windows 10 Home (19018+)

Windows 10 2004 になっていれば、WSL2 の有効化を効かれるのでそのままOK。

f:id:guitarrapc_tech:20200421033427p:plain
Enable WSL 2 Features は必ず有効にする

f:id:guitarrapc_tech:20200421033444p:plain

インストール後は再起動する。

f:id:guitarrapc_tech:20200421033454p:plain

再起動後に wsl2 のインストールが完了していないことが言われる。

f:id:guitarrapc_tech:20200421033501p:plain

wsl2 の有効化

Linux カーネル更新プログラムパッケージを入れる。

https://aka.ms/wsl2kernel のダウンロードから。

f:id:guitarrapc_tech:20200421033507p:plain

これで wsl2 が有効になる。

PC を再起動して、docker-desktop が見えるようになる。

$ wsl -l

f:id:guitarrapc_tech:20200421033531p:plain
左が Windows 10 Home on Hyper-V on Windows Server 2019 on Azure VM、右が Windows 10 Home on Hyper-V on Windows 10 Pro

ついでに wsl2 をデフォルトバージョンにしておく。

$ wsl --set-default-version 2

Docker の実行確認

実行できるか確認する

docker run --rm hello-world

f:id:guitarrapc_tech:20200421033620p:plain
どちらの環境でも docker run が可能

ボリュームマウントも問題ない

f:id:guitarrapc_tech:20200421033641p:plain
ボリュームマウントも問題なし

Docker の設定

ドライブ共有が不要になった

従来の Docker Desktop for Windows では ホストマシンとコンテナのドライブ共有が必要だったが、これは不要になった模様。 実際、なにもしなくてもボリュームマウントされた。Firewall や アンチマルウェアとのバトルがなくなってめでたい。

f:id:guitarrapc_tech:20200421033727p:plain
ボリュームマウントメニューがなくなった

Hyper-V 上の Windows 10 Home はだめなの?

問題なく動作する。 Windows 10 Pro 上の Hyper-V で Windows 10 Home をいれても wsl2 + Docker 動かすこともできるし、Azure 上のWindows Server 2019 上で Nested Hyper-V の上に Windows 10 Home をいれても wsl2 + Docker を動かすことができる。

もし CPU 側の 仮想化支援を有効にしていない場合、Windows 10 Home on Hyper-V on Windows Pro も Windows 10 Home on Nested Hyper-V on Windows Server 2019 (Azure) も動作しない。

f:id:guitarrapc_tech:20200421033750p:plain
Hyper-V や 物理マシンの Windows 10 Home で Dockerの起動が失敗する例

これが出た場合は、Hyper-V ホストマシンで Set-VMProcessor コマンドレットを使って、ExposeVirtualizationExtensions を $true にし忘れている。

Set-VMProcessor -VMName "VMName" -ExposeVirtualizationExtensions $true

物理マシンなら UEFI で CPU のAdvance メニューあたりに設定があるはず。

CircleCI 上で dotnet test の verbosity がオーバーライドできない問題とその対処

CircleCI で dotnet test、妙な挙動をすることは前回一つ紹介しました。

tech.guitarrapc.com

が、まさかまた一つネタが見つかるとは思わなかったです。

今回は dotnet test がこけた時の出力について。

目次

TL;DR

  • dotnet test< /dev/null を付けると出力も正常になる

まさかのこれと同じ対策で解消します。

tech.guitarrapc.com

問題

問題は2つあります。

  • CircleCI 上で dotnet test をしたときに、verbosity が quiet として実行される。(dotnet test のデフォルトverbosity は minimal)
  • CircleCI 上で dotnet test のConsole Logger の verbosity をオーバーライド指定して実行しても quiet から変わらない。

dotnet test のおさらい

問題を把握するためには、dotnet test の基本的な挙動を把握しておく必要があります。 ざっくり説明します。

ローカルで適当に xUnit のプロジェクトを作って、適当に失敗する Factを作りdotnet test で実行すると、テストの失敗個所が表示されます。 このテストの失敗個所が表示されるのは、verbosity レベルが minimal 以上の時で、dotnet test はデフォルトで minimal に設定されています。

$ dotnet test

Test run for C:\git\guitarrapc\dotnet-test-lab\tests\XUnitTestProject1\bin\Debug\netcoreapp3.1\XUnitTestProject1.dll(.NETCoreApp,Version=v3.1)
Microsoft (R) Test Execution Command Line Tool Version 16.3.0
Copyright (c) Microsoft Corporation.  All rights reserved.

Starting test execution, please wait...

A total of 1 test files matched the specified pattern.
[xUnit.net 00:00:01.61]     XUnitTestProject1.UnitTest1.BoolFailTest [FAIL]
  X XUnitTestProject1.UnitTest1.BoolFailTest [7ms]
  Error Message:
   Assert.False() Failure
Expected: False
Actual:   True
  Stack Trace:
     at XUnitTestProject1.UnitTest1.BoolFailTest() in C:\git\guitarrapc\dotnet-test-lab\tests\XUnitTestProject1\UnitTest1.cs:line 33

Test Run Failed.
Total tests: 4
     Passed: 3
     Failed: 1
 Total time: 2.9317 Seconds

この結果の解像度をオーバーライドすることは、dotnet test 自体、あるいは Test Loggerごとに設定可能です。

  • dotnet test 自体のverbosity をオーバーライドするなら--verbosity 引数を使って dotnet test --verbosity [quiet|minimal|normal|detailed] を指定します。
  • dotnet test の特定のテストロガーをオーバーライドするなら --logger:ロガー名;verbosity=[quiet|minimal|normal|detailed] を指定します。

dotnet test 自体のverbosity を上げると、ビルドログなど見たくないログも増えてしまうのであまり好まれないと思います。 そのため通常は、Consoleロガーのverbosity だけ挙げて対処するでしょう。

たとえばConsoleLoggerのverbosity を normal にするならこのように指定します。 normal にしてお手元で実行すると、今まで dotnet test で出力されていなかった一つ一つのテストが成功、失敗にかかわらず出力されるはずです。

dotnet test "--logger:Console;verbosity=normal"

このことから、押さえておくべきは2つです。

  • dotnet test をverbosity 指定なしに実行したときは minimal を期待している
  • もし verbosity がちがった場合でも、--logger:console;verbosity=minimal などでオバーライドできることを期待している

何が問題なのか

CircleCI 上でローカルと同じように dotnet test を実行すると、失敗したテストの詳細が表示されないことに気がつきます。 これは verbosity が quiet の時の表示と合致し、ローカルでも dotnet test --verbosity quiet とすると再現します。 つまり、指定していないのになぜか verbosity がminimal ではなく quiet になっています。

$ dotnet test -c Debug

Test run for /root/project/tests/XUnitTestProject1/bin/Debug/netcoreapp3.1/XUnitTestProject1.dll(.NETCoreApp,Version=v3.1)
VSTest: Starting vstest.console...
VSTest: Arguments: dotnet exec /usr/share/dotnet/sdk/3.1.100/vstest.console.dll --testAdapterPath:/root/.nuget/packages/coverlet.collector/1.2.0/build/netstandard1.0/ --framework:.NETCoreApp,Version=v3.1 --logger:Console;verbosity=minimal --Diag:/root/project/bin/default/log.txt /root/project/tests/XUnitTestProject1/bin/Debug/netcoreapp3.1/XUnitTestProject1.dll
Microsoft (R) Test Execution Command Line Tool Version 16.3.0
Copyright (c) Microsoft Corporation.  All rights reserved.

Starting test execution, please wait...

A total of 1 test files matched the specified pattern.
[xUnit.net 00:00:00.49]     XUnitTestProject1.UnitTest1.BoolFailTest [FAIL]
Test run in progress.VSTest: Exit code: 1

Exited with code exit status 1

また、このverbosityをオーバーライドしようとしても minimal から変更されず quiet 時の出力のままです。

#!/bin/bash -eo pipefail
dotnet test -c Debug "--logger:Console;verbosity=minimal"

Test run for /root/project/tests/XUnitTestProject1/bin/Debug/netcoreapp3.1/XUnitTestProject1.dll(.NETCoreApp,Version=v3.1)
VSTest: Starting vstest.console...
VSTest: Arguments: dotnet exec /usr/share/dotnet/sdk/3.1.100/vstest.console.dll --testAdapterPath:/root/.nuget/packages/coverlet.collector/1.2.0/build/netstandard1.0/ --framework:.NETCoreApp,Version=v3.1 --logger:Console;verbosity=minimal --Diag:/root/project/bin/default/log.txt /root/project/tests/XUnitTestProject1/bin/Debug/netcoreapp3.1/XUnitTestProject1.dll
Microsoft (R) Test Execution Command Line Tool Version 16.3.0
Copyright (c) Microsoft Corporation.  All rights reserved.

Starting test execution, please wait...

A total of 1 test files matched the specified pattern.
[xUnit.net 00:00:00.49]     XUnitTestProject1.UnitTest1.BoolFailTest [FAIL]
Test run in progress.VSTest: Exit code: 1

Exited with code exit status 1

これはもちろん、dotnet test -c Debug --verbosity=minimal でも変わらず quiet のままです。

再現リポジトリ

再現リポジトリを組んであります。

github.com

ワークアラウンド

dotnet test< /dev/null を付けると、dotnet test のデフォルトverbosityが minimal に正常化されます。

また、verbosity のオーバーライドも正常に機能するようになります。

進捗

microsoft/vstest に報告してあるので、根本対処されるかはIssueの結果次第です。

github.com

まとめ

知らないとこれはまるのでは。

英語含めて記事が全然ないところみると、.NET で CircleCI やはり利用少ない気配があります。 知ってたけど。

前なら Azure Pipeline、今なら GitHub Actions を使う人が多くなっていくんだろうなぁと感じます。 これは Microsoft のdocs などでの露出に誘導されてるのも多分にあるんですかね? と思ったり思わなかったり。

余談: 調査する

当初、CircleCI ではなく ubuntu か何か環境問題か? と思ったのですが、ローカルで dotnet test したり、Docker 上でCircleCI と同じ dotnet-sdk:3.1 イメージを使ってテストを実行しても再現せず正常に指定した verbosity でオーバーライドができます。 この時点で環境問題の可能性は消えます。Dockerさいこー。

コードを確認すると、--Logger:"Console;verbosity=minimal" を渡せば解釈されることがわかります。

            var verbosityExists = parameters.TryGetValue(ConsoleLogger.VerbosityParam, out string verbosity);
            if (verbosityExists && Enum.TryParse(verbosity, true, out Verbosity verbosityLevel))
            {
                this.verbosityLevel = verbosityLevel;
            }

vstest/ConsoleLogger.cs at 5ef1f3b60404588b91fabb4fab22304ac88b108a · microsoft/vstest

他に何か原因があるのかといろいろ調べたけど、ことごとく該当せず....

そういえば自分のプロジェクトで、この問題が出ず正常にできているものがあるのでなぜかを見てみると、まさかの < /dev/null をしていて前のIssueと同じ対処で解決できています。

tech.guitarrapc.com

ということで、原因はよくわからないけど < /dev/null で出力まで改善されるという謎結果に....

なお、Diagnostics Logを --diag:diag.txt で出してみてみると、<dev/null を指定しなくてもちゃんと verbosity が設定した値になっているので、 < /dev/null で治るのは make sense といえばそうですが、謎い。

しかし、なんで /dev/null で出力というか挙動が変わるんだ.... stdin なんて何か色ない限りhandleしないと思うんだけど....?

pulumi でstateが事故ったときに過去のバージョンのstateを当てなおす

terraform もそうですが、Infrastructure as Code とかやってるとstate が壊れる日が来て軽く絶望します。

Pulumi で、誤った操作から state からリソースが200あまり消えたときにどのように復旧したのかをメモしておきます。

目次

TL;DR

  • アンドキュメントな REST API api.pulumi.com を使うことで指定したバージョンのstate がダウンロードできる
  • pulumi state import は神
  • pulumi refresh はpreview と違って認証さえ通れば実行されてしまうので注意
  • なお、terraform では同じことは起こらない.... Pulumi 独特の仕様に起因する...

状況

  • pulumi を CircleCI で実行中に、AWSアカウント情報の入れ替え中にビルドが実行される。
  • pulumi refresh が実行されたため、State がなにもリソースがないAWSアカウントと同期されて過半数が消える。
  • pulumi preview で大量の差分が出てup実行前に停止

この状況は、本来管理している AWS アカウント (Aとします) と同期していた Pulumi コードが、そのコードで管理していないAWSアカウント (Bとします) とrefresh で状態を同期しようとして、Bには何も管理しているリソースがないためにstate ファイルからリソース情報が消えました。

refresh であるため、実際のAWSアカウントにはA/Bともに影響が出ないのですが、state ファイルがコードと大幅に変わってしまっています。

復旧目標

pulumi refresh 前のState 状態に戻す。

前提情報

pulumi の state について知らないと何がどういうことかわからないまま取り組んでしまうのでおさらいです。

State とは

Pulumi のstate は、コードなどで表現されたあるべき状態がJSONとしてシリアライズされた状態です。 terraform でいうところの tfstate に相当します。

state ファイルは Pulumi の各種言語ホスト (Language Host) から生成され、クラウドやk8s プロバイダーに対してその状態になっているかチェック、適用を行います。

f:id:guitarrapc_tech:20200115181646p:plain
Pulumi state

www.pulumi.com

ポイントとなるのは、「コードと state 」及び「state とプロバイダー」がどのように一致をみているかです。 図の通り、プロバイダーはawsやk8s などの適用対象となります。

コードとstateのマッチング

urn によって一致をみています。 その言語上でどのような書き方をしようと、コードからキーとなるurnを state に拾いに行ってマッチングします。

  • コードのurn がstate にあれば、コードとstateで求める状態に変更がないかチェック (up-to-date / change)
  • コードのurn がstate になければ、新規で追加 (create)
  • コードのurn が消えて、state にだけあれば削除 (delete)

state とプロバイダーのマッチング

state に含まれる、そのリソースがプロバイダーで一意に特定されるid を見て合致を見ます。 ただし、state とプロバイダーの同期を refresh を使って明示的に行った時だけです。

state には必ず、対象のプロバイダーでリソースを一意に絞れるキーが含まれれます。 AWS であれば、多くの場合は arn であったり id や name がそれに相当します。

さて、Pulumi が terraform と違ってやりにくいのが、Pulumi は コードと state を見るというフローであることです。 terraform は、state と プロバイダーが自動的に同期をしていましたが、Pulumi では明示的に同期 (refresh) 操作を行わない限り state とプロバイダーの一致は取りません。

この辺りは以前書いた通りで、同じような動作が欲しければ pulumi refreshpulumi preview --refrepshpulumi up --refresh を行うことになります。

tech.guitarrapc.com

State が変化するタイミング

「state と コード」、「stateとプロバイダー」それぞれについてどのようにマッチングしているのかは確認できました。 では、どのような操作をすると State が変化するのでしょうか?

terraform なら tfstate が変化するのは apply や destroy、refresh のタイミングです。

Pulumi は、state とコード、stateとプロバイダーのそれぞれのタイミングで変化します。

  • state とコード: pulumi uppulumi destroy でstateに変化が適用される
  • state とプロバイダー: pulumi refreshpulumi xxxx --refresh でstateに変化が適用される

State を誤って更新しかねないタイミング

CI/CD をしていると、refresh や up は動作する環境で実行されるはずなので通常は事故が起こりにくいといえます。 多くの場合は、クラウド上のリソース同期を維持すため refresh を挟んで操作するのではないでしょうか?

  • pulumi refresh
  • pulumi preview
  • approve
  • pulumi refresh
  • pulumi up

このフローで事故が起こるとすると、例えばプロバイダーの認証が切り替わって別のプロバイダーときにCIが走って refresh が走ると、stateが吹き飛びます。 preview の時点で差分が莫大な数が出るので気づくでしょうが、refresh しているので state は後の祭りです。

例えばAWS アカウント A から アカウントB に認証が変わってしまったなど、発生する状況はいくつもあるでしょう。

あまりやることはないでしょうが、ローカルで実行しているときとはこういったアカウントの事故はよりカジュアルに起こりかねません。

過去バージョンの State を何とかして得る

refresh 前のstateにすればいいので、Pulumi Web でバージョンがとられているログから戻すべきバージョンはわかっています。

ドキュメントを見ると「現在のバージョンのState に関する記述」はあるものの、「過去バージョンのState に関して記述」がありません。 ではどうやってとるのか調べましょう。

NO: Pulumi Stack の State からは過去バージョンが export できない

pulumi は、pulumi stack export をするとstate が丸ごとエキスポートできます。 ということは、以前書いた通り stack 経由で state の Export > Import を行えば state は戻るはずです。

tech.guitarrapc.com

やった!セーフ! と思いきや、こういった事故の時に pulumi stack export が使えないことにすぐに気づきます。 過去バージョンを Export できないのです。

pulumi stack export なんて使えない子。

NO: Pulumi Web の Activityの過去実行履歴からState はダウンロードできない

Pulumi Web には Activity 画面があり、過去の実行ログが見えます。 Terraform Cloud の感覚だと、こういったところから state がダウンロードできそうですが、残念ながら Pulumi の実行ログ画面からは state をダウンロードできません。

f:id:guitarrapc_tech:20200115184310p:plain
CHANGESのログ右上のメニューはログの表示のみ

f:id:guitarrapc_tech:20200115184333p:plain
TIMELINEはログのみ

f:id:guitarrapc_tech:20200115184424p:plain
CONFIGURATIONは実行時のコンフィグを表示するだけ

まさかの過去バージョンの実行ログがあっても Stateが取れない

NOT YET: pulumi stack に 過去バージョンの stack export機能が追加されるかも

こういうリクエストは当然すぐ出てくるもので、現在 Issue にあります。

pulumi stack export で過去バージョンを指定できるようにできないかというものです。 最新バージョンの Stack だけ Export できても、いざというときには何の役にも立たないですからね。

github.com

CLIでオプションでもいいけど、Pulumi Web からダウンロードしたくないですか? Terraform Cloud のように。

OK: REST API を使って過去バージョンのstate をダウンロードする

Stack Exchange や Issue でも質問がなく、困ってしまったので Stack Exchange に投げたところ中の人から回答がありました。

stackoverflow.com

Pulumi には アンドキュメントながら REST API があります。 これを使うことで過去バージョンの Stack が exportできます。

現在ドキュメントにしようIssue が立っています。

github.com

以下のようなフォーマットでエキスポートできます。

curl -H "Authorization: token $PULUMI_TOKEN" https://api.pulumi.com/api/stacks/<org>/<project>/<stack>/export/<version>

PULUMI_TOKEN はいいとして、URL のパスは https://app.pulumi.com/ で ACTIVITY から該当処理の履歴を見ているときのURL からわかります。

例えば、Pulumi Web でその実行履歴が[https://app.pulumi.com/hoge/aws-sandbox/master/updates/352]というURL なら、API は [https://api.pulumi.com/api/stacks/hoge/aws-sandbox/master/export/352] となります。

Export したstate から復旧する

過去バージョンのstate が REST API からダウンロードできたら、話は簡単です。

  • pulumi stack import < downloadしたstate.json でインポート
  • pulumi refresh で state と プロバイダーの同期
  • pulumi preview でコードとstateに差分が出ないことを確認

お疲れさまでした。

まとめ

同じようなstate事故は、terraform だとあっという間に解決だけど、Pulumi 厳しい....です。 過去バージョンのstate の取り扱いは楽になる動きはあるので期待。

そもそも pulumi の state と プロバイダーの同期が一段介していて、terraform に比べても事故りやすい..... ので、いい加減 refresh の仕組み、もうちょっとなんとかならないですか (terraform だと発生しないだけどなぁ)

おまけ

State ファイルを解析するのに使っていた State ファイルの構造です。

gist.github.com