ARM Templateを使って、API ManagementにAPIを追加する方法を説明します。 APIを追加し、バックエンドと接続するために、operations, policy, backendなどのリソースを使用します。 バックエンドはAzure functionsで実装されている状況を想定します。
まず、API Management に API の定義を設定していきます。
API Management 上では個々の API はoperations
というリソースで扱われます。
operations は一つの API に対応したリソースで、 API のパスやパラメータなどを定義することができます。 operations の ARM Template を見ていきましょう。
{
"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
の部分は前回のapis
やapiVersionSets
で定義しました。
なお、OpenAPI.yaml を import して API を定義することもできます。その方法については、別の記事で記載します。
次に 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。
$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 で指定してあげる必要があります。
backend と API Management を接続するために、最後に policy を設定します。 policy は API Management に様々な処理を追加することができる重要な機能です。 ここでは、その中の 1 つを使ってバックエンドとの接続を行います。
{
"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 で指定しています。backendPolicy
はvariables
で定義していて、xml がべた書きされているのがわかると思います。
ポイントはbackend-id
で backend リソースを指定していることです。
これにより、先ほど作成した backend のリソースと、バックエンドポリシーが紐づくことになります。
properties
は API Management で利用する設定値を key-value 形式で保存するためのリソースです。ここでは、API Management が backend の azure functions に接続するための API Key を保持するのに利用します。
{
"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 をデプロイしていきます。
{
"$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 が動くようになりましたね。