カスタム コンテナーを使用して Linux で関数を作成する

このチュートリアルでは、コードを作成し、Linux の基本イメージを使用したカスタム Docker コンテナーとして Azure Functions にデプロイします。 カスタム イメージを使用するのは通常、特定の言語バージョン、特定の依存関係、または組み込みイメージで提供されない構成が関数に必要になるときです。

Azure Functions では、カスタム ハンドラーを使用するすべての言語またはランタイムがサポートされています。 このチュートリアルで使用する R プログラミング言語など、一部の言語では、カスタム コンテナーの使用を必要とする依存関係として、ランタイムまたは追加のライブラリをインストールする必要があります。

関数コードをカスタム Linux コンテナーにデプロイするには、Premium プランまたは専用 (App Service) プランのホスティングが必要です。 このチュートリアルを完了すると、お使いの Azure アカウントで数ドルのコストが発生します。これは、完了時にリソースをクリーンアップすることによって最小限に抑えることができます。

Linux でホストされる初めての関数の作成に関するページで説明されている既定の Azure App Service コンテナーを使用することもできます。 Azure Functions でサポートされている基本イメージについては、Azure Functions 基本イメージ リポジトリを参照してください。

このチュートリアルでは、以下の内容を学習します。

  • Azure Functions Core Tools を使用して関数アプリと Dockerfile を作成します。
  • Docker を使用してカスタム イメージをビルドします。
  • カスタム イメージをコンテナー レジストリに発行します。
  • 関数アプリ用の関連リソースを Azure に作成します。
  • Docker Hub から Function App をデプロイします。
  • Function App にアプリケーション設定を追加します。
  • 継続的デプロイを有効にします。
  • コンテナーへの SSH 接続を有効にします。
  • Queue storage の出力バインドを追加します。
  • Azure Functions Core Tools を使用して関数アプリと Dockerfile を作成します。
  • Docker を使用してカスタム イメージをビルドします。
  • カスタム イメージをコンテナー レジストリに発行します。
  • 関数アプリ用の関連リソースを Azure に作成します。
  • Docker Hub から Function App をデプロイします。
  • Function App にアプリケーション設定を追加します。
  • 継続的デプロイを有効にします。
  • コンテナーへの SSH 接続を有効にします。

このチュートリアルは、Windows、macOS、または Linux が動作している任意のコンピューターで実行できます。

ローカル環境を構成する

開始する前に、次の項目を用意する必要があります。

  • Node.js アクティブ LTS およびメンテナンス LTS バージョン (8.11.1 および 10.14.1 を推奨)。
  • 使用している言語の開発ツール。 このチュートリアルでは、例として R プログラミング言語を使用します。

仮想環境を作成してアクティブにする

適切なフォルダーで次のコマンドを実行し、.venv という名前の仮想環境を作成してアクティブにします。 必ず、Azure Functions でサポートされている Python 3.8、3.7、または 3.6 を使用してください。

python -m venv .venv
source .venv/bin/activate

お使いの Linux ディストリビューションに Python の venv パッケージがインストールされていなかった場合は、次のコマンドを実行します。

sudo apt-get install python3-venv

以降のコマンドはすべて、このアクティブ化された仮想環境で実行します

ローカル関数プロジェクトを作成してテストする

ターミナルまたはコマンド プロンプトで、自分が選択した言語に合わせて次のコマンドを実行し、LocalFunctionsProject という名前のフォルダーに関数アプリ プロジェクトを作成します。

func init LocalFunctionsProject --worker-runtime dotnet --docker
func init LocalFunctionsProject --worker-runtime node --language javascript --docker
func init LocalFunctionsProject --worker-runtime powershell --docker
func init LocalFunctionsProject --worker-runtime python --docker
func init LocalFunctionsProject --worker-runtime node --language typescript --docker

空のフォルダーで次のコマンドを実行して、Maven アーキタイプから Functions プロジェクトを生成します。

mvn archetype:generate -DarchetypeGroupId=com.microsoft.azure -DarchetypeArtifactId=azure-functions-archetype -DjavaVersion=8 -Ddocker

-DjavaVersion パラメーターは、使用する Java のバージョンを Functions Runtime に指示します。 Java 11 で関数を実行する場合は、-DjavaVersion=11 を使用します。 -DjavaVersion を指定しない場合、Maven の既定値は Java 8 になります。 詳細については、「Java のバージョン」を参照してください。

重要

この記事の作業を行うには、JAVA_HOME 環境変数を、適切なバージョンの JDK のインストール場所に設定する必要があります。

Maven により、デプロイ時にプロジェクトの生成を終了するための値の入力が求められます。
入力を求められたら、次の値を入力します。

Prompt 説明
groupId com.fabrikam Java のパッケージ命名規則に従って、すべてのプロジェクトにわたって対象のプロジェクトを一意に識別する値。
artifactId fabrikam-functions バージョン番号のない、jar の名前である値。
version 1.0-SNAPSHOT 既定値を選択します。
package com.fabrikam.functions 生成された関数コードの Java パッケージである値。 既定値を使用します。

Y」と入力するか、Enter キーを押して確認します。

Maven により、artifactId という名前の新しいフォルダーにプロジェクト ファイルが作成されます (この例では fabrikam-functions)。

func init LocalFunctionsProject --worker-runtime custom --docker

--docker オプションによって、プロジェクトの Dockerfile が生成されます。これにより、Azure Functions および選択されたランタイムで使用するための適切なカスタム コンテナーが定義されます。

プロジェクト フォルダーに移動します。

