カスタム コンテナーを使用して 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 が動作している任意のコンピューターで実行できます。

重要

カスタム コンテナーを使用する場合は、コンテナーの基本イメージを、サポートされている最新の基本イメージに更新しておく必要があります。 Azure Functions でサポートされている基本イメージは言語固有であり、言語別の Azure Functions 基本イメージ リポジトリにあります。

Functions チームは、これらの基本イメージの毎月の更新プログラムを公開できるよう取り組んでいます。 通常の更新プログラムには、Functions ランタイムと言語の両方について、最新のマイナー バージョンの更新プログラムとセキュリティ修正プログラムが含まれます。 カスタム コンテナーの場合は、Dockerfile 内の基本イメージを定期的に更新し、カスタム コンテナーの更新されたバージョンを再構築して再デプロイする必要があります。

ローカル環境を構成する

操作を始める前に、以下の要件を満たしておく必要があります。

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

Azure サブスクリプションをお持ちでない場合は、開始する前に Azure 無料アカウントを作成してください。

Docker と Docker ID も取得する必要があります。

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

適切なフォルダーで次のコマンドを実行し、.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

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

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

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

func init --worker-runtime dotnet --docker
func init --worker-runtime node --language javascript --docker
func init --worker-runtime powershell --docker
func init --worker-runtime python --docker
func init --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 --worker-runtime custom --docker

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

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

cd fabrikam-functions

Dockerfile を変更する必要はありません。

次のコマンドを使用して、関数を自分のプロジェクトに追加します。ここで、--name 引数は関数の一意の名前で、--template 引数は関数のトリガーを指定するものです。 func new により、お使いのプロジェクトに C# コード ファイルが作成されます。

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

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

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

テキスト エディターで、プロジェクト フォルダー内に 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.json セクションを、カスタム ハンドラーのスタートアップ コマンドを構成するように変更します。

"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 Hub アカウント ID に置き換え、ポート引数として -p 8080:80 を追加してください。

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

イメージがローカル コンテナーで開始したら、http://localhost:8080/api/HttpExample?name=Functions に移動します。そこに、前と同じ "hello" メッセージが表示されている必要があります。 作成した HTTP トリガー関数は匿名承認を使用するため、コンテナーで実行されている関数は、アクセス キーを取得しなくても呼び出すことができます。 詳細については、承認キーに関するセクションを参照してください。

イメージがローカル コンテナーで開始したら、http://localhost:8080/api/HttpExample?name=Functions に移動します。そこに、前と同じ "hello" メッセージが表示されている必要があります。 作成した HTTP トリガー関数は匿名承認を使用するため、コンテナーで実行されている関数は、アクセス キーを取得しなくても呼び出すことができます。 詳細については、承認キーに関するセクションを参照してください。

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

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

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

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

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

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

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

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

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

