しばやん雑記

Azure とメイドさんが大好きなフリーランスのプログラマーのブログ

Dev Container / GitHub Codespaces を利用した Azure Functions 開発環境のベストプラクティス

昔にも Dev Container を利用して Azure Functions の開発環境を構築する方法を書いたのですが、その後 Dev Container の機能強化と Azure Functions のアップデートによってベストプラクティスが変わってきたので、現時点でのベストプラクティスを確認しておきました。

Windows 環境であれば Visual Studio 2022 を利用しておけば Azure Functions + C# の開発環境は一発で構築できますが、それ以外の言語で特に Python の場合は Dev Container を利用した方が良いケースが多いです。最近では Visual Studio Code を使う人も増えていますし、Dev Container を用意しておくと最悪でも GitHub Codespaces 上でブラウザベースの開発が出来るので便利になります。

Azure Functions 向けの Dev Container は以前はテンプレートを利用して作る流れでしたが、現在公開されている Dev Container 向けのイメージは 1 年以上更新がされていないため、もはや使い物になりません。Dev Container のページでも以下のように表示されています。

古いイメージを使ってしまうと Python V2 / Node.js V4 といった新しいプログラミングモデルが利用できないので、Azure Functions Core Tools のバージョンは常に新しいことが重要になります。

Visual Studio Code を使った Azure Functions の開発には Azure Functions Core Tools がインストールされていれば問題ないので、公式で用意されている各言語向けイメージに Core Tools を自動でインストールするのがシンプルです。作成した Dev Container のテンプレートは以下に公開しておきました。

ここから先はテンプレートの内容を簡単に説明しているだけなので、Dev Container を今すぐ使いたい人はあまり読む必要はないです。C# 向けの Dev Container を利用した例となります。

Dev Container のベースとなるイメージを選択する

Visual Studio Code を使うとドロップダウンでベースとなるイメージを選べますが、以下の公式ページでも一覧は確認出来るので、こちらの方が目当てのイメージを探すのが楽です。

今回は C# を使うので .NET のイメージを選択します。imageVariant は適当に選択しておきます。

重要になるのは devcontainer.jsonimage に書かれた値だけなので、この devcontainer.json をコピペして作っても、新しく作成して image を指定する方法でもどちらでも良いです。

Azure Functions Core Tools を自動インストールする

ベースとなるイメージの選択はシンプルですが、Azure Functions Core Tools のインストールは公式ドキュメントの通りにすると Dockerfile を用意してインストールすることになるので手間です。

最近の Dev Container では Features という形で Dev Container に対して Azure CLI などのツールをインストール出来るようになっているので、これを利用して Dockerfile を用意することなく Azure Functions Core Tools をインストールします。

非常に有難いことに、既に Azure Functions Core Tools をインストールする Feature が公開されているので、これを使うだけで任意の言語 + Azure Functions Core Tools の環境が出来上がります。

使い方も devcontainer.json ファイルの featuresghcr.io/jlaundry/devcontainer-features/azure-functions-core-tools:1 を追加するだけなのでシンプルです。これだけで最新の Core Tools v4 がインストール出来ます。v3 が必要な場合は version を指定します。

現時点の問題としてベースイメージに Debian 12 (bookworm) を指定すると Feature のインストールに失敗します。これは以下の Issue で指摘されている通り、まだ Azure Functions Core Tools が Debian 12 向けに公開されていないことあります。

対応されるまでは Debian 11 (bullseye) のベースイメージを使う必要があります。意外にはまる部分なのでバージョン指定は気を付けたいですね。

拡張機能を自動インストールする

Visual Studio Code で Azure Functions 開発を行うには専用の拡張機能が必要になるので、Dev Container が立ち上がったタイミングで自動的にインストールされるように設定します。

ローカルで拡張機能がインストール済みであれば、歯車ボタンを押して表示されるメニューから Add to devcontainer.json を選びます。これで devcontainer.json に設定が追加されます。

後は言語向けの拡張機能をインストールするようにしておきます。C# の場合は GitHub Codespaces にライセンスが含まれている C# Dev Kit をインストールするようにしておけば、一通りの機能が揃うので簡単です。

これで Azure Functions のプロジェクト作成と、その対応した言語でコードを書く準備が出来ます。後は Azure Functions の実行に必要なストレージアカウントを用意するだけです。

Azurite をバックグラウンドで自動起動する