cd LocalFunctionsProject
cd fabrikam-functions

次のコマンドを使用して、関数を自分のプロジェクトに追加します。ここで、--name 引数は関数の一意の名前で、--template 引数は関数のトリガーを指定するものです。 func new によって、関数と同じ名前のサブフォルダーが作成されます。ここには、プロジェクト用に選択した言語に適したコード ファイルと、function.json という名前の構成ファイルが含まれます。

func new --name HttpExample --template "HTTP trigger"

次のコマンドを使用して、関数を自分のプロジェクトに追加します。ここで、--name 引数は関数の一意の名前で、--template 引数は関数のトリガーを指定するものです。 func new によって、関数と同じ名前のサブフォルダーが作成されます。このサブフォルダーには、function.json という名前の構成ファイルが含まれます。

func new --name HttpExample --template "HTTP trigger"

テキスト エディターで、プロジェクト フォルダー内に handler.R という名前のファイルを作成します。 その内容として、以下を追加します。

library(httpuv)

PORTEnv <- Sys.getenv("FUNCTIONS_CUSTOMHANDLER_PORT")
PORT <- strtoi(PORTEnv , base = 0L)

http_not_found <- list(
  status=404,
  body='404 Not Found'
)

http_method_not_allowed <- list(
  status=405,
  body='405 Method Not Allowed'
)

hello_handler <- list(
  GET = function (request) {
    list(body=paste(
      "Hello,",
      if(substr(request$QUERY_STRING,1,6)=="?name=") 
        substr(request$QUERY_STRING,7,40) else "World",
      sep=" "))
  }
)

routes <- list(
  '/api/HttpExample' = hello_handler
)

router <- function (routes, request) {
  if (!request$PATH_INFO %in% names(routes)) {
    return(http_not_found)
  }
  path_handler <- routes[[request$PATH_INFO]]

  if (!request$REQUEST_METHOD %in% names(path_handler)) {
    return(http_method_not_allowed)
  }
  method_handler <- path_handler[[request$REQUEST_METHOD]]

  return(method_handler(request))
}

app <- list(
  call = function (request) {
    response <- router(routes, request)
    if (!'status' %in% names(response)) {
      response$status <- 200
    }
    if (!'headers' %in% names(response)) {
      response$headers <- list()
    }
    if (!'Content-Type' %in% names(response$headers)) {
      response$headers[['Content-Type']] <- 'text/plain'
    }

    return(response)
  }
)

cat(paste0("Server listening on :", PORT, "...\n"))
runServer("0.0.0.0", PORT, app)

host.jsoncustomHandler セクションを、カスタム ハンドラーのスタートアップ コマンドを構成するように変更します。

"customHandler": {
  "description": {
      "defaultExecutablePath": "Rscript",
      "arguments": [
      "handler.R"
    ]
  },
  "enableForwardingHttpRequest": true
}

関数をローカルでテストするために、プロジェクト フォルダーのルートでローカルの Azure Functions ランタイム ホストを起動します。

func start  
func start  
npm install
npm start
mvn clean package  
mvn azure-functions:run
R -e "install.packages('httpuv', repos='http://cran.rstudio.com/')"
func start

出力に HttpExample エンドポイントが表示されたら、http://localhost:7071/api/HttpExample?name=Functions に移動します。 name クエリ パラメーターに指定された値、Functions をエコーバックする "hello" メッセージがブラウザーに表示されます。

Ctrl - C キーを使用してホストを停止します。

コンテナー イメージを作成してローカルでテストする

(省略可) プロジェクト フォルダーのルートにある Dockerfile を確認します。 Dockerfile には、Linux 上で関数アプリを実行するために必要な環境が記述されています。 Azure Functions でサポートされている基本イメージの詳細な一覧については、Azure Functions 基本イメージ ページを参照してください。

プロジェクト フォルダーのルートにある Dockerfile を確認します。 Dockerfile には、Linux 上で関数アプリを実行するために必要な環境が記述されています。 カスタム ハンドラー アプリケーションでは、そのベースとして mcr.microsoft.com/azure-functions/dotnet:3.0-appservice イメージが使用されます。

Dockerfile を、R をインストールするように変更します。Dockerfile の内容を、次のように置き換えます。

FROM mcr.microsoft.com/azure-functions/dotnet:3.0-appservice 
ENV AzureWebJobsScriptRoot=/home/site/wwwroot \
    AzureFunctionsJobHost__Logging__Console__IsEnabled=true

RUN apt update && \
    apt install -y r-base && \
    R -e "install.packages('httpuv', repos='http://cran.rstudio.com/')"

COPY . /home/site/wwwroot

ルート プロジェクト フォルダーで、docker build コマンドを実行し、名前に azurefunctionsimage、タグに v1.0.0 を指定します。 <DOCKER_ID> を Docker Hub アカウント ID で置換します。 このコマンドでは、コンテナーの Docker イメージがビルドされます。

docker build --tag <DOCKER_ID>/azurefunctionsimage:v1.0.0 .

コマンドが完了すると、新しいコンテナーをローカルで実行できます。

ビルドをテストするために、ローカル コンテナーで docker run コマンドを使用してイメージを実行します。この場合も、<DOCKER_ID は実際の Docker ID に置き換え、ポート引数 -p 8080:80 を追加してください。

docker run -p 8080:80 -it <docker_id>/azurefunctionsimage:v1.0.0