以下のコマンドを使用してこれらの項目を作成します。 Azure CLI と PowerShell の両方がサポートされます。

  1. まだ Azure にサインインしていない場合は、Azure にサインインします。

    az login
    

    az login コマンドで Azure アカウントにサインインします。

  2. 選択したリージョンに AzureFunctionsContainers-rg という名前のリソース グループを作成します。

    az group create --name AzureFunctionsContainers-rg --location <REGION>
    

    az group create コマンドはリソース グループを作成します。 上記のコマンドで、<REGION> コマンドから返された使用可能なリージョン コードを使用して、<REGION> を自分の近くのリージョンに置き換えます。

  3. リソース グループとリージョン内に汎用ストレージ アカウントを作成します。

    az storage account create --name <STORAGE_NAME> --location <REGION> --resource-group AzureFunctionsContainers-rg --sku Standard_LRS
    

    az storage account create コマンドでストレージ アカウントを作成します。

    前の例の <STORAGE_NAME> は、適宜、Azure Storage 内で一意の名前に置き換えてください。 ストレージ名は 3 文字から 24 文字とし、小文字のみを使用する必要があります。 Standard_LRS は汎用アカウントを指定します。これは Functions でサポートされています。

  4. コマンドを使用して、Azure Functions 用の Premium プランを myPremiumPlan という名前で作成します。価格レベルは myPremiumPlan (--sku EP1) とし、<REGION> を指定し、作成先を Linux コンテナー (--is-linux) とします。

    az functionapp plan create --resource-group AzureFunctionsContainers-rg --name myPremiumPlan --location <REGION> --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 --name <APP_NAME> --storage-account <STORAGE_NAME> --resource-group AzureFunctionsContainers-rg --plan myPremiumPlan --deployment-container-image-name <DOCKER_ID>/azurefunctionsimage:v1.0.0
    

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

    注意

    カスタム コンテナー レジストリを使っている場合は、deployment-container-image-name パラメーターからレジストリ URL が参照されます。

    この例では、<STORAGE_NAME> を、前のセクションで使用したストレージ アカウントの名前に置き換えます。 また、<APP_NAME> は適宜グローバルに一意の名前に、<DOCKER_ID> は実際の Docker Hub アカウント ID に置き換えます。 カスタム コンテナー レジストリからデプロイしているときは、deployment-container-image-name パラメーターを使ってレジストリの URL を指定します。

    ヒント

    host.json ファイルで DisableColor の設定を使用して、ANSI 制御文字がコンテナー ログに書き込まれないようにすることができます。

  2. 次のコマンドを使用して、作成したストレージ アカウントの接続文字列を取得します。

    az storage account show-connection-string --resource-group AzureFunctionsContainers-rg --name <STORAGE_NAME> --query connectionString --output tsv
    

    ストレージ アカウントの接続文字列は、az storage account show-connection-string コマンドを使用することで返されます。

    <STORAGE_NAME> を、以前に作成したストレージ アカウントの名前に置き換えます。

  3. 次のコマンドを使用して、設定を関数アプリに追加します。

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

    az functionapp config appsettings set コマンドは設定を作成します。

    このコマンドで、<APP_NAME> を関数アプリの名前に、<CONNECTION_STRING> を前の手順の接続文字列に置き換えます。 接続は、DefaultEndpointProtocol= で始まる長いエンコードされた文字列である必要があります。

  4. 関数でこの接続文字列を使用してストレージ アカウントにアクセスできるようになりました。

Note

カスタム イメージをプライベート コンテナー レジストリに発行する場合は、DOCKER_REGISTRY_SERVER_USERNAME 変数と DOCKER_REGISTRY_SERVER_PASSWORD 変数も設定する必要があります。 詳細については、App Service 設定リファレンスの「カスタム コンテナー」を参照してください。

Azure 上で関数を確認する

イメージを Azure の関数アプリにデプロイしたら、関数を前のように HTTP 要求を通じて呼び出すことができます。 ブラウザーで、次の URL に移動します。

https://<APP_NAME>.azurewebsites.net/api/HttpExample?name=Functions

https://<APP_NAME>.azurewebsites.net/api/HttpExample?name=Functions

<APP_NAME> をお使いの関数アプリの名前に置き換えます。 この URL にアクセスすると、この関数をローカルで実行したときと同様の出力がブラウザーに表示されるはずです。

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

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

  1. 次のコマンドを使用して継続的デプロイを有効にし、Webhook URL を取得します。

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

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

    これまでと同様に、<APP_NAME> をお使いの関数アプリ名に置き換えます。

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

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

    Docker Hub ウィンドウで 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 Hub アカウント 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 Queue Storage に書き込む

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

このセクションでは、関数と Azure Queue 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 

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

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

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

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

バインド属性を宣言する方法は、Python プログラミング モデルによって異なります。

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

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

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

コレクションの 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"
  }
]

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

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

コレクションの 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 つ目のバインディングの名前は 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# プロジェクトでは、バインドは関数メソッドのバインド属性として定義されます。 具体的な定義は、お使いのアプリがインプロセス (C# クラス ライブラリ) で実行されるのか、分離ワーカー プロセスで実行されるのかによって異なります。

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

[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 プロジェクト ファイルを開きます。 メソッド定義に、次のパラメーターを追加します。

@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) {
    ...
}

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

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

次のコードに合わせて 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.Out 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(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(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 ドルの料金がかかります。

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

az group delete --name AzureFunctionsContainers-rg

次のステップ