bicep 用 dev contaner

最近、bicep を触ることが多く。また、開発環境の再現性やポータビリティが気になることがママある。VSCode dev container[1]がなかなか便利だったので、bicep 用 dev contanerを作成した。[2]

Distecca

作成に当たっては、下記の2つのレポジトリを参考にした。

概要

大まかな流れととしては、下記の3段階となる。docker 絡みは、普通にDockerfileを書くのとほぼ同じだ。

  1. bicep dev containers 用のベースとなるDocker Image の作成
  2. dev containers の プレースホルダー用 Dockerfile の作成
  3. devcontainer.json の 作成

1. ベースとなるDocker Image の作成

大枠は、テンプレートvscode-dev-containers/container-templates/があるので、持ってくる。

mkdir vscode-dev-containers-bicep
cd vscode-dev-containers-bicep
cp -r ../vscode-dev-containers/container-templates/dockerfile/. .

今回は、ベースイメージを作成するDockerfileをbase.Dockerfileとして追加した。

script-libraryによく使われるパッケージのインストール用のスクリプトが用意されているので必要なのを持ってくる。ドキュメントは、この辺にある。ここでは、az clipowershellを入れる。手間をかけたくなければ、現状ではdebian/busterを baseのOSとして選ぶのが良い。

echo -n "common-debian.sh powershell-debian.sh azcli-debian.sh" | \
    xargs -t -d ' ' -I {} cp ../vscode-dev-containers/script-library/{} .devcontainer/library-scripts/

bicep 用のインストールスクリプトを作る。bicep-debian.sh。やってることは、bicep本体のインストールと、vscode plugin のダウンロード。あと普段使いのツールもおまけで入れてる。bicep の vscode plugin は、まだmarket place に出てないので、ローカルにダウンロードして、devcontainer.jsonでフルパス指定で入れている。

作成した、bicep-debian.sh は、こんな感じで、主要部分は割りとシンプル。

... snip ...

# Install the Bicep CLI
# https://github.com/Azure/bicep/blob/master/docs/installing.md#install-the-bicep-cli

curl -Lo bicep https://github.com/Azure/bicep/releases/latest/download/bicep-linux-x64
chmod +x ./bicep
mv ./bicep /usr/local/bin/bicep
bicep --help

# Fetch the latest Bicep VSCode extension
curl -Lo vscode-bicep.vsix https://github.com/Azure/bicep/releases/latest/download/vscode-bicep.vsix

持ってきた、スクリプトと、bicep のインストール[3]base.Dockerfileに追加する。

作成したDockerfileを抜粋