ローカル コンテナーでイメージが実行状態になったら、ブラウザーで http://localhost:8080 を開きます。以下に示したプレースホルダー画像が表示されます。 この時点で画像が表示されるということは、Azure で実行されるときと同じように、ローカル コンテナーで関数が実行されているということです。つまり、function.json"authLevel": "function" プロパティで定義されたアクセス キーによって関数は保護されています。 ただし、Azure の関数アプリに対してまだコンテナーが発行されていないため、そのキーはまだ利用できません。 ローカル コンテナーに対してテストしたい場合は、Docker を停止し、承認プロパティを "authLevel": "anonymous" に変更して、イメージをリビルドしてから Docker を再起動してください。 その後、function.json"authLevel": "function" をリセットします。 詳細については、承認キーに関するセクションを参照してください。

コンテナーがローカルで実行されていることを示すプレースホルダー画像

ローカル コンテナーでイメージが実行状態になったら、http://localhost:8080/api/HttpExample?name=Functions に移動します。そこに、前と同じ "hello" メッセージが表示されます。 Maven アーキタイプでは、匿名の承認を使用する、HTTP によってトリガーされる関数が生成されるため、関数はコンテナーで実行されている場合でも呼び出すことができます。

コンテナー内の関数アプリを確認したら、Ctrl + C キーで Docker を停止します。

イメージを Docker Hub にプッシュする

Docker Hub は、イメージのホストとしてイメージ サービスとコンテナー サービスを提供するコンテナー レジストリです。 イメージを共有するには (Azure へのデプロイも含む)、それをレジストリにプッシュする必要があります。

  1. まだ Docker にサインインしていない場合は、docker login コマンドでサインインします。<docker_id> は、実際の Docker ID に置き換えてください。 このコマンドでは、ユーザー名とパスワードを入力するよう求められます。 "ログインに成功しました" のメッセージで、サインインしていることを確認します。

    docker login
    
  2. サインインしたら、docker push コマンドを使用して Docker Hub にイメージをプッシュします。ここでも、<docker_id> は実際の Docker ID に置き換えてください。

    docker push <docker_id>/azurefunctionsimage:v1.0.0
    
  3. ネットワーク速度によっては、イメージの初回プッシュに数分かかる場合があります (それ以降に行う変更のプッシュは、はるかに短い時間で済みます)。 待っている間、次のセクションに進んで、別のターミナルで Azure リソースを作成することができます。

関数用の関連 Azure リソースを作成する

関数コードを Azure にデプロイするには、3 つのリソースを作成する必要があります。

  • リソース グループ。関連リソースの論理コンテナーです。
  • Azure ストレージ アカウント。プロジェクトについての状態とその他の情報を保持します。
  • 関数アプリ。関数コードを実行するための環境となります。 関数アプリは、ローカルの関数プロジェクトと対応関係にあります。これを使用すると、リソースの管理、デプロイ、共有を容易にするための論理ユニットとして関数をグループ化できます。

Azure CLI コマンドを使用して、これらの項目を作成しましょう。 それぞれのコマンドからは、完了時に JSON 出力が返されます。

  1. az login コマンドを使用して、Azure にサインインします。

    az login
    
  2. az group create コマンドを使用して、リソース グループを作成します。 次の例では、AzureFunctionsContainers-rg という名前のリソース グループを westeurope リージョンに作成します。 (リソース グループとリソースは通常、近くのリージョンに作成します。az account list-locations コマンドから返される利用可能なリージョンを使用してください。)

    az group create --name AzureFunctionsContainers-rg --location westeurope
    

    注意

    Linux と Windows のアプリを同じリソース グループ内でホストすることはできません。 Windows の関数アプリまたは Web アプリで AzureFunctionsContainers-rg という名前のリソース グループが存在する場合、別のリソース グループを使用する必要があります。

  3. az storage account create コマンドを使用して、リソース グループとリージョン内に汎用ストレージ アカウントを作成します。 次の例の <storage_name> は適宜、グローバルに一意の名前に置き換えてください。 名前は 3 文字から 24 文字とし、小文字のみを使用する必要があります。 Standard_LRS では、標準的な汎用アカウントを指定します。

    az storage account create --name <storage_name> --location westeurope --resource-group AzureFunctionsContainers-rg --sku Standard_LRS
    

    このチュートリアルでは、ストレージ アカウントに関して数セントの料金が発生します。

  4. コマンドを使用して、Azure Functions 用の Premium プランを myPremiumPlan という名前で作成します。価格レベルは Elastic Premium 1 (--sku EP1)、リージョンは西ヨーロッパ (-location westeurope) または最寄りのリージョン、作成先は Linux コンテナー (--is-linux) とします。

    az functionapp plan create --resource-group AzureFunctionsContainers-rg --name myPremiumPlan --location westeurope --number-of-workers 1 --sku EP1 --is-linux
    

    ここでは、必要に応じてスケーリングできる Premium プランを使用します。 ホスティングについて詳しくは、「Azure Functions のホスティング プランの比較」をご覧ください。 コストを計算するには、Functions の価格に関するページを参照してください。

    また、このコマンドを実行すると、関連する Azure Application Insights インスタンスが同じリソース グループにプロビジョニングされます。このインスタンスを使用することで、関数アプリを監視したりログを確認したりすることができます。 詳しくは、「Azure Functions を監視する」をご覧ください。 このインスタンスは、アクティブにするまでコストが発生しません。

イメージを使用して Azure 上の関数アプリを作成、構成する

