rm /blog

IT系技術職のおっさんがIT技術とかライブとか日常とか雑多に語るブログです。* 本ブログに書かれている内容は個人の意見・感想であり、特定の組織に属するものではありません。/All opinions are my own.*

【Azure】Azure FunctionsをローカルのRemote Containerで開発するためのDockerイメージをつくった

タイトルの通り。
ノリ的にはこれに近い。
完全に個人用。
似たようなことをやりたくなったときのための備忘録として残す。

はじめに

Azure Functionsの勉強というか趣味使用のためにローカルに何らかの開発環境が欲しかったのだが、Azure Functionsを作成するときに出てくるWindow

f:id:rmrmrmarmrmrm:20210301213531p:plain

によれば色々インストールしなきゃならんものがある。

  1. azure-functions-core-tools
  2. .NET Core 2.1
  3. VS Code用のAzure Functions拡張機能

うーん、面倒くさい。。。
そもそもそんなに色々とローカルにぶち込みたくない。
それぞれにCLIツールがあるみたいだし、頑張ればDockerイメージで出来るんじゃねえの、と思った。

試行錯誤

どうもAzure FunctionsをCLIで操作・管理するためにはaz-cliとfunc-cliの2つが必要のようだ。
それぞれに公式のDockerイメージは存在する(az-clifunc-cli)が、この2つがくっついてるイメージがない。
どっちかをベースに育ててもいいと最初は思ったのだが、az-cliに関しても、func-cliに関しても、それぞれ個別にインストールする手順は公開されているので、どうせだからどっかの適当なベースイメージ使って1から育ててみるかと思った。

参考:

というわけで今回、ubuntuをベースにaz-cliとfunc-cliをインストールして起動するDockerイメージを作ることにした。

Dockerfile

FROM ubuntu:20.04

RUN apt-get update
RUN apt-get install -y curl wget

# install dotnet core sdk
RUN wget https://packages.microsoft.com/config/ubuntu/20.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb
RUN dpkg -i packages-microsoft-prod.deb
RUN apt-get update
RUN apt-get install -y apt-transport-https
RUN apt-get update
RUN apt-get install -y aspnetcore-runtime-5.0

# remove azure-cli
RUN apt remove azure-cli -y && apt autoremove -y

# install azure-cli
RUN curl -sL https://aka.ms/InstallAzureCLIDeb | bash

# install azure-functions-core-tools
RUN apt-get update
RUN apt-get install -y azure-functions-core-tools-3

# install nodejs
RUN curl -sL https://deb.nodesource.com/setup_14.x -o /tmp/nodesource_setup.sh
RUN bash /tmp/nodesource_setup.sh
RUN apt install nodejs
  • ベースイメージはubuntu:20.04である。
  • az-cliubuntuにインストールする方法はここを参照。
    ちなみにubuntu20.04の場合、もともとのリポジトリに「azure-cli」というのが含まれており、それ削除したほうがいいよとこの手順に書いてあるので、それも実行している。
  • func-cliのインスト―ルは上のリンクにもあるがここの「Linux」のタブを参照。
    v3.x系とv2.x系の2種類があるらしいが、違いが良く分からない。
    とりあえず上のページで「推奨」とされているv3.x系を入れることにした。
  • Azure Functionsは動作させるために.NETが必要らしい(これが入ってないとfunc initとかしたときに「dotnetがねーよ」と怒られる)ので、それも入れる。
    これはこちらにその方法が載っている。(リンク先はUbuntuに入れる方法だが)
    なお上のDockerfileではSDKを入れているが、ランタイムでも動くのかどうかはわからない。
  • あと個人的にFunctionsのランタイムとしてNode.jsを使う事が多い(っていうかほぼそれ)なので、最後にNode.jsのインストールもしている。
    これはここに載っている方法に従う。
    ちなみに後述する「functionのローカルでの実行確認」をしない(つまりfunc startを使うことがない)のであれば、実装ランタイムのインストール自体必要ない。

これでazfuncも使える汎用環境の出来上がりだ!
しかし色々詰め込みすぎてるせいだろうがこれで作るDockerイメージは1.77Gとなかなかのサイズを誇る。
もう少し軽量化を目指したいものだ。

使い方

起動方法

ふつうに動かす

上のDockerfileがある場所まで移動してdocker build -t hogehoge:latest .とかしてイメージをつくり、docker run --rm -it -d hogehoge:latestとかでコンテナを起動、返ってきたコンテナIDを使ってdocker exec -it [コンテナID] shで中に入る。
中に入ったらazでもfuncでも使って好きに作業すればよい。

VS Codeで動かす

しかしことfunctionの開発をするうえではVS Codeから動かした方が良い(っていうかそもそもそのためにこのDockerイメージを作ったのだ)
Remote Containerの拡張機能を使い、「Remote-Containers: Open Folder In Container ...」を選択→Dockerfileがあるフォルダを選択すれば、裏で自動でbuildが走って勝手にコンテナが立ち上がる。
コンテナ起動後は、VS CodeのTerminal機能が↑のdocker exec ...までやってくれた状態で、dockerコンテナ内に入ったところから始まる。
あとはエディタとTerminalを併用して作業していけばよい。

操作方法

az login

普通に動かそうがVS Codeで動かそうが、とにもかくにも最初にやる必要があるのはaz loginである。
なおMicrosoft Accountでログインする場合はaz login --tenant [tenant-id]と、--tenantオプションが必要だ。
ログインできたらaz account listとか実行して正常に接続できているか確認する。

functionapp作成