Visual Studio 2022 には Azurite が組み込まれているので、Azure Functions のプロジェクトを作成すれば自動的に Azurite が起動するので、特に設定の必要なしで Azure Functions アプリケーションの開発が行えていましたが、VS Code ではそこまで高度な統合は用意されていないので、自前で何とかする必要があります。

一応 VS Code 向けの Azurite 拡張は用意されていますが、コマンドパレットで起動や停止を行う必要があるのでシームレスではありません。Azurite は Dev Container の起動と同時に立ち上がっていて欲しいので、今回は Docker Compose を利用して同時に Azurite のコンテナーを立ち上げるようにします。

Dev Container では Docker Compose 定義を使うことも出来るので、今回のように同時にデータストア系のコンテナーが必要な場合にも応用出来ます。

特に Dev Container だから書き方が変わるということはないですが、Dev Container 用のコンテナーがすぐに終わらないように command で無限にスリープするように指定しておくぐらいです。

version: "3"

services:
  app:
    image: mcr.microsoft.com/devcontainers/dotnet:0-6.0-bullseye
    volumes:
      - ..:/workspace:cached
    command: sleep infinity
    network_mode: service:azurite

  azurite:
    image: mcr.microsoft.com/azure-storage/azurite
    restart: unless-stopped

Azurite 用のコンテナーはデータを揮発させるつもりなので、特にボリュームの指定をしていません。永続化が必要な場合はマウントを追加して、Codespaces などの揮発しないストレージに保存させればよいです。

この docker-compose.yml を利用すると Dev Container と Azurite の両方が同時に起動します。

完成した devcontainer.json の例

最後に作成した devcontainer.json を載せておきます。dockerComposeFileservice の指定は Dev Container として Docker Compose を使う際に必要なものです。forwardPorts に Azurite のポートを追加しているので、ローカルの Azure Storage Explorer からアクセス可能です。

{
  "name": "Azure Functions (.NET)",
  "dockerComposeFile": "docker-compose.yml",
  "service": "app",
  "workspaceFolder": "/workspace",
  "forwardPorts": [
    7071,
    10000,
    10001,
    10002
  ],
  "otherPortsAttributes": {
    "onAutoForward": "ignore"
  },
  "features": {
    "ghcr.io/devcontainers/features/azure-cli:1": {},
    "ghcr.io/jlaundry/devcontainer-features/azure-functions-core-tools:1": {}
  },
  "customizations": {
    "vscode": {
      "extensions": [
        "ms-azuretools.vscode-azurefunctions",
        "ms-dotnettools.csdevkit"
      ]
    }
  }
}

この定義を使って Dev Container を起動すると、以下の通り Azurite が利用しているポートが稼働していることが分かります。GitHub Codespaces を使ってブラウザで実行している場合には外部からアクセスできませんが、ローカルの VS Code で開くと localhost にフォワーディングされるので、これまで通り Azure Storage Explorer からアクセス可能となります。

Azure Functions を VS Code から作成すると、デフォルトで用意された local.settings.json 内の AzureWebJobsStorage が空になっていますが、コマンドパレットから設定コマンドを実行するとエミュレーターを選択できます。意外に忘れがちなので注意します。

これで Azure Functions の開発環境が Dev Container / GitHub Codespaces 向けに構築出来ました。新しく Azure Functions のプロジェクトを作成して F5 を押せばスムーズにデバッグ実行まで行えることが確認出来ます。後はこの定義をリポジトリに含めておけば完成です。

今回は .NET 向けでしたが、最初に紹介したリポジトリには各言語向けのサンプルも用意しているので、そちらも参照してください。とはいえ考え方は .NET の時とほとんど同じで、重要なのは Azurite になります。

カスタム Docker イメージを利用する場合

.NET や Node.js ではカスタム Docker イメージを使う機会はほぼ無いのですが、Python の場合はビルドが必要なパッケージがあり、コンテナー側にもインストールが必要なケースも多いのでカスタム Docker イメージを使うことがあります。構成についてはドキュメントを確認してください。

Dev Container や GitHub Codespaces のようなコンテナーベースの環境では、Feature として ghcr.io/devcontainers/features/docker-in-docker:2 を追加するとコンテナーの中でも Docker CLI が使えるようになるので、Azure Functions のカスタム Docker イメージを使った開発が行えるようになります。

公開したリポジトリでは Python 向けの Dev Container 定義のみ対応しています。