Azure 上の関数アプリでは、ホスティング プランで関数の実行を管理します。 このセクションでは、前のセクションの Azure リソースを使用して、Docker Hub 上のイメージから関数アプリを作成し、Azure Storage への接続文字列を使用してそれを構成します。

  1. az functionapp create コマンドを使用して関数アプリを作成します。 次の例の <storage_name> は、前のセクションで使用したストレージ アカウントの名前に置き換えてください。 また、<app_name> は適宜グローバルに一意の名前に、<docker_id> は実際の Docker ID に置き換えます。

    az functionapp create --name <app_name> --storage-account <storage_name> --resource-group AzureFunctionsContainers-rg --plan myPremiumPlan --runtime <functions runtime stack> --deployment-container-image-name <docker_id>/azurefunctionsimage:v1.0.0
    
    az functionapp create --name <app_name> --storage-account <storage_name> --resource-group AzureFunctionsContainers-rg --plan myPremiumPlan --runtime custom --deployment-container-image-name <docker_id>/azurefunctionsimage:v1.0.0
    

    deployment-container-image-name パラメーターでは、関数アプリに使用するイメージを指定します。 デプロイに使用されているイメージに関する情報は、az functionapp config container show コマンドを使用して表示できます。 az functionapp config container set コマンドを使用して、別のイメージからデプロイすることもできます。

  2. 作成したストレージ アカウントの接続文字列を az storage account show-connection-string コマンドで表示します。 <storage-name> を、上で作成したストレージ アカウントの名前に置き換えます。

    az storage account show-connection-string --resource-group AzureFunctionsContainers-rg --name <storage_name> --query connectionString --output tsv
    
  3. az functionapp config appsettings set コマンドを使用して、この設定を関数アプリに追加します。 次のコマンドで、<app_name> を関数アプリの名前に置き換え、<connection_string> を前の手順の接続文字列 ("DefaultEndpointProtocol=" で始まる長いエンコード文字列) に置き換えます。

    az functionapp config appsettings set --name <app_name> --resource-group AzureFunctionsContainers-rg --settings AzureWebJobsStorage=<connection_string>
    

    ヒント

    Bash では、クリップボードではなく、シェル変数を使用して接続文字列をキャプチャできます。 まず、次のコマンドを使用して、接続文字列の変数を作成します。

    storageConnectionString=$(az storage account show-connection-string --resource-group AzureFunctionsContainers-rg --name <storage_name> --query connectionString --output tsv)
    

    次に、2 つ目のコマンドで変数を参照します。

    az functionapp config appsettings set --name <app_name> --resource-group AzureFunctionsContainers-rg --settings AzureWebJobsStorage=$storageConnectionString
    
  4. 関数でこの接続文字列を使用してストレージ アカウントにアクセスできるようになりました。

注意

カスタム イメージをプライベート コンテナー アカウントに発行する場合は、接続文字列に Dockerfile 内の環境変数を使用する必要があります。 詳細については、ENV の手順を参照してください。 また、変数 DOCKER_REGISTRY_SERVER_USERNAME および DOCKER_REGISTRY_SERVER_PASSWORD も設定する必要があります。 これらの値を使用するには、イメージをリビルドしてそのイメージをレジストリにプッシュした後、Azure で関数アプリを再起動する必要があります。

Azure 上で関数を確認する

Azure 上の関数アプリにイメージをデプロイしたら、HTTP 要求を使用して関数を呼び出すことができます。 function.json 定義に "authLevel": "function" プロパティが存在するので、まずアクセス キー ("関数キー" とも呼ばれます) を取得して、それを URL パラメーターとしてエンドポイントへの要求に含める必要があります。

  1. Azure portal を使用するか、または Azure CLI と az rest コマンドを使用して、アクセス (関数) キーを含む関数の URL を取得します。

    1. Azure portal にサインインし、"関数アプリ" を検索して選択します。

    2. 検証する関数を選択します。

    3. 左側のナビゲーション パネルで [関数] を選択し、検証する関数を選択します。

      Azure portal で関数を選択する

    4. [関数の URL の取得] を選択します。

      Azure portal から関数 URL を取得する

    5. ポップアップ ウィンドウで、 [default (function key)](既定 (関数キー)) を選択し、URL をクリップボードにコピーします。 ?code= に続く文字列がキーです

      既定の関数アクセス キーを選択する

    注意

    関数アプリはコンテナーとしてデプロイされるため、ポータルでその関数コードを変更することはできません。 ローカル イメージでプロジェクトを更新して、再度イメージをレジストリにプッシュした後、Azure に再デプロイする必要があります。 後続のセクションで継続的デプロイを設定することができます。

  2. 関数 URL をブラウザーのアドレス バーに貼り付けます。この URL の末尾にパラメーター &name=Azure を追加してください。 "Hello, Azure" のようなテキストがブラウザーに表示されます。

    ブラウザーでの関数の応答。

  3. 承認をテストするために、URL から code= パラメーターを取り除き、関数から応答がないことを確認します。

Azure への継続的デプロイを有効にする

レジストリ内のイメージを更新するたびに、イメージのデプロイを Azure Functions に自動的に更新させることができます。

  1. az functionapp deployment container config コマンドを使用して継続的デプロイを有効にします。<app_name> は、実際の関数アプリの名前に置き換えてください。

    az functionapp deployment container config --enable-cd --query CI_CD_URL --output tsv --name <app_name> --resource-group AzureFunctionsContainers-rg
    

    このコマンドは、継続的デプロイを有効にして、デプロイの Webhook URL を返します。 (この URL は、後から az functionapp deployment container show-cd-url コマンドを使用していつでも取得できます。)

  2. デプロイの Webhook URL をクリップボードにコピーします。

  3. [Docker Hub] を開いてサインインし、ナビゲーション バーの [Repositories](リポジトリ) を選択します。 イメージを検索して選択し、 [Webhooks] タブを選択します。次に、Webhook の名前 を指定して、 [Webhook URL] に URL を貼り付け、 [Create](作成) を選択します。

    DockerHub リポジトリに Webhook を追加する

  4. Webhook の設定後は、Docker Hub でイメージを更新するたびに、それが Azure Functions によって再デプロイされます。

