Azure API Managementにバックエンドを接続する

最終更新日: 2019/12/24
Azure
API Management

ARM Templateを使って、API ManagementにAPIを追加する方法を説明します。 APIを追加し、バックエンドと接続するために、operations, policy, backendなどのリソースを使用します。 バックエンドはAzure functionsで実装されている状況を想定します。

API の定義

まず、API Management に API の定義を設定していきます。 API Management 上では個々の API はoperationsというリソースで扱われます。

Operations の定義

operations は一つの API に対応したリソースで、 API のパスやパラメータなどを定義することができます。 operations の ARM Template を見ていきましょう。

operations
{
  "name": "[concat(parameters('serviceName'), '/', parameters('apiName'), ';rev=', parameters('apiRevision'), '/', parameters('operationName'))]",
  "type": "Microsoft.ApiManagement/service/apis/operations",
  "apiVersion": "2018-01-01",
  "dependsOn": [
    "[resourceId('Microsoft.ApiManagement/service/apis', parameters('serviceName'), concat(parameters('apiName'), ';rev=', parameters('apiRevision')))]",
    "[resourceId('Microsoft.ApiManagement/service/backends', parameters('serviceName'), parameters('backendName'))]"
  ],
  "properties": {
    "description": "GET Resource",
    "templateParameters": [
      {
        "name": "resourceId",
        "type": "string",
        "values": []
      }
    ],
    "responses": [],
    "policies": null,
    "displayName": "GetResource",
    "method": "GET",
    "urlTemplate": "resources/{resourceId}"
  },
  "resources": []
}

前回同様に、特定のリビジョンとしてデプロイするので、名前にrev;=<apiRevision>がついている点に注意してください。 urlTemplateには url を指定します。可変なパラメータは{resourceId}のような形で表現します。なお、ここで指定したパラメータの詳細はtemplateParametersに指定します。 上の例では、resourceIdの type が string であることを定義しています。 methodには HTTP Method を指定します。

上記の例から作成される API は、GET http(s)://<api managementのfqdn>/api/v1/resources/{resourceId}となります。今回定義したのは/resources/{resourceId}の部分で、/api/v1の部分は前回のapisapiVersionSetsで定義しました。 なお、OpenAPI.yaml を import して API を定義することもできます。その方法については、別の記事で記載します。

Backends の定義

次に backends を定義します。

backends
{
  "name": "[concat(variables('serviceName'), '/', parameters('backendName'))]",
  "type": "Microsoft.ApiManagement/service/backends",
  "apiVersion": "2018-01-01",
  "dependsOn": [
    "[resourceId('Microsoft.ApiManagement/service/properties', parameters('serviceName'), parameters('propertyName'))]"
  ],
  "properties": {
    "title": null,
    "description": "Backend of API management",
    "url": "[concat('https://', parameters('functionsAppName'),'.azurewebsites.net/api')]",
    "protocol": "http",
    "credentials": {
      "query": {
        "code": ["[concat('{{', parameters('propertyName'), '}}')]"]
      }
    },
    "resourceId": "[concat('https://management.azure.com/subscriptions/', subscription().id, '/resourceGroups/', resourceGroup().name, '/providers/Microsoft.Web/sites/', parameters('functionsAppName'))]"
  }
}

backends は文字通り、API の実体となるバックエンドのサービスに相当するリソースです。今回はバックエンドとして Azure functions の HTTPTrigger を想定します。 urlには azure functions の url が指定され、resourceIdで azure functions のリソースを指定しています。この場合の前提として azure functions の HTTPTrigger の route はresources/{resourceId}になっている必要があります。

credentialsには azure functions と接続するための API Key を指定します。上記の template の例は、?code=というクエリパラメータの形式でクレデンシャルが指定されることを意味し、 propertyNameで指定される文字列が設定されます。credentials の値は{{<credential>}}というように、{}を 2 重に囲って指定する形式となります。 API Key のようなクレデンシャルは、後述のpropertiesというリソースに保存します。ここで指定したpropertyNameは、properties に保存した クレデンシャルを示す名前となります。