... snip ...
RUN bash /tmp/library-scripts/common-debian.sh "${INSTALL_ZSH}" "${USERNAME}" "${USER_UID}" "${USER_GID}" "${UPGRADE_PACKAGES}" \
    # Install the Azure CLI
    && bash /tmp/library-scripts/azcli-debian.sh \
    # Install the powershell
    && bash /tmp/library-scripts/powershell-debian.sh \
    # Install the bicep
    && bash /tmp/library-scripts/bicep-debian.sh \
    # Clean up
    && apt-get clean -y && rm -rf /var/lib/apt/lists/* /tmp/library-scripts

Makefileを作ってあるので、下記のように実行するとbuildしてpushされる。[4]

make build push

2. Dockerfile を作る

Dockerfileで、先程作った、base.Dockerfile のイメージを利用する。このDockerfileは、プレースホルダーで、非常に簡単な感じになった。専用のイメージを作るべきか、どこまで入れるかはケースバイケースではある。

# See here for image contents: base.Dockerfile
ARG VARIANT="latest"
FROM takekazuomi/devcontainers-bicep:${VARIANT}

3. devcontainer.json を作成

ちょっと長いが、devcontainer.jsonを下記に全文引用。ポイントは、”extensions” で、docker image にダウンロードされてる、vscode-bicep.vsix をフルパス指定していること。フルパスで書くと、ローカルからロードしてくれる[#r5]_

{
    "name": "Bicep",
    "build": {
        "dockerfile": "Dockerfile",
        "args": {
            "VARIANT": "latest"
        }
    },
    "runArgs": [
        "--cap-add=SYS_PTRACE",
        "--security-opt",
        "seccomp=unconfined"
    ],
    // Set *default* container specific settings.json values on container create.
    "settings": {
        "terminal.integrated.shell.linux": "/bin/zsh"
    },
    // Add the IDs of extensions you want installed when the container is created.
    "extensions": [
        "ms-azuretools.vscode-docker",
        "ms-vscode.azure-account",
        "davidanson.vscode-markdownlint",
        "ms-vscode.powershell",
        "dandric.vscode-jq",
        "redhat.vscode-yaml",
        "dotjoshjohnson.xml",
        "timonwong.shellcheck",
        "editorconfig.editorconfig",
        "/tmp/vscode-bicep.vsix"
    ],
    // Uncomment to connect as a non-root user. See https://aka.ms/vscode-remote/containers/non-root.
    "remoteUser": "vscode"
}

最後に

dev container は割と簡単に作れるしカスタマイズも容易で利便性が高い。開発環境は、少し使っていると魔窟化してしまい再現性やポータビリティが無くなり、チーム開発などで環境を揃えたい場合に問題になりやすい。

その点Windows も問題だが、Linuxだとディストーションの違いや、パッケージのレポジトリを追加したり、細かいカスタマイズが効いたりなどの自由度の高さに起因して、更に問題が深刻化してしまうことがある。

dev container を使うとクリアな開発環境を維持しやすくなり問題のかなりの部分が解決できるのではないかと期待している。贅沢を言うと、VS Code だけでなく、他のエディターでも使ると嬉しいところではある。vimとかemacsとか。

参照

[1]Developing inside a Container
[2]このBlogは、tinkererで書いてるのだが、 Windows + python だとなにかと辛く。python 環境をvenvから docker に移し、さらに dev container を使っうようにしたら。複数のライティング環境を維持しやすくなり苦しみが無くなった、素晴らしい。
[3]最近、なんだかんだ言って task runner とての make は優秀だと思い直している。その昔、make の複雑さが嫌になって、ant にしたのだが、20年経って戻ってきたことになる
[4]Build and image generation for vscode-dev-containers
[5]vscode plugin をローカルからロードのにフルパスで書く方法がどこかに書いてあったと思うのだが原典が見つからない。見つかったら更新する

bicep moduleを使う

最近、bicepが気に入って使ってる。まだ、かなり初期段階の開発中のプロダクトだが、ARM Template のファイルを生成するので、記述ツールという位置付なら今でも十分便利に使える。

先日、vscode-remote-try-bicepがいつまでも空のままなのでvs code dev containerのテンプレートとしてdevcontainer-bicepを作って公開した。個人の嗜好の部分をどこまで入れるのかは微妙な感じではある。

Stone

話がズレた、10/16に、masterに、0.2でリリースされる予定のbicep module[1]がマージされたので動かしてみた。まだリリースされてないのでソースを持ってきてビルドして、path の切れてるディレクトリにリンクを張リ、実行属性を付ける。最後に、versionを確認。2020/10/17 時点では、0.1.149-alpha (1ca02b52de)だった。ビルドは、こんな感じのコマンドになる。

% git clone git@github.com:Azure/bicep.git
% cd bicep/src/Bicep.Cli
% dotnet publish -r linux-x64 -c Release --self-contained true /p:PublishTrimmed=true -p:PublishReadyToRun=true -p:PublishSingleFile=true -p:PublishReadyToRunShowWarnings=true
% ln -s $PWD/bin/Release/netcoreapp3.1/linux-x64/publish/bicep ~/.local/bin/bicep
% chmod +x ~/.local/bin/bicep
% bicep --version
Bicep CLI version 0.1.149-alpha (1ca02b52de)

ビルド後のbicepサイズを見たら、74MBほどある。今のリリースが45MBなのでちょっとでかい。

.github/workflows/build.ymlを見たら、PublishReadyToRunが指定されていなかったので外してみる。

run: dotnet publish --configuration ${{ matrix.configuration }} --self-contained true -p:PublishTrimmed=true -p:PublishSingleFile=true -r ${{ matrix.rid }} ./src/Bicep.Cli/Bicep.Cli.csproj

オプションを合わせてコンパイルし直す。

% dotnet publish -r linux-x64 -c Release --self-contained true /p:PublishTrimmed=true -p:PublishSingleFile=true -p:PublishReadyToRunShowWarnings=true
% ls -lh ./bin/Release/netcoreapp3.1/linux-x64/publish/bicep -rwxr-xr-x 1 takekazu takekazu 54M 10 17 17:41 ./bin/Release/netcoreapp3.1/linux-x64/publish/bicep

54MBになった。AOTは無くても良さそうなので外すことにする。また、VS Code Pluginはとりあえず、無くて良いので後で試す。

Modulesを試す

ドキュメントでは、このあたりModulesに書いてある。

まずは使ってみる、モジュール利用のシナリオは色々あると思うが、今回は仮想マシンのデプロイをやって、ポイントを整理する。

仮想マシン(VM)をデプロイする場合、ポータルやAzure Quickstart Templateを見ると、ほとんどの場合は仮想ネットワーク(VNet)とVMは固定の構成で作成されることが前提となっている。一方、実業務では既存のVNetに追加でVMをデプロイしたり、異なったVNetに同一構成のVMをデプロイすることが頻繁にある。実際、仮想マシンと仮想ネットワークのライフサイクルは別と考えた方が良く、このようなケースは、テンプレートのモジュール利用の典型的な例になる。

bicep で、vmとvnetをmoduleに分けてデプロイするレートを書いた例をレポジトリにあげたbisep module examples

簡単にポイントをだけ説明する。

下記のようにメインのテンプレートを作成し、module を利用する。従来のARM Templateでは、テンプレート参照する場合は、URL経由である必要があったが、bicep moduleではローカルファイルにモジュールを置くことができる。また、vnet: vnetMod.outputs.results.vnetのように、モジュールの入力として他のモジュールの出力を使うことで、モジュール間の依存関係をもたせることができる。

param vmSize string
param adminUsername string
param adminPassword string {
    secure: true
}

module vnetMod './vnet.bicep' = {
    name: 'vnetMod'
    params: {
        vnetName: 'vNet'
    }
}

module vmMod './vm.bicep' = {
    name: 'vmMod'
    params: {
        vnet: vnetMod.outputs.results.vnet
        adminUsername: adminUsername
        adminPassword: adminPassword
        vmSize: vmSize
        vmName: 'vm1'
    }
}

output results object = {
    vnet: vnetMod
    vm: vmMod
}

module vnetMod ‘./vnet.bicep’ = {と書くと、vnet.bicepが読み込まれる。下記は内容の抜粋。

param vnetName string = 'vNet'
param addressPrefix string = '10.1.0.0/16'
param location string = resourceGroup().location

output results object = {
    vnet: vnet
}

... snip ....

// https://docs.microsoft.com/en-us/azure/templates/microsoft.network/virtualnetworks
resource vnet 'Microsoft.Network/virtualNetworks@2020-05-01' = {
    name: vnetName
    location: location
    properties: {
        addressSpace: {
            addressPrefixes: [
                addressPrefix
            ]
        }
        subnets: [
            subnet1
            subnet2
        ]
    }
}

モジュールは、Microsoft.Resources/deploymentsのインラインのテンプレートに展開される。該当部分、main.json#L20-L104。 以下抜粋

"type": "Microsoft.Resources/deployments",
     "apiVersion": "2019-10-01",
     "name": "vnetMod",

   ... snip ....

       "template": {

   ... snip ....

         "resources": [
           {
             "type": "Microsoft.Network/virtualNetworks",
             "apiVersion": "2020-05-01",
             "name": "[parameters('vnetName')]",
             "location": "[parameters('location')]",
             "properties": {
               "addressSpace": {
                 "addressPrefixes": [
                   "[parameters('addressPrefix')]"
                 ]
               },
               "subnets": [
                 "[variables('subnet1')]",
                 "[variables('subnet2')]"
               ]
             }
           }
         ],

  ... snip ....

bicep全般の動きとして、ネイティブのARM Tempateとの関係は、ほぼ1:1で非常にシンプルな展開で、moduleでも同様な動きになっている。

ポイント1:依存関係

vmは、vnetに依存している、これは、普通ARM Template では、ネストしたリソースで書くか、dependsOnで記述する。[2]bicepには、自動依存性解決[3]の機能があり、bicep resources 定義内で、別のリソースを参照すると、json生成時に、dependsOnを追加してくれる。

今回のケースだと、vmModvnetパラメータで、vnetMod.outputs.results.vnetを参照している。生成されたmain.jsonでは、下記のmain.json#L329-L331部分に依存関係は記述されているのがわかる。

"dependsOn": [
    "[resourceId('Microsoft.Resources/deployments', 'vnetMod')]"
]

単純に vnetModeのdeploymentsへに依存するように展開されてるので、大規模な展開で展開時間がセンシティブな場合は少々注意が必要だが殆どの場合は問題無い素直な変換になっている。

この依存関係の自動解決(暗黙の依存関係)は、コードをシンプルにするし、dependsOnの書き漏れが軽減されるので非常に気に入っている。

ポイント2:オブジェクトの利用

従来からARM Templateではパラメータにオブジェクトを渡せたが、あまり便利では無かったので殆ど使っていなかった。このスクリプトでは、vnetもモジュールでは、vnet reference を返し。vm モジュールのパラメータに渡している。

vnet.bicep#L5-L7

output results object = {
    vnet: vnet
}

param vnet objectで受け取って、vm.bicep#L25-L26で使っている。ここでは、vnet name 使っていないが、vnetそのものが渡ってくるので他の操作もできる。あと、呼び出し側のmain.bicepで、参照関係が生まれるので、自動依存関係で処理できるという利点もある。[4]

今のbicepだと、仕様のブレと未実装が混ざっていて少し混乱するが、そんな時はIssueを見れば良い。[5]

まとめ

bicepは、単純にARM Templateを各ツールとして捉えると今まであったどのツールより書きやすい。今回、module が実装されたことでかなり実用的に使えるようになった。この手のものを書くにはGUIツールより、テキストベースのツールが向いているように思う。ただ、ARM Template (= Azure Resource API) の難しさは残るのでハマる時はハマる。

bicepのリリースは来年になりそうだが、筋が良いので積極的に使って方が良いと思う、お勧めだ。ただ、copy が実装されいないので要注意、copy は、12月リリース予定の0.3でサポートされる予定だ。

あと、今回実行は基本WSL2内で行い、azコマンドのランナーとしては、昔ながらのMakefileを使っている。Azure PowerShell Cmdletを使うなら、全部Poshスクリプトで書けば良いが、az cliを使う場合、コマンド間を繋ぐ何らかのglue script[6]が必要だ。bashで全部書くのも辛いので、最近は、Makefileを使うことにしている。GNU Make なんて、20年ぐらいまともに使ってなかったのでだいぶ忘れていたがだいぶ思い出してきてる。

2020/10/22 「GitHub Action の Workflow から持ってくると最新のビルドが拾えるよ」、って話が追記された。[7]

参照

[1]Module implementation (#608)
[2]Define the order for deploying resources in ARM templates
[3]Implicit dependency
[4]このあたりコードでちょと変なことをしているのは、Microsoft.Network virtualNetworks template referenceによると、name属性があるはずなのだが、参照すると実行エラーになってしま。仕方が無いので、id からvnet nameを求めていてる
[5]自動依存関係解決だけあれば、module にdependsOn は不要じゃないかという議論どうやら、modeule に dependsOn 構文を追加するつもりらしい。これは、未確認
[6]Glue languages、ぐぐったら、AWS Glue ばかりが出てきた心配になってのでリンクを張っておく。日本語のWikipediaはイマイチだったので、en.wikipedia.orgにした
[7]Installing the “Nightly” build of bicep CLI and VS Code extension

Azure Docker Integration

先日(7/30)と言っても、既に2ヶ月前。第28回 Tokyo Jazug Nightの資料をやっとSlideshareに上げた。Introduction of Azure Docker Integration

だいぶ空いてしまったので、更新情報を追記。先日、2020/9/24 に、docker compose をクラウド上で動かすための、docker cli 拡張SDKが公開された。アーキテクチャを見ると、ECS/ACI バックエンドとなっているのが、それぞれのコンテキストを提供している部分で、ECS, ACI 統合がどのようにされているのかがわかる。

Docker Open Sources Compose for Amazon ECS and Microsoft ACI

Stone

現時点で、この図のNode SDKCompose CLIの部分がオープンソース化されている。このアーキテクチャは最終的なものではなく、「後でCompose CLIを既存のCLIとマージする予定」ということだ。

どうやら、Compose CLIに、gRPCのAPIを追加して、複数の言語から、Docker CLIを使えるようにするつもりらしい。今は、Node SDKしかなく、APIはCompose CLIに付いてるが、これは暫定。将来的には、人気のある言語用にSDKを用意し、Compose CLIは、Docker CLIに統合、また、コンテキストのバックエンドは、ECS、ACIだけでは無く他のものもサポートする。ということらしい。なかなか興味深い

閑話休題

このBlog、2年ほど空いてしまったが、今回復活にあたり、static site generatorとして使ってるtinkererを docker 化しWSL2でビルドすることにした。tinkerer の Dockerfiletakekazuomi/tinkerer。最近は、Windows Python環境が安定しないのと、Python 環境自体が壊れやすいので、venv を使うより、いっそのことWSL2+Docker の方が良いかなと思っている。

参考までに、Blogの方のDockerfile

FROM takekazuomi/tinkerer:1.7.2

WORKDIR /tmp

ENV DEBIAN_FRONTEND noninteractive

USER root

COPY requirements.txt ./

RUN apt-get -y update \
&& apt-get -y clean \
&& rm -rf /var/lib/apt/lists/* \
&& pip3 install pip -U \
&& pip install -r requirements.txt \
&& python -m pip install https://github.com/takekazuomi/sphinx-gist-embed/archive/master.tar.gz

USER tinker

WORKDIR /blog

CMD ["tinker", "-h"]

Bookmarks

複数Subscriptionへのデプロイ

この記事はMicrosoft Azure Advent Calendar 2017の2日目の記事です。

ちょっと便利な、ARM template の小技を紹介します。

以前から1つのテンプレートを使って、複数のリソースグループへデプロイすることができましたが[1]。さらに、Microsoft.Resources/deploymentsAPI Version: 2017-05-10から、別のサブスクリプションへもデプロイできるようになりました。[2]

リソースを異なるサブスクリプションにデプロイするには、別のリソースグループへデプロイする時と同じように、nested template(Microsoft.Resources/deployments) を使います。 deployments リソースにsubscriptionId プロパティが追加され、そこに指定したサブスクリプションにnested templateで定義したリソースが展開されるってわけです、簡単ですね。

IMG_3105

ちょっとやってみよう、下のテンプレートの抜粋を見て下さい。[3]テンプレートは、別のリソースグループへデプロイする時とほぼ同じで、8行目のsubscriptionId のところが違うぐらいなのがわかります。

これで上手くデプロイできるか確認しましょう。 local subscription (083c462a-7c24-4a16-8bfe-876cb0ab434b) を選択した状態でデプロイします。そうするとストレージアカウント(東日本)が作成され、同時に remote subscription (ff05d8ad-12ee-4c68-97bb-78baefa1c01a) にもストレージアカウント(西日本)も作成されます。

実行には事前に、リソースグループが必要です。サブスクリプションを切替ながら、リソースグループを両側に用意します。

$ Select-AzureRmSubscription -SubscriptionId 083c462a-7c24-4a16-8bfe-876cb0ab434b
$ New-AzureRmResourceGroup -Name LocalRG -Location japaneast

$ Select-AzureRmSubscription -SubscriptionId ff05d8ad-12ee-4c68-97bb-78baefa1c01a
$ New-AzureRmResourceGroup -Name RemoteRG -Location japanwest

これでリソースグループが出来たので、local subscription を選択し直して、前記のテンプレートをデプロイします。

$ Select-AzureRmSubscription -SubscriptionId 083c462a-7c24-4a16-8bfe-876cb0ab434b
$ New-AzureRmResourceGroupDeployment -TemplateFile .\deploystorage.json `
       -StorageAccountName kyrtlocal01 `
       -ResourceGroupName LocalRG `
       -RemoteResourceGroup RemoteRG `
       -RemoteSubscriptionId ff05d8ad-12ee-4c68-97bb-78baefa1c01a `
       -RemoteStorageAccountName kyrtremote01

思った通りに出来ているか確認しましょう。

$ Select-AzureRmSubscription -SubscriptionId 083c462a-7c24-4a16-8bfe-876cb0ab434b
$ Get-AzureRmStorageAccount -ResourceGroupName RemoteRG | fl ResourceGroupName,Id, Location

ResourceGroupName : localrg
Id                : /subscriptions/08.../resourceGroups/localrg/providers/Microsoft.Storage/storageAccounts/kyrtlocal01
Location          : japaneast


$ Select-AzureRmSubscription -SubscriptionId ff05d8ad-12ee-4c68-97bb-78baefa1c01a
$ Get-AzureRmStorageAccount -ResourceGroupName RemoteRG | fl ResourceGroupName,Id, Location

ResourceGroupName : remoterg
Id                : /subscriptions/ff.../resourceGroups/remoterg/providers/Microsoft.Storage/storageAccounts/kyrtremote01
Location          : japanwest

ストレージアカウントが2つ作成され、それぞれ別のサブスクリプションに東日本と西日本に配置されているのが確認できました。複数サブスクリプションへの配置が必要なケースはあまり無い気もしますが、なかなか面白いですね。

注意

テンプレートの、38行目で、location を西日本を直接指定しています。 ここを、location": "[resourceGroup().location]としたら、locationが東日本になってしまいました。resourceGroup()は、親のdeploymentsリソースを指すようです。ちょっと混乱しますが、「まあ納得できる範囲かな」という気はします。

最後に

今回の元ネタは、@rjmaxの、Demos for Ignite US 2017 session BRK3167[4]です。このレポジトリだけで無く彼のレポジトリはARM template を使う人には必見の情報満載です、素晴らしい。特に、rjmax/ArmExamplesはお勧めですよ、ここのテンプレートは短くて見やすいのも良い感じです。