SSH 接続を有効にする

SSH では、コンテナーとクライアント間の通信をセキュリティで保護できます。 SSH が有効になっている場合は、App Service Advanced Tools (Kudu) を使用してコンテナーに接続できます。 SSH を使用してコンテナーに簡単に接続できるように、Azure Functions には、SSH が既に有効になっている基本イメージが用意されています。 必要な作業は、Dockerfile を編集してから、イメージをリビルドして再デプロイするだけです。 その後、[高度なツール (Kudu)] を使用してコンテナーに接続することができます。

  1. Dockerfile で、FROM 命令の基本イメージに文字列 -appservice を追加します。

    FROM mcr.microsoft.com/azure-functions/dotnet:3.0-appservice
    
    FROM mcr.microsoft.com/azure-functions/node:2.0-appservice
    
    FROM mcr.microsoft.com/azure-functions/powershell:2.0-appservice
    
    FROM mcr.microsoft.com/azure-functions/python:2.0-python3.7-appservice
    
    FROM mcr.microsoft.com/azure-functions/node:2.0-appservice
    
  2. もう一度 docker build コマンドを使用してイメージをリビルドします。<docker_id> は、実際の Docker ID に置き換えてください。

    docker build --tag <docker_id>/azurefunctionsimage:v1.0.0 .
    
  3. 更新済みのイメージを Docker Hub にプッシュします。アップロードする必要があるのは、イメージの更新済みセグメントのみであるため、初回プッシュ時よりもはるかに短時間で済みます。

    docker push <docker_id>/azurefunctionsimage:v1.0.0
    
  4. Azure Functions によってイメージが自動的に関数アプリに再デプロイされます。この処理の所要時間は 1 分未満です。

  5. ブラウザーで https://<app_name>.scm.azurewebsites.net/ を開きます。<app_name> は、一意の名前に置き換えてください。 この URL は、関数アプリ コンテナーの [高度なツール (Kudu)] エンドポイントです。

  6. 自分の Azure アカウントにサインインし、 [SSH] を選択して、コンテナーとの接続を確立します。 Azure によるコンテナー イメージの更新がまだ行われている場合、接続に少し時間がかかる場合があります。

  7. コンテナーとの接続が確立されたら、top コマンドを実行して、現在実行中のプロセスを表示します。

    SSH セッションで実行されている Linux top コマンド

Azure Storage キューに書き込む

Azure Functions を使用すると、独自の統合コードを記述することなく他の Azure サービスやリソースに関数を接続できます。 これらの バインド は、入力と出力の両方を表し、関数定義内で宣言されます。 バインドからのデータは、パラメーターとして関数に提供されます。 "トリガー" は、特殊な種類の入力バインドです。 関数はトリガーを 1 つしか持てませんが、複数の入力および出力バインドを持つことができます。 詳細については、「Azure Functions でのトリガーとバインドの概念」を参照してください。

このセクションでは、関数と Azure Storage キューを統合する方法について説明します。 この関数に追加する出力バインドは、HTTP 要求のデータをキュー内のメッセージに書き込みます。

Azure Storage の接続文字列を取得する

先ほど、関数アプリで使用する Azure ストレージ アカウントを作成しました。 このアカウントの接続文字列は、Azure のアプリ設定に安全に格納されています。 関数をローカルで実行するときは、local.settings.json ファイルに設定をダウンロードすることにより、その接続を使用して同じアカウントのストレージ キューへの書き込みを行うことができます。

  1. プロジェクトのルートから、次のコマンドを実行します。<app_name> は、前のクイックスタートの関数アプリ名に置き換えてください。 このコマンドを実行すると、ファイル内の既存の値はすべて上書きされます。

    func azure functionapp fetch-app-settings <app_name>
    
  2. local.settings.json を開き、AzureWebJobsStorage という名前の値を検索します。それがストレージ アカウントの接続文字列です。 AzureWebJobsStorage という名前と接続文字列は、この記事の他のセクションで使用します。

重要

local.settings.json には、Azure からダウンロードされたシークレットが含まれているため、このファイルは必ずソース管理から除外してください。 ローカル関数プロジェクトで作成される .gitignore ファイルからは、このファイルが既定で除外されます。

バインディング拡張機能を登録する

HTTP トリガーとタイマー トリガーを除き、バインドは拡張機能パッケージとして実装されます。 ターミナル ウィンドウで次の dotnet add package コマンドを実行して、Storage 拡張機能パッケージをプロジェクトに追加します。

dotnet add package Microsoft.Azure.WebJobs.Extensions.Storage --version 3.0.4

これで、Storage の出力バインドをプロジェクトに追加できるようになります。

出力バインディングの定義を関数に追加する

関数に割り当てることができるトリガーは 1 つだけですが、入力と出力のバインディングは複数割り当てることができます。これらを使用すると、カスタム統合コードを記述しなくても、他の Azure サービスやリソースに接続できます。

関数フォルダーの function.json ファイルでそれらのバインディングを宣言します。 前のクイックスタートの HttpExample フォルダーにある function.json ファイルでは、bindings コレクション内に 2 つのバインディングが含まれています。