ちなみに、ここで azure functions の API Key を指定するためには、azure functions から Key を取得する必要があります。 ARM Template における Key の取得は Template 関数の listSecrets や listKeys を使うのが一般的ですが、azure functions の v2 に対して期待通りに動作しません1。Azure CLI 等を使って事前に取得しておくのがよいでしょう2

azure-cli
$resourceId = "/subscriptions/$subscriptionid/resourceGroups/$resourceGroup/providers/Microsoft.Web/sites/$functionsAppName"
$url = "$resourceId/host/default/listKeys?api-version=2016-03-01"
$ret = az rest --method post --uri $url | ConvertFrom-Json
$key = $ret.functionKeys.default

azure functions には host key と function key があり、この場合は host key を使っています。簡単に言うと、function key は関数毎の key で、host key は全ての関数に接続可能な key となります。 これで backends が定義されますが、API の backend として機能させるにはさらにこの backends を policy で指定してあげる必要があります。

policy の定義

backend と API Management を接続するために、最後に policy を設定します。 policy は API Management に様々な処理を追加することができる重要な機能です。 ここでは、その中の 1 つを使ってバックエンドとの接続を行います。

policy
{
  "variables": {
    "backendPolicy": "[concat('<policies>\r\n  <inbound>\r\n    <base />\r\n    <set-backend-service id=\"apim-generated-', 'policy','\" backend-id=\"', parameters('backendName'), '\" />\r\n  </inbound>\r\n  <backend>\r\n    <base />\r\n  </backend>\r\n  <outbound>\r\n    <base />\r\n  </outbound>\r\n  <on-error>\r\n    <base />\r\n  </on-error>\r\n</policies>')]"
  },
  "resources": [
    {
      "name": "[concat(parameters('serviceName'), '/', parameters('apiName'), ';rev=', parameters('apiRevision'), '/', 'policy')]",
      "type": "Microsoft.ApiManagement/service/apis/policies",
      "apiVersion": "2018-01-01",
      "properties": {
        "policyContent": "[variables('backendPolicy')]",
        "contentFormat": "xml"
      }
    }
  ]
}

policyContentに policy を xml で指定しています。backendPolicyvariablesで定義していて、xml がべた書きされているのがわかると思います。 ポイントはbackend-idで backend リソースを指定していることです。 これにより、先ほど作成した backend のリソースと、バックエンドポリシーが紐づくことになります。

properties の定義

propertiesは API Management で利用する設定値を key-value 形式で保存するためのリソースです。ここでは、API Management が backend の azure functions に接続するための API Key を保持するのに利用します。

properties
{
  "type": "Microsoft.ApiManagement/service/properties",
  "name": "[concat(parameters('serviceName'), '/', parameters('propertyName'))]",
  "apiVersion": "2018-06-01-preview",
  "scale": null,
  "properties": {
    "displayName": "[parameters('propertyName')]",
    "value": "[parameters('functionHostKey')]",
    "tags": ["key", "function"],
    "secret": true
  }
}

displayName に先ほど backends で指定したのと同じプロパティの名前を指定します。 value に値を指定します。この場合は、先ほど Azure CLI で取得した azure functions の API Key を指定します。 tagsは後でプロパティを検索するときなどに使用するタグです。任意の文字列を指定しておきましょう。 secretは true にしておくことで、プロパティが暗号化され安全に保持されます。

デプロイ

それでは、今まで説明したことを踏まえて、ARM Template をデプロイしていきます。