「関数アプリ」を作成する。
以下のコマンドを実行。
az functionapp create --resource-group [リソースグループ名] --consumption-plan-location japaneast --runtime node --functions-version 3 --name [関数アプリ名] --runtime-version 14 --storage-account [ストレージアカウント名]

  • --resource-groupは関数アプリを作成するリソースグループ名を指定する。
  • --consumption-plan-locationは関数アプリを作成する地域を指定する。japaneastは東日本を指す。これはaz functionapp list-consumption-locationsでリストが出せるので、返ってきた中から好きな地域を選んで指定する(参考)
  • --runtimeは関数の実装言語を指定する。nodeはNode.jsを指す。未指定でも作れるが、その場合デフォルトでdotnetが指定されたことになる。
  • --storage-accountはストレージアカウントを指定する。

これが正常に実行されると、実行した場所の直下に関数アプリ名のディレクトリが作成される。

function作成

functionappのディレクトリ配下に移動後、以下のコマンドで関数を作成する。
例えば以下はHTTPトリガーの作成例。
func new --name [関数名] --template "HTTP trigger" --language Javascript --authlevel "anonymous"

  • --languageは実装言語を指す。
  • --templateはトリガーの種類を指定する。"HTTP trigger"はその名の通りHTTPトリガーを指す。
  • --authlevelは関数の承認レベルを指定する。"anonymous"は未承認実行の許可を指す。

Timerトリガーの関数の場合は以下のようになる。
func new --name [関数名] --template "Timer trigger" --language Javascript

  • 実はHTTPトリガー(とkafkaトリガー)の場合はaz functionapp createのときに--storage-accountの指定が必要ない。
    要するにHTTPトリガー(とkafkaトリガー)に関してはストレージアカウントなしで作成できる。
    しかしTimerトリガー等の場合、ストレージアカウント未指定だと、func newした時点で「ストレージアカウントがねえよ」と言われて怒られる。
  • Timerトリガーの場合は--authlevelは付けられない(付けると怒られる)

func new実行後、直下に[関数名]のディレクトリが作成され、その配下に、ポータルで初期作成した直後と同様のファイル群(index.jsfunction.json)が出来上がっている。
VS Code上でそれらのファイルを開き、中身を開いて編集し、関数を実装していく。

functionのローカルでの実行確認

「関数アプリ」のルートディレクトリ(配下に「関数」のディレクトリがある場所)で、以下のコマンドを実行することで、ローカルでfunction環境(ローカルサーバー)が立ち上がる。
func start

こんなかんじで表示される↓
f:id:rmrmrmarmrmrm:20210301214525p:plain

  • デフォルトだと7101番ポートを使ってサーバーが立ち上がるらしい。
    Dockerコンテナで動かしてる場合はこのポートに対するポートフォワーディングの設定が必要である(まあVS CodeだとUI上で簡単に操作できてしまうが)
  • Timerトリガーなど、実行にストレージアカウントが必要な関数をローカルで動かす場合、事前にfunc azure functionapp fetch-app-settings [関数アプリ名]を実行しておく必要がある。
    このコマンドを実行すると、local.settings.jsonの中身が更新され、「AzureWebJobsStorage」の値が書き込まれる。
  • HTTPトリガーの場合は起動時にURLが標準出力されるので、そのURLに対してローカルのホスト側からcurlするなりブラウザアクセスするなりすれば動作が確認できる。
    Timerトリガーの場合はfunction.jsonに記述されたNCRONTAB式にしたがって実際に起動される。(例えば* */10 * * * *だったらfunc start実行後10分置きにfunctionが起動される)
  • 例えば今回のケースでは関数アプリの実行ランタイムをNode.jsに指定しているため、func startを実行した環境にNode.jsが入ってないと、「ランタイムがねえよ」と言われて怒られる。
    func startは指定したランタイムで実行するよう制御されているようだ。(まあでも普通に考えればそりゃそうか)
    逆にいうとローカルで実行確認をしない覚悟があるならDockerイメージ内に関数アプリの実行ランタイムのインストールは不要である。

一通り動作確認し終わったらCtrl+Cでプロセスを止める。

function deploy

「関数アプリ」のルートディレクトリ(配下に「関数」のディレクトリがある場所)で、以下のコマンドを実行することで、自身のAzure環境に関数アプリがデプロイされる。
func azure functionapp publish [関数アプリ名]

  • 事前にaz loginが必要だ。(ここでaz loginの実行有無がきいてくる。まあそりゃそうか)
    Azureにログインしてないと「サブスクリプションがねえよ」みたいなことを言われて怒られる。
  • 関数アプリのデプロイは「完全上書き」形式のようだ。
    要するにこのコマンドを実行した時点で配下にある関数群に基づきazure上の関数アプリの中身は置き換えになる。
    Azure上にAとBの関数があり、ローカルにAとCの関数があった場合、デプロイするとAzure上はAとCになる(Bが消滅する)

余談

実は最初、冒頭で書いたように、ローカルにazure-functions-core-toolsとか.NET SDKとかVS Code拡張とか全部入れて(つまりDockerイメージじゃなく)試してみたのだ。
だがなんだかどういうわけかうまくいかず(そもそもaz loginの部分(Azureにサインインする部分)が失敗してしまった)、これを追及するよりDockerつくったほうが早そうだなと思うに至った。
そういう意味ではこの環境作成はちょっと消極的に(半ば無理やり)たどり着いた方法論であり、個人的にイマイチ感があるにはあるのだが、一応これで開発できているので、まあいいかと思うようにしている。
(そそもそ今のところそんな大仰な開発はするつもりもないし)
それにローカルの環境を汚さないで開発ができるというのも一応はプラスになっている。
まあその分Dockerイメージのサイズが膨れ上がっている傾向はあるが。
今後必要に駆られた場合にちょいちょい機能追加していこうかなと思う次第である。