"bindings": [
    {
        "authLevel": "function",
        "type": "httpTrigger",
        "direction": "in",
        "name": "req",
        "methods": [
            "get",
            "post"
        ]
    },
    {
        "type": "http",
        "direction": "out",
        "name": "res"
    }
]
"scriptFile": "__init__.py",
"bindings": [
    {
        "authLevel": "function",
        "type": "httpTrigger",
        "direction": "in",
        "name": "req",
        "methods": [
            "get",
            "post"
        ]
    },
    {
        "type": "http",
        "direction": "out",
        "name": "$return"
    }
"bindings": [
  {
    "authLevel": "function",
    "type": "httpTrigger",
    "direction": "in",
    "name": "Request",
    "methods": [
      "get",
      "post"
    ]
  },
  {
    "type": "http",
    "direction": "out",
    "name": "Response"
  }
]

それぞれのバインディングには、少なくとも型、方向、名前があります。 上の例の 1 つ目のバインディングは、型が httpTrigger で、方向が in になっています。 in 方向の場合、トリガーによって呼び出された関数に送信される入力パラメーターの名前が name で指定されます。

コレクションの 2 つ目のバインディングの名前は res です。 この http バインディングは、HTTP 応答の書き込みに使用される出力バインディング (out) です。

この関数から Azure Storage キューに書き込みを行うには、次のコードで示すように、queue 型の out バインディングを msg という名前で追加します。

    {
      "authLevel": "function",
      "type": "httpTrigger",
      "direction": "in",
      "name": "req",
      "methods": [
        "get",
        "post"
      ]
    },
    {
      "type": "http",
      "direction": "out",
      "name": "res"
    },
    {
      "type": "queue",
      "direction": "out",
      "name": "msg",
      "queueName": "outqueue",
      "connection": "AzureWebJobsStorage"
    }
  ]
}

コレクションの 2 つ目のバインディングは、型が http で方向が out です。この場合、このバインディングは入力パラメーターを渡すものではなく、関数の戻り値を使用するものであることが、$return という特殊な name からわかります。

この関数から Azure Storage キューに書き込みを行うには、次のコードで示すように、queue 型の out バインディングを msg という名前で追加します。

"bindings": [
  {
    "authLevel": "anonymous",
    "type": "httpTrigger",
    "direction": "in",
    "name": "req",
    "methods": [
      "get",
      "post"
    ]
  },
  {
    "type": "http",
    "direction": "out",
    "name": "$return"
  },
  {
    "type": "queue",
    "direction": "out",
    "name": "msg",
    "queueName": "outqueue",
    "connection": "AzureWebJobsStorage"
  }
]

コレクションの 2 つ目のバインディングの名前は res です。 この http バインディングは、HTTP 応答の書き込みに使用される出力バインディング (out) です。

この関数から Azure Storage キューに書き込みを行うには、次のコードで示すように、queue 型の out バインディングを msg という名前で追加します。

    {
      "authLevel": "function",
      "type": "httpTrigger",
      "direction": "in",
      "name": "Request",
      "methods": [
        "get",
        "post"
      ]
    },
    {
      "type": "http",
      "direction": "out",
      "name": "Response"
    },
    {
      "type": "queue",
      "direction": "out",
      "name": "msg",
      "queueName": "outqueue",
      "connection": "AzureWebJobsStorage"
    }
  ]
}

このケースでは、msg が出力引数として関数に与えられています。 型 queue では、queueName にキューの名前を指定し、(local.settings.json から得られる) Azure Storage 接続の "名前" を connection に指定する必要もあります。

C# クラス ライブラリ プロジェクトでは、バインドは関数メソッドのバインド属性として定義されます。 その後、Functions に必要な function.json ファイルが、これらの属性に基づいて自動的に生成されます。

HttpExample.cs プロジェクト ファイルを開いて、Run メソッドの定義に次のパラメーターを追加します。

[Queue("outqueue"),StorageAccount("AzureWebJobsStorage")] ICollector<string> msg,

msg パラメーターは ICollector<T> 型です。これは、関数の完了時に出力バインドに書き込まれるメッセージのコレクションを表します。 この場合、出力は outqueue という名前のストレージ キューです。 このストレージ アカウントの接続文字列は、StorageAccountAttribute で設定されます。 この属性は、ストレージ アカウントの接続文字列を含む設定を示し、クラス、メソッド、パラメーター レベルで適用できます。 この例では、既定のストレージ アカウントを既に使用しているため、StorageAccountAttribute を省略できます。

Run メソッドの定義は次のようになります。