operations.json
{
  "$schema": "https://schema.management.azure.com/schemas/2018-05-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "serviceName": {
      "type": "string",
      "metadata": {
        "description": "Service Name"
      }
    },
    "apiName": {
      "type": "string",
      "metadata": {
        "description": "API Name"
      }
    },
    "apiRevision": {
      "type": "string",
      "metadata": {
        "description": "API Revision"
      }
    },
    "operationName": {
      "type": "string",
      "metadata": {
        "description": "Operation name"
      }
    },
    "backendName": {
      "type": "string",
      "metadata": {
        "description": "Backend Name"
      }
    },
    "functionHostKey": {
      "type": "securestring",
      "metadata": {
        "description": "Host key for the functions app"
      }
    },
    "functionAppName": {
      "type": "string",
      "metadata": {
        "description": "Functions App Name"
      }
    },
    "propertyName": {
      "type": "string",
      "metadata": {
        "description": "Property Name"
      }
    }
  },
  "variables": {
    "backendPolicy": "[concat('<policies>\r\n  <inbound>\r\n    <base />\r\n    <set-backend-service id=\"apim-generated-', 'policy','\" backend-id=\"', parameters('backendName'), '\" />\r\n  </inbound>\r\n  <backend>\r\n    <base />\r\n  </backend>\r\n  <outbound>\r\n    <base />\r\n  </outbound>\r\n  <on-error>\r\n    <base />\r\n  </on-error>\r\n</policies>')]"
  },
  "resources": [
    {
      "name": "[concat(parameters('serviceName'), '/', parameters('apiName'), ';rev=', parameters('apiRevision'), '/', parameters('operationName'))]",
      "type": "Microsoft.ApiManagement/service/apis/operations",
      "apiVersion": "2019-01-01",
      "dependsOn": [
        "[resourceId('Microsoft.ApiManagement/service/backends', parameters('serviceName'), parameters('backendName'))]"
      ],
      "properties": {
        "description": "GET Resource",
        "templateParameters": [
          {
            "name": "resourceId",
            "type": "string",
            "values": []
          }
        ],
        "responses": [],
        "policies": null,
        "displayName": "GetResource",
        "method": "GET",
        "urlTemplate": "resources/{resourceId}"
      },
      "resources": []
    },
    {
      "name": "[concat(parameters('serviceName'), '/', parameters('apiName'), ';rev=', parameters('apiRevision'), '/', 'policy')]",
      "type": "Microsoft.ApiManagement/service/apis/policies",
      "apiVersion": "2018-01-01",
      "properties": {
        "policyContent": "[variables('backendPolicy')]",
        "contentFormat": "xml"
      }
    },
    {
      "name": "[concat(parameters('serviceName'), '/', parameters('backendName'))]",
      "type": "Microsoft.ApiManagement/service/backends",
      "apiVersion": "2018-01-01",
      "dependsOn": [
        "[resourceId('Microsoft.ApiManagement/service/properties', parameters('serviceName'), parameters('propertyName'))]"
      ],
      "properties": {
        "title": null,
        "description": "Backend of API management",
        "url": "[concat('https://', parameters('functionAppName'),'.azurewebsites.net/api')]",
        "protocol": "http",
        "credentials": {
          "query": {
            "code": ["[concat('{{', parameters('propertyName'), '}}')]"]
          }
        },
        "resourceId": "[concat('https://management.azure.com/subscriptions/', subscription().id, '/resourceGroups/', resourceGroup().name, '/providers/Microsoft.Web/sites/', parameters('functionAppName'))]"
      }
    },
    {
      "type": "Microsoft.ApiManagement/service/properties",
      "name": "[concat(parameters('serviceName'), '/', parameters('propertyName'))]",
      "apiVersion": "2018-06-01-preview",
      "scale": null,
      "properties": {
        "displayName": "[parameters('propertyName')]",
        "value": "[parameters('functionHostKey')]",
        "tags": ["key", "function"],
        "secret": true
      }
    }
  ]
}

前回までに作られた サービスインスタンス に対して、上記の template を使って API をデプロイすることができました。 これで API が動くようになりましたね。

2019 Copyright Channel.241