[FunctionName("HttpExample")]
public static async Task<IActionResult> Run(
    [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req, 
    [Queue("outqueue"),StorageAccount("AzureWebJobsStorage")] ICollector<string> msg, 
    ILogger log)

Java プロジェクトでは、バインドは関数メソッドのバインド注釈として定義されます。 その後、これらの注釈に基づいて function.json ファイルが自動的に生成されます。

src/main/java の下の対象の関数コードの場所を参照し、Function.java プロジェクト ファイルを開きます。run メソッド定義に、次のパラメーターを追加します。

@QueueOutput(name = "msg", queueName = "outqueue", connection = "AzureWebJobsStorage") OutputBinding<String> msg

msg パラメーターは OutputBinding<T> 型です。これは、関数の完了時に出力バインドにメッセージとして書き込まれる文字列のコレクションを表します。 この場合、出力は outqueue という名前のストレージ キューです。 このストレージ アカウントの接続文字列は、connection メソッドによって設定されます。 接続文字列自体ではなく、ストレージ アカウントの接続文字列を含むアプリケーション設定を渡します。

run メソッドの定義は次の例のようになります。

@FunctionName("HttpTrigger-Java")
public HttpResponseMessage run(
        @HttpTrigger(name = "req", methods = {HttpMethod.GET, HttpMethod.POST}, authLevel = AuthorizationLevel.FUNCTION)  
        HttpRequestMessage<Optional<String>> request, 
        @QueueOutput(name = "msg", queueName = "outqueue", connection = "AzureWebJobsStorage") 
        OutputBinding<String> msg, final ExecutionContext context) {
    ...
}

出力バインディングを使用するコードを追加する

キュー バインディングが定義されたら、msg 出力パラメーターを受け取ってメッセージをキューに書き込むように関数を更新することができます。

次のコードに合わせて HttpExample\__init__.py を更新します。関数の定義に msg パラメーターを、if name: ステートメントの下に msg.set(name) を追加してください。

import logging

import azure.functions as func


def main(req: func.HttpRequest, msg: func.Out[func.QueueMessage]) -> str:

    name = req.params.get('name')
    if not name:
        try:
            req_body = req.get_json()
        except ValueError:
            pass
        else:
            name = req_body.get('name')

    if name:
        msg.set(name)
        return func.HttpResponse(f"Hello {name}!")
    else:
        return func.HttpResponse(
            "Please pass a name on the query string or in the request body",
            status_code=400
        )

msg パラメーターは、azure.functions.InputStream class のインスタンスです。 その set メソッドでは、文字列メッセージ (この場合、URL のクエリ文字列として関数に渡された名前) をキューに書き込みます。

context.bindingsmsg 出力バインド オブジェクトを使用してキュー メッセージを作成するコードを追加します。 このコードを context.res ステートメントの前に追加します。

// Add a message to the Storage queue,
// which is the name passed to the function.
context.bindings.msg = (req.query.name || req.body.name);

この時点で、関数は次のようになります。

module.exports = async function (context, req) {
    context.log('JavaScript HTTP trigger function processed a request.');

    if (req.query.name || (req.body && req.body.name)) {
        // Add a message to the Storage queue,
        // which is the name passed to the function.
        context.bindings.msg = (req.query.name || req.body.name);
        context.res = {
            // status: 200, /* Defaults to 200 */
            body: "Hello " + (req.query.name || req.body.name)
        };
    }
    else {
        context.res = {
            status: 400,
            body: "Please pass a name on the query string or in the request body"
        };
    }
};

context.bindingsmsg 出力バインド オブジェクトを使用してキュー メッセージを作成するコードを追加します。 このコードを context.res ステートメントの前に追加します。

context.bindings.msg = name;

この時点で、関数は次のようになります。

import { AzureFunction, Context, HttpRequest } from "@azure/functions"

const httpTrigger: AzureFunction = async function (context: Context, req: HttpRequest): Promise<void> {
    context.log('HTTP trigger function processed a request.');
    const name = (req.query.name || (req.body && req.body.name));

    if (name) {
        // Add a message to the storage queue, 
        // which is the name passed to the function.
        context.bindings.msg = name; 
        // Send a "hello" response.
        context.res = {
            // status: 200, /* Defaults to 200 */
            body: "Hello " + (req.query.name || req.body.name)
        };
    }
    else {
        context.res = {
            status: 400,
            body: "Please pass a name on the query string or in the request body"
        };
    }
};

export default httpTrigger;

Push-OutputBinding コマンドレットと msg 出力バインディングを使用してキューにテキストを書き込むコードを追加します。 if ステートメントで OK ステータスを設定する前に、このコードを追加してください。

$outputMsg = $name
Push-OutputBinding -name msg -Value $outputMsg

この時点で、関数は次のようになります。

using namespace System.Net

# Input bindings are passed in via param block.
param($Request, $TriggerMetadata)

# Write to the Azure Functions log stream.
Write-Host "PowerShell HTTP trigger function processed a request."

# Interact with query parameters or the body of the request.
$name = $Request.Query.Name
if (-not $name) {
    $name = $Request.Body.Name
}

if ($name) {
    # Write the $name value to the queue, 
    # which is the name passed to the function.
    $outputMsg = $name
    Push-OutputBinding -name msg -Value $outputMsg

    $status = [HttpStatusCode]::OK
    $body = "Hello $name"
}
else {
    $status = [HttpStatusCode]::BadRequest
    $body = "Please pass a name on the query string or in the request body."
}

# Associate values to output bindings by calling 'Push-OutputBinding'.
Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{
    StatusCode = $status
    Body = $body
})

msg 出力バインド オブジェクトを使用してキュー メッセージを作成するコードを追加します。 このコードは、メソッドから制御が戻る前に追加します。

if (!string.IsNullOrEmpty(name))
{
    // Add a message to the output collection.
    msg.Add(string.Format("Name passed to the function: {0}", name));
}

この時点で、関数は次のようになります。

[FunctionName("HttpExample")]
public static async Task<IActionResult> Run(
    [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req, 
    [Queue("outqueue"),StorageAccount("AzureWebJobsStorage")] ICollector<string> msg, 
    ILogger log)
{
    log.LogInformation("C# HTTP trigger function processed a request.");

    string name = req.Query["name"];

    string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
    dynamic data = JsonConvert.DeserializeObject(requestBody);
    name = name ?? data?.name;

    if (!string.IsNullOrEmpty(name))
    {
        // Add a message to the output collection.
        msg.Add(string.Format("Name passed to the function: {0}", name));
    }
    return name != null
        ? (ActionResult)new OkObjectResult($"Hello, {name}")
        : new BadRequestObjectResult("Please pass a name on the query string or in the request body");
}

これで、新しい msg パラメーターを使用して、関数コードから出力バインドに書き込むことができます。 成功応答の前に次のコード行を追加して、name の値を msg 出力バインドに追加します。

msg.setValue(name);

出力バインドを使用すると、認証、キュー参照の取得、またはデータの書き込みに、Azure Storage SDK のコードを使用する必要がなくなります。 Functions ランタイムおよびキューの出力バインドが、ユーザーに代わってこれらのタスクを処理します。

run メソッドは次の例のようになります。

public HttpResponseMessage run(
        @HttpTrigger(name = "req", methods = {HttpMethod.GET, HttpMethod.POST}, authLevel = AuthorizationLevel.ANONYMOUS) 
        HttpRequestMessage<Optional<String>> request, 
        @QueueOutput(name = "msg", queueName = "outqueue", 
        connection = "AzureWebJobsStorage") OutputBinding<String> msg, 
        final ExecutionContext context) {
    context.getLogger().info("Java HTTP trigger processed a request.");

    // Parse query parameter
    String query = request.getQueryParameters().get("name");
    String name = request.getBody().orElse(query);

    if (name == null) {
        return request.createResponseBuilder(HttpStatus.BAD_REQUEST)
        .body("Please pass a name on the query string or in the request body").build();
    } else {
        // Write the name to the message queue. 
        msg.setValue(name);

        return request.createResponseBuilder(HttpStatus.OK).body("Hello, " + name).build();
    }
}

テストを更新する

アーキタイプはテストのセットも作成するため、run メソッド シグネチャ内の新しい msg パラメーターを処理するためにこれらのテストを更新する必要があります。

src/main/java の下の対象のテスト コードの場所を参照し、Function.java プロジェクト ファイルを開きます。//Invoke の下のコード行を次のコードに置き換えます。

@SuppressWarnings("unchecked")
final OutputBinding<String> msg = (OutputBinding<String>)mock(OutputBinding.class);
final HttpResponseMessage ret = new Function().run(req, msg, context);

レジストリ内のイメージを更新する

  1. ルート フォルダーで docker build コマンドを再度実行します。今回は、タグ内のバージョンを v1.0.1 に更新します。 以前と同様に、<docker_id> を Docker Hub アカウント ID に置き換えてください。

    docker build --tag <docker_id>/azurefunctionsimage:v1.0.1 .
    
  2. docker push を使用して、更新済みのイメージをリポジトリにプッシュして戻します。

    docker push <docker_id>/azurefunctionsimage:v1.0.1
    
  3. 継続的デリバリーを構成したため、レジストリにあるイメージを再度更新すると、Azure にある関数アプリが自動的に更新されます。

Azure Storage キューのメッセージを確認する

ブラウザーで、これまでと同じ URL を使用して自分の関数を呼び出します。 以前と同じ応答がブラウザーに表示されるはずです。その部分については、関数コードに変更を加えていないためです。 ただし追加したコードでは、URL パラメーター name を使用して、outqueue ストレージ キューにメッセージを書き込みました。

キューは、Azure portal または Microsoft Azure Storage Explorer で確認できます。 次の手順に従って、Azure CLI でキューを確認することもできます。

  1. 関数プロジェクトの local.setting.json ファイルを開き、接続文字列の値をコピーします。 ターミナルまたはコマンド ウィンドウで、次のコマンドを実行して、AZURE_STORAGE_CONNECTION_STRING という名前の環境変数を作成し、<MY_CONNECTION_STRING> の代わりに実際の接続文字列を貼り付けます。 (この環境変数を作成すれば、--connection-string 引数を使用して接続文字列を後続の各コマンドに指定する必要はありません。)

    export AZURE_STORAGE_CONNECTION_STRING="<MY_CONNECTION_STRING>"
    
  2. (省略可) az storage queue list コマンドを使用して、ご利用のアカウント内のストレージ キューを表示します。 このコマンドからの出力には、outqueue という名前のキューが含まれています。これはこのキューに対する最初のメッセージを関数が書き込んだときに作成されたものです。

    az storage queue list --output tsv
    
  3. az storage message get コマンドを使用して、このキューからメッセージ (先ほど関数をテストするときに使用した名) を読み取ります。 このコマンドは、キューから最初のメッセージを読み取って削除します。

    echo `echo $(az storage message get --queue-name outqueue -o tsv --query '[].{Message:content}') | base64 --decode`
    

    メッセージ本文は base64 でエンコードされた状態で保存されるため、表示するメッセージをあらかじめデコードしておく必要があります。 az storage message get を実行すると、メッセージがキューから削除されます。 outqueue にメッセージが 1 つしかない場合、このコマンドを 2 回目に実行したときにメッセージは取得されず、代わりにエラーが返されます。

リソースをクリーンアップする

このチュートリアルで作成したリソースを使用して、引き続き Azure 関数に取り組む場合は、それらのリソースをすべてそのままにしてかまいません。 Azure Functions 用の Premium プランを作成したため、継続するためのコストとして、1 日につき 1 ドルまたは 2 ドルの料金がかかります。

継続コストを避けるためには、AzureFunctionsContainer-rg リソース グループを削除して、そのグループのリソースをすべてクリーンアップしてください。

az group delete --name AzureFunctionsContainer-rg

次のステップ