Azure Application Insights – behind the scenes

This post explains the Application Insights using API and SDK. By seeing with APIs, it helps you to understand what you can do or cannot do with Application Insights, and how to leverage the Application Insights in your applications.

Azure Application Insights collects the application events (exceptions, requests, page views, etc), and you can view or analyze that data.
If you are familiar with the event tracing in Windows, you will find that Application Insights is the similar one for the cloud-based platform. But, unlike Windows event tracing, it’s open and cross-platform (not only for Windows or .NET), and you can leverage, even if it’s on the on-premise or other cloud platforms. (Please see “Application Insights – Developer analytics: languages, platforms, and integrations“.)

Note : Windows event log (including system log) and performance data itself on Azure VM can also be sent to Application Insights, and you can see these data in one view. (In most cases, these telemetry is sent to the Azure Table by Azure diagnostics extensions.)

In general you can use the Application Insights with no additional code and programming by your own (the SDK does all), but here we use the Application Insights by custom code and see how it works behind the scenes for your understanding.

Before starting, please create the Application Insights resource in Azure Portal. (select [+New] – [Developer tools] – [Application Insights] in the portal)

Sending telemetry

First we send the simple custom event by a bit of programming code with Application Insights SDK.
Before using SDK, please get the instrumentation key in Azure Portal. (You need this key for sending the telemetry.)

Here we create the console application using C#, and we use only the Application Insights core library (Microsoft.ApplicationInsights.dll). Then you must install only “Microsoft.ApplicationInsights” nuget package in your console application.

First we create the following code. Please use your own api key as the following instrumentation key.
By this code, the telemetry data is sent to dc.services.visualstudio.com (using the json format) and stored.

...
using Microsoft.ApplicationInsights;
using Microsoft.ApplicationInsights.Extensibility;
...

static void Main(string[] args)
{
  TelemetryConfiguration.Active.InstrumentationKey =
    "0a3090fc-...";  // you must set your own instrumentation key
  TelemetryConfiguration.Active.TelemetryChannel.DeveloperMode
    = true;  // see the following note
  TelemetryClient cl = new TelemetryClient();

  cl.TrackEvent("TestEvent01");

  cl.Flush();

  Console.WriteLine("Done");
  Console.ReadLine();
}

Note : If you set true as DeveloperMode (see above), you can see the sending telemetry in the debugging output window in IDE. (You should run the code as debugging.)

Note : Your app can also instrument at run time without SDK. (Here I don’t describe about this settings and steps.)

Application Insights SDK is doing several tasks internally and encapsulates the difficulties.

For example, the telemetry sending is done with low overheads using non-blocking and threading.
In this Windows console app, the telemetry is sent by the Windows event tracing mechanism (ETW), i.e, by the event source and the event listener. Eventually, by non-blocking and threading of this mechanism, the overheads of your application’s performance makes very low.
For this reason, it’s not recommended that you directly call the rest endpoint for sending the telemetry, and it’s better to use the Application Insights SDK for sending the telemetry.
The server also responds soon, and the subsequent steps will be done in the server side by asynchronously after the acceptance.

Here we’re now programming with .NET class of Application Insights and manually provisioning. But, in the most cases, this task will be done by some sort of the injection mechanism.
For the .NET Fx applications (including this application), if ApplicationInsights.config file exists in your project, all these instances (assemblies) are dynamically created from the ApplicationInsights.config when TelemetryConfiguration.Active method is called. Moreover this method (TelemetryConfiguration.Active) is caused by the custom http module in the web.config. That is, the config settings does these all tasks, and no need to create the code by your own. (The SDK automatically injects the config settings. You can also customize the default configuration by editing ApplicationInsights.config or Global.asax.)

Getting the collected data (Events)

After you send the telemetry, you can see and search (or analyze) the collected data in Azure Portal or Visual Studio. (It takes several minutes to appear. Sorry, but be patient !)
Using Visual Studio, you can also check the several advanced metrics on Application Insights with load testing.

Like existing Azure Monitor REST API, you can also access  Application Insights data using your custom code, and in this post, we see the collected data by this rest api called Application Insights REST API without UI. (Note that this endpoint is not for sending telemetry which I previously explained. This is for getting events and metrics.)

Before using this REST API, first you must get the application id (app id) and create the api key in Azure Portal. (Note that this is not the previous instrumentation key.)

Later I show you the rest api’s raw data, but using following api explorer you don’t need to create your code and check the results soon. (You just set the application id and api key in this api explorer, and done !)

Application Insights REST API Explorer
https://dev.applicationinsights.io/apiexplorer/

 

For example, next is retrieving the events in Application Insights resource. Note that the data is returned for last 12 hours by default. (Later I show you how to retrieve more data in the past.)

GET https://api.applicationinsights.io/beta/apps/{app id}/events/
Accept: application/json
X-API-Key: {api key}
HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Content-Type: application/json; charset=utf-8; ieee754compatible=false; odata.metadata=none; odata.streaming=false
OData-Version: 4.0;

{
  "@odata.context": "https://api.applicationinsights.io/beta/apps/{app id}/events/$metadata#Collection(Edm.EntityType)",
  "@ai.messages": [
    {
      "code": "AddedLimitToQuery",
      "message": "The query was limited to 500 rows"
    }
  ],
  "value": [
    {
      "id": "eb54a650-b0a3-11e6-99bb-39d55d240b8a",
      "type": "customEvent",
      "count": 1,
      "timestamp": "2016-11-22T11:07:54.770Z",
      "customDimensions": {
        "DeveloperMode": "true"
      },
      "customMeasurements": null,
      "operation": {
        "name": null,
        "id": "",
        "parentId": "",
        "syntheticSource": null
      },
      "session": {
        "id": null
      },
      "user": {
        "id": null,
        "accountId": null,
        "authenticatedId": null
      },
      "cloud": {
        "roleName": null,
        "roleInstance": null
      },
      "ai": {
        "iKey": "0a3090fc-...",
        "appName": "testinsights01",
        "appId": "{app id}",
        "sdkVersion": "dotnet: 2.1.0.26048"
      },
      "customEvent": {
        "name": "TestEvent01"
      },
      "application": {
        "version": null
      },
      "client": {
        "model": null,
        "os": null,
        "type": "PC",
        "browser": null,
        "ip": "167.220.232.0",
        "city": null,
        "stateOrProvince": null,
        "countryOrRegion": "Japan"
      }
    },
    ...

  ]
}

All built-in events (customEvents, exceptions, traces, requests, pageViews, dependencies, etc) inherits this “event”, and you can narrow by the event type like /events/customEvents as follows.

GET https://api.applicationinsights.io/beta/apps/{app id}/events/customEvents
Accept: application/json
X-API-Key: {api key}

You can use the OData expression. ($filter, $orderBy, $search, $apply, $top, $skip and $format is supported.)
For instance, if you want to get more data in the past, you can specify the timestamp condition as follows. The following example retrieves data for last 5 days.
(“timestamp%20gt%20now()%20sub%20duration%27P5D%27” is the url-encoded string of “timestamp gt now() sub duration'P5D'“.)

GET https://api.applicationinsights.io/beta/apps/{app id}/events/$all?$filter=timestamp%20gt%20now()%20sub%20duration%27P5D%27
Accept: application/json
X-API-Key: {api key}

Note : The data has its retention (up to 90 days), and if you want to keep the data, please use the continuous export. (see “Data collection, retention and storage in Application Insights” for details.) You cannot also delete the data on purpose. (The retention is automatically determined by the Application Insights.)

Now let’s say your application is adding some custom properties as follows.

static void Main(string[] args)
{
  TelemetryConfiguration.Active.InstrumentationKey = "0a3090fc-...";
  TelemetryConfiguration.Active.TelemetryChannel.DeveloperMode = true;
  TelemetryClient cl = new TelemetryClient();

  var props = new Dictionary{
    { "importance", "high"},
    { "difficulty", "C"}};
  cl.TrackEvent("TestEvent01", props);

  cl.Flush();

  Console.WriteLine("Done");
  Console.ReadLine();
}

Then you can also filter using this custom properties. See the following example.

GET https://api.applicationinsights.io/beta/apps/{app id}/events/customEvents?$filter=customDimensions%2Fimportance%20eq%20%27high%27
Accept: application/json
X-API-Key: {api key}
HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Content-Type: application/json; charset=utf-8; ieee754compatible=false; odata.metadata=none; odata.streaming=false
OData-Version: 4.0;

{
  "@odata.context": "https://api.applicationinsights.io/beta/apps/{app id}/events/$metadata#customEvents",
  "@ai.messages": [
    {
      "code": "AddedLimitToQuery",
      "message": "The query was limited to last 12 hours, since no other limit for timestamp field was specified"
    },
    {
      "code": "AddedLimitToQuery",
      "message": "The query was limited to 500 rows"
    }
  ],
  "value": [
    {
      "id": "2f312141-b2c1-11e6-a5fd-f381db5f52d8",
      "type": "customEvent",
      "count": 1,
      "timestamp": "2016-11-25T03:42:22.938Z",
      "customDimensions": {
        "DeveloperMode": "true",
        "difficulty": "C",
        "importance": "high"
      },
      "customMeasurements": null,
      "operation": {
        "name": null,
        "id": null,
        "parentId": null,
        "syntheticSource": null
      },
      "session": {
        "id": null
      },
      "user": {
        "id": null,
        "accountId": null,
        "authenticatedId": null
      },
      "cloud": {
        "roleName": null,
        "roleInstance": null
      },
      "ai": {
        "iKey": "0a3090fc-...",
        "appName": "testinsights01",
        "appId": "{app id}",
        "sdkVersion": "dotnet: 2.1.0.26048"
      },
      "customEvent": {
        "name": "TestEvent01"
      },
      "application": {
        "version": null
      },
      "client": {
        "model": null,
        "os": null,
        "type": "PC",
        "browser": null,
        "ip": "167.220.232.0",
        "city": null,
        "stateOrProvince": null,
        "countryOrRegion": "Japan"
      }
    }
  ]
}

Metrics

Let’s see about metrics.

First you can check what kind of metrics is supported for each data types using the metadata endpoint.
The following is the returned result of the metadata. This shows a list of supported metrics in Application Insights.
It’s very long result, and here I’m cutting the latter parts.

GET https://api.applicationinsights.io/beta/apps/{app id}/metrics/metadata
Accept: application/json
X-API-Key: {api key}
{
  "metrics": {
    "requests/count": {
      "supportedAggregations": [
        "sum"
      ],
      "displayName": "Server requests",
      "supportedGroupBy": {
        "all": [
          "request/name",
          "request/urlPath",
          "request/urlHost",
          "request/success",
          "request/resultCode",
          "request/performanceBucket",
          "operation/name",
          "operation/synthetic",
          "user/authenticated",
          "application/version",
          "client/type",
          "client/model",
          "client/os",
          "client/city",
          "client/stateOrProvince",
          "client/countryOrRegion",
          "client/browser",
          "cloud/roleName",
          "cloud/roleInstance"
        ]
      },
      "defaultAggregation": "sum"
    },
    "requests/duration": {
      "supportedAggregations": [
        "avg",
        "min",
        "max",
        "sum",
        "count"
      ],
      "displayName": "Server response time",
      "units": "ms",
      "supportedGroupBy": {
        "all": [
          "request/name",
          "request/urlPath",
          "request/urlHost",
          "request/success",
          "request/resultCode",
          "request/performanceBucket",
          "operation/name",
          "operation/synthetic",
          "user/authenticated",
          "application/version",
          "client/type",
          "client/model",
          "client/os",
          "client/city",
          "client/stateOrProvince",
          "client/countryOrRegion",
          "client/browser",
          "cloud/roleName",
          "cloud/roleInstance"
        ]
      },
      "defaultAggregation": "avg"
    },
    "requests/failed": {
      ...

    },
    "pageViews/count": {
      ...

    },
    "pageViews/duration": {
      ...

    },
    "customEvents/count": {
      ...

    },
    "dependencies/count": {
      ...

    },
    "dependencies/failed": {
      ...

    },
    "dependencies/duration": {
      ...

    },
    "exceptions/count": {
      ...

    },
    "exceptions/browser": {
      ...

    },
    "exceptions/server": {
      ...

    },
    "performanceCounters/requestExecutionTime": {
      ...

    },
    "performanceCounters/requestsPerSecond": {
      ...

    },
    "performanceCounters/requestsInQueue": {
      ...

    },
    "performanceCounters/memoryAvailableBytes": {
      ...

    },
    "performanceCounters/exceptionsPerSecond": {
      ...

    },
    "performanceCounters/processCpuPercentage": {
      ...

    },
    "performanceCounters/processIOBytesPerSecond": {
      ...

    },
    "performanceCounters/processPrivateBytes": {
      ...

    },
    "performanceCounters/processorCpuPercentage": {
      ...

    },
    "traces/count": {
      ...

    },
    ...

  },
  "dimensions": {
    ...

  }
}

Note : “Dependency” is the tracking telemetry of the calls between your application components (a HTTP service, a database, or a file system). Using this data, Application Insights service in Azure Portal can display the map (diagram) of your application components.

Now I show you the example of “requests/duration” metric.
The “duration” is the time span between start time and end time, and the telemetry like “request” or “pageView” is having this property.

Let’s say we send the “request” telemetry as follows. This code is sending the telemetry periodically (each seconds), and it’s having the various duration values between 5 seconds (5,000 millisec) to 15 seconds (15,000 millisec).

Note : As I described before, this is usually done by the http module injected by the Application Insights SDK when using ASP.NET. (The programmers don’t have to write this code in most cases.)

...
using Microsoft.ApplicationInsights.DataContracts;
...

static void Main(string[] args)
{
  TelemetryConfiguration.Active.InstrumentationKey = "0a3090fc-...";
  TelemetryConfiguration.Active.TelemetryChannel.DeveloperMode = true;
  TelemetryClient cl = new TelemetryClient();

  // Sending Request telemetry
  int lag = 0;
  while(true)
  {
    RequestTelemetry telemetry = new RequestTelemetry(
      "test-request",  // request name
      DateTimeOffset.Now,  // start time
      new TimeSpan(0, 0, 5 + lag),  // duration
      "OK",  // status
      true);  // success or failure

    // You can also write as follows
    //RequestTelemetry telemetry = new RequestTelemetry();
    //telemetry.Id = Guid.NewGuid().ToString();
    //telemetry.Name = "test-request";
    //telemetry.Start();
    //System.Threading.Thread.Sleep((5 + lag) * 1000);
    //telemetry.Stop();

    cl.TrackRequest(telemetry);

    System.Threading.Thread.Sleep(1000);
    lag = (lag == 10 ? 0 : lag + 1);
  }
}

As you can see in the previous metadata, “requests/duration” supports the aggregation operations of “avg” (average), “min” (minimum), “max” (maximum), “sum”, and “count”.
The following is requesting the metrics of minimum, maximum, and average duration from 2016-11-28T04:55:00Z till 2016-11-28T04:56:00Z.
As you can guess, the result of minimum duration is 5,000 millisec, the maximun duration is 15,000, and the average duration is around 10,000.

GET https://api.applicationinsights.io/beta/apps/{app id}/metrics/requests/duration?timespan=2016-11-28T04%3A55%3A00Z%2F2016-11-28T04%3A56%3A00Z&aggregation=min%2Cmax%2Cavg
Accept: application/json
X-API-Key: {api key}
HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Content-Type: application/json; charset=utf-8

{
  "value": {
    "start": "2016-11-28T04:55:00.000Z",
    "end": "2016-11-28T04:56:00.000Z",
    "requests/duration": {
      "min": 5000,
      "max": 15000,
      "avg": 9830.51
    }
  }
}

You can also request using the following format.

// Requesting the result from 2016-11-28T05:00:00Z for 1 hour (from 05:00:00 to 06:00:00)

GET https://api.applicationinsights.io/beta/apps/{app id}/metrics/requests/duration?timespan=2016-11-28T05%3A00%3A00Z%2FPT1H&aggregation=min%2Cmax%2Cavg
// Requesting the result past 1 hour

GET https://api.applicationinsights.io/beta/apps/{app id}/metrics/requests/duration?timespan=PT1H&aggregation=min%2Cmax%2Cavg

If you skip the parameters, the default value is used.
For instance, the following is requesting the average (avg) value past 12 hours. Because the “defaultAggregation” in the metadata is “avg”.

GET https://api.applicationinsights.io/beta/apps/{app id}/metrics/requests/duration
Accept: application/json
X-API-Key: {api key}
HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Content-Type: application/json; charset=utf-8

{
  "value": {
    "start": "2016-11-27T18:00:01.002Z",
    "end": "2016-11-28T06:00:01.002Z",
    "requests/duration": {
      "avg": 10000.88
    }
  }
}

Using “interval” property, you can see the transition of the aggregated metrics in the specific timeline.
For instance, the following returns the results of each one minute from 2016-11-28T05:00:00Z till 2016-11-28T06:00:00Z (one hour long). This returns 60 elements of data.

GET https://api.applicationinsights.io/beta/apps/{app id}/metrics/requests/duration?timespan=2016-11-28T05%3A00%3A00Z%2F2016-11-28T06%3A00%3A00Z&interval=PT1M&aggregation=min%2Cmax%2Cavg
Accept: application/json
X-API-Key: {api key}
HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Content-Type: application/json; charset=utf-8

{
  "value": {
    "start": "2016-11-28T05:00:00.000Z",
    "end": "2016-11-28T06:00:00.000Z",
    "interval": "PT1M",
    "segments": [
      {
        "start": "2016-11-28T05:00:00.000Z",
        "end": "2016-11-28T05:01:00.000Z",
        "requests/duration": {
          "min": 5000,
          "max": 15000,
          "avg": 9881.36
        }
      },
      {
        "start": "2016-11-28T05:01:00.000Z",
        "end": "2016-11-28T05:02:00.000Z",
        "requests/duration": {
          "min": 5000,
          "max": 15000,
          "avg": 9966.1
        }
      },
      {
        "start": "2016-11-28T05:02:00.000Z",
        "end": "2016-11-28T05:03:00.000Z",
        "requests/duration": {
          "min": 5000,
          "max": 15000,
          "avg": 10237.29
        }
      },
      ...

    ]
  }
}

Using this way, you could draw the transition graphs in your application like same as the graphs in Azure Portal.

Using Application Insights in Azure Portal, you can also set the webhook for some threshold of metrics.

Query

You can use the advanced analytics query for the telemetry data. Let’s see the simple example.

Now here we send the “pageView” telemetry as follows.

static void Main(string[] args)
{
  TelemetryConfiguration.Active.InstrumentationKey = "0a3090fc-...";
  TelemetryConfiguration.Active.TelemetryChannel.DeveloperMode = true;
  TelemetryClient cl = new TelemetryClient();

  // Sending Page View telemetry
  Random rnd = new Random();
  while (true)
  {
    cl.TrackPageView($"test{rnd.Next(10)}.htm");
    System.Threading.Thread.Sleep(1000);
  }
}

Let me show the top 3 pages of the most page views using the analytics query. This time, first we see the UI in the analytics portal.

Please click [Analytics] in the Application Insights portal (Azure Portal), and the analytics portal window is opened. Just you input the following query in the query editor on the analytics portal, and press [Go].

pageViews
| where timestamp >= ago(1d)
| order by timestamp desc
| summarize count() by name
| top 3 by count_ desc

You can get the query results in the analytics portal like the following screenshot.

Note : This is just the very simple example, and of course, you can use more advanced query. Please see “Application Insights : Reference for Analytics” for more analytics query functions and expressions.

Same as this analytics portal, you can also use the Apllication Insights REST API as follows.
Here “pageViews%20%7C%20where%20timestamp%20%3E%3D%20ago(1d)%20%7C%20order%20by%20timestamp%20desc%20%7C%20summarize%20count()%20by%20name%20%7C%20top%203%20by%20count_%20desc” is the url-encoded string of “pageViews | where timestamp >= ago(1d) | order by timestamp desc | summarize count() by name | top 3 by count_ desc“.

GET https://api.applicationinsights.io/beta/apps/{app id}/query?query=pageViews%20%7C%20where%20timestamp%20%3E%3D%20ago(1d)%20%7C%20order%20by%20timestamp%20desc%20%7C%20summarize%20count()%20by%20name%20%7C%20top%203%20by%20count_%20desc
Accept: application/json
X-API-Key: {api key}
HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Content-Type: application/json; charset=utf-8

{
  "Tables": [
    {
      "TableName": "Table_0",
      "Columns": [
        {
          "ColumnName": "name",
          "DataType": "String",
          "ColumnType": "string"
        },
        {
          "ColumnName": "count_",
          "DataType": "Int64",
          "ColumnType": "long"
        }
      ],
      "Rows": [
        [
          "test0.htm",
          91
        ],
        [
          "test3.htm",
          84
        ],
        [
          "test1.htm",
          83
        ]
      ]
    },
    {
      "TableName": "Table_1",
      "Columns": [
        {
          "ColumnName": "Value",
          "DataType": "String",
          "ColumnType": "string"
        }
      ],
      "Rows": [
        [
          "{"Visualization":"table","Title":"","Accumulate":false,"IsQuerySorted":true,"Annotation":"","By":null}"
        ]
      ]
    },
    {
      "TableName": "Table_2",
      "Columns": [
        {
          "ColumnName": "Timestamp",
          "DataType": "DateTime",
          "ColumnType": "datetime"
        },
        {
          "ColumnName": "Severity",
          "DataType": "Int32",
          "ColumnType": "int"
        },
        {
          "ColumnName": "SeverityName",
          "DataType": "String",
          "ColumnType": "string"
        },
        {
          "ColumnName": "StatusCode",
          "DataType": "Int32",
          "ColumnType": "int"
        },
        {
          "ColumnName": "StatusDescription",
          "DataType": "String",
          "ColumnType": "string"
        },
        {
          "ColumnName": "Count",
          "DataType": "Int32",
          "ColumnType": "int"
        },
        {
          "ColumnName": "RequestId",
          "DataType": "Guid",
          "ColumnType": "guid"
        },
        {
          "ColumnName": "ActivityId",
          "DataType": "Guid",
          "ColumnType": "guid"
        },
        {
          "ColumnName": "SubActivityId",
          "DataType": "Guid",
          "ColumnType": "guid"
        },
        {
          "ColumnName": "ClientActivityId",
          "DataType": "String",
          "ColumnType": "string"
        }
      ],
      "Rows": [
        [
          "2016-11-28T08:05:12.6937983Z",
          4,
          "Info",
          0,
          "Query completed successfully",
          1,
          "5d542224-9509-41f3-b1ea-f5f471fbb4ea",
          "5d542224-9509-41f3-b1ea-f5f471fbb4ea",
          "a264b37b-e980-4e49-9e21-7adc847099b1",
          "907ae68b-904d-4fcf-997f-c7e7efc39335"
        ],
        [
          "2016-11-28T08:05:12.6937983Z",
          6,
          "Stats",
          0,
          "{"ExecutionTime":0.0312509,"resource_usage":{"cache":{"memory":{"hits":388,"misses":0,"total":388},"disk":{"hits":0,"misses":0,"total":0}},"cpu":{"user":"00:00:00.0937500","kernel":"00:00:00.0468750","total cpu":"00:00:00.1406250"},"memory":{"peak_per_node":184550432}}}",
          1,
          "5d542224-9509-41f3-b1ea-f5f471fbb4ea",
          "5d542224-9509-41f3-b1ea-f5f471fbb4ea",
          "a264b37b-e980-4e49-9e21-7adc847099b1",
          "907ae68b-904d-4fcf-997f-c7e7efc39335"
        ]
      ]
    },
    {
      "TableName": "Table_3",
      "Columns": [
        {
          "ColumnName": "Ordinal",
          "DataType": "Int64",
          "ColumnType": "long"
        },
        {
          "ColumnName": "Kind",
          "DataType": "String",
          "ColumnType": "string"
        },
        {
          "ColumnName": "Name",
          "DataType": "String",
          "ColumnType": "string"
        },
        {
          "ColumnName": "Id",
          "DataType": "String",
          "ColumnType": "string"
        }
      ],
      "Rows": [
        [
          0,
          "QueryResult",
          "PrimaryResult",
          "edbe4563-29ab-49e1-9da4-948fbd274f1e"
        ],
        [
          1,
          "QueryResult",
          "@ExtendedProperties",
          "be0bf081-3a09-4767-9c7c-7f02209ec8ef"
        ],
        [
          2,
          "QueryStatus",
          "QueryStatus",
          "00000000-0000-0000-0000-000000000000"
        ]
      ]
    }
  ]
}

 

Here I showed the overall concepts of Application Insights using SDK and API. For more about the SDK or API, please refer the following official document.

Application Insights API for custom events and metrics
https://docs.microsoft.com/en-us/azure/application-insights/app-insights-api-custom-events-metrics

Application Insights REST API documentation
https://dev.applicationinsights.io/documentation/overview

 

Advertisements

Microsoft Bot Framework 1.0 の HTTP Flow (REST)

2016/08 : This is an old post (which describes about version 1). Please see “BUILD BOT with Microsoft Bot Framework Rest Api” (which describes about version 3) as new one.
この投稿は古い情報です。最新の Microsoft Bot Framework Rest (version 3) については「BUILD BOT with Microsoft Bot Framework Rest Api」を参照してください。

こんにちは。

Microsoft Bot Framework や Skype Bot Platform について、「.NET と NodeJS しか SDK がないので不便」といった声をよく聞きます。(実は今日もチーム ミーティングでそんな話になりました。エバンジェリスト チームの会議では、そんな話をしています。。。)
そこで今回は、Microsoft Bot Framework の内部で呼ばれている HTTP Flow を紹介します。

ここで記載する内容を理解することで、SDK (C#, Node.js など) がサポートしていないさまざまな言語 (PHP, Python, Java など) で構築できるので、これまで手がつけにくいと思っていた方も、是非いろいろと遊んでみてください。

なお、今回は Microsoft Bot Framework のみ紹介しますが、Skype Bot Platform も同様に HTTP (REST) がベースとなっています。Skype Bot Platform も、いずれ Public になった際に紹介したいと思います。(現在、Skype Bot Platform は Insider Preview の位置づけです。Skype Bot Platform では、Basic 認証ではなく OAuth token、送受信のパターンも異なるなど、方式が異なっています。)

Authentication

Microsoft Bot Framework に Bot を登録する際、下図の通り、App Id とパスワードに相当する App Secret を設定・取得したはずです。
皆さんが作成する Bot と Microsoft Bot Framework の間は、この App Id と App Secret を使用した Basic 認証 (Basic Authentication) を使って保護します。

ユーザーが Bot を呼び出した際は、Microsoft Bot Framework が Basic 認証を使って App Id と Secret を Bot に渡します。Bot (皆さんが作成するアプリ) 側は受け取った App Id と Secret を確認し、正しければ処理をおこないます。(もし正しくなければ HTTP Status の Forbidden (403) を返してください。)
また、Bot がトリガーとなってユーザーを呼び出す場合は、Bot が Microsoft Bot Framework に (Basic 認証を使って) App Id と Secret を渡し、Microsoft Bot Framework はこの渡された値を確認します。

HTTP における Basic 認証の利用方法は簡単です。{App Id}:{Secret} の形式の文字列 (例えば、tsmatsuzsamplebot01:d340e614fd...) を Base64 Encode した文字列を HTTP Header に設定するだけです。例えば、下記のような形です。(受信側は、この dHNtYXRzdX... を Decode することで App Id と Secret を取り出し、その内容を確認します。)

POST https://{your bot endpoint} HTTP/1.1
Authorization: Basic dHNtYXRzdX...
Content-Type: application/json; charset=utf-8
. . .

いずれ紹介しますが、例えば、Skype Bot Platform の場合は Azure AD v2 endpoint が使用されているなど、一般に、Bot Platform ごとに異なる認証方法が使用されています。Microsoft Bot Framework では、こうした Channel ごとの差異を吸収し、Channel に応じた方式に変換して処理します。(Channel の初期設定時を除き、開発者がこの差異を意識する必要はありません。)

Request-Reply Conversation (Inbound)

天気を問い合わせたり、レストランの検索を依頼するなど、多くは利用者 (User) が Bot に尋ねて、Bot がこれに応答します。まずは、こうした Request-Reply 方式 (Bot に Inbound に Request が入ってくる方式) の場合の REST (Raw HTTP) を紹介します。

まず、利用者 (User) が Bot に Text Message を送信すると、Framework は Bot に以下の HTTP Request を渡します。
ここでは、”Hi, Matsu!” という Text Message が渡されています。

上述の通り、Authorization ヘッダーの値については、Base64 Decode により内容を検証 (確認) してください。

HTTP Request by Framework

POST https://{your bot endpoint} HTTP/1.1
Authorization: Basic dHNtYXRzdX...
Content-Type: application/json; charset=utf-8

{
  "type": "Message",
  "id": "9ir4EFUT5qi",
  "conversationId": "7CgDU8BuME645xD7qLAPzGlQETd2Pa5h7EfAqh9R71hPCV2",
  "created": "2016-05-19T04:07:18.3777251Z",
  "text": "Hi, Matsu!",
  "attachments": [],
  "from": {
    "name": "devportal",
    "channelId": "test",
    "address": "devportal",
    "id": "JMQ0KLCKN6R",
    "isBot": false
  },
  "to": {
    "name": "Tsmatsuz Sample Bot01",
    "channelId": "test",
    "address": "tsmatsuzsamplebot01",
    "id": "tsmatsuzsamplebot01",
    "isBot": true
  },
  "participants": [
    {
      "name": "devportal",
      "channelId": "test",
      "address": "devportal",
      "id": "JMQ0KLCKN6R",
      "isBot": false
    },
    {
      "name": "Tsmatsuz Sample Bot01",
      "channelId": "test",
      "address": "tsmatsuzsamplebot01",
      "id": "tsmatsuzsamplebot01",
      "isBot": true
    }
  ],
  "totalParticipants": 2,
  "mentions": [],
  "channelConversationId": "tsmatsuzsamplebot01",
  "hashtags": []
}

Bot では、利用者 (User) への Reply Message を、下記の通り HTTP Response を使って返します。(今回は、”Fine !” と返しています。)
上記の HTTP Request の通り、受信する各 Message には id (上記の 9ir4EFUT5qi) が付与されていますが、Response ではこの Id を replyToMessageId に設定します。(Bot Framework 側で Message とその応答の対応をおこなう際に、この id を使用します。)
なお、この Message Id は Message が送信されるたびに変更されますが、conversationId は利用者 (User) との一連のスレッドを識別する Id で、一連の会話の中で一意です。(プログラミングする Bot の中で、こうした Id を使ってスレッドなどを識別してください。)

HTTP Response by Bot

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{
  "conversationId": "7CgDU8BuME645xD7qLAPzGlQETd2Pa5h7EfAqh9R71hPCV2",
  "text": "Fine !",
  "from": {
    "name": "Tsmatsuz Sample Bot01",
    "channelId": "test",
    "address": "tsmatsuzsamplebot01",
    "isBot": true
  },
  "to": {
    "name": "devportal",
    "channelId": "test",
    "address": "devportal",
    "isBot": false
  },
  "replyToMessageId": "9ir4EFUT5qi",
  "participants": [
    {
      "name": "devportal",
      "channelId": "test",
      "address": "devportal"
    },
    {
      "name": "Tsmatsuz Sample Bot01",
      "channelId": "test",
      "address": "tsmatsuzsamplebot01"
    }
  ],
  "totalParticipants": 2,
  "channelConversationId": "tsmatsuzsamplebot01"
}

上記の channelId は使用されている Channel (skype, slack, email などの区別) を表していて、この Channel に応じて Bot の address も変わるので注意してください。
例えば、 Skype (Skype Bot Platform) の Channel を使用する場合、address には Skype の Bot ID (下図) を指定します。

補足 : なお、上記の channelId の test は、Bot Framework Developer Portal の Web Test Client の場合の値です。

Channel の判別をおこなって、例えば、Skype の場合には絵文字 (emoticon) を返すなど、Channel ごとの独自な応答も可能です。
下記は、絵文字を返す Skype の場合の REST のサンプル (HTTP Requset, HTTP Response) です。

HTTP Request by Framework (Skype の場合)

POST https://{your bot endpoint} HTTP/1.1
Authorization: Basic dHNtYXRzdX...
Content-Type: application/json; charset=utf-8

{
  "type": "Message",
  "id": "4oij16x17tf",
  "conversationId": "1MC2mc1qR7up7bTG2w2by2v3Cay1CbFEGEQlFYQ1xX6Zc6x1",
  "created": "2016-05-19T06:11:15.965Z",
  "text": "Smile !",
  "attachments": [],
  "from": {
    "name": "8:live:tsuyoshi.matsuzaki",
    "channelId": "skype",
    "address": "8:live:tsuyoshi.matsuzaki",
    "id": "949dPd8X5wF",
    "isBot": false
  },
  "to": {
    "name": "tsmatsuzsamplebot01",
    "channelId": "skype",
    "address": "fc4a62b9-55de-49d8-b5ee-89a3235b6c7a",
    "id": "tsmatsuzsamplebot01",
    "isBot": true
  },
  "participants": [
    {
      "name": "tsmatsuzsamplebot01",
      "channelId": "skype",
      "address": "fc4a62b9-55de-49d8-b5ee-89a3235b6c7a",
      "id": "tsmatsuzsamplebot01",
      "isBot": true
    },
    {
      "name": "8:live:tsuyoshi.matsuzaki",
      "channelId": "skype",
      "address": "8:live:tsuyoshi.matsuzaki",
      "id": "949dPd8X5wF",
      "isBot": false
    }
  ],
  "totalParticipants": 2,
  "mentions": [],
  "channelMessageId": "0",
  "channelConversationId": "8:live:tsuyoshi.matsuzaki",
  "hashtags": []
}

HTTP Response by Bot (Skype の場合)

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{
  "conversationId": "1MC2mc1qR7up7bTG2w2by2v3Cay1CbFEGEQlFYQ1xX6Zc6x1",
  "text": "<ss type="wink">;)</ss>",
  "from": {
    "name": "tsmatsuzsamplebot01",
    "channelId": "skype",
    "address": "fc4a62b9-55de-49d8-b5ee-89a3235b6c7a",
    "isBot": true
  },
  "to": {
    "name": "8:live:tsuyoshi.matsuzaki",
    "channelId": "skype",
    "address": "8:live:tsuyoshi.matsuzaki",
    "isBot": false
  },
  "replyToMessageId": "4oij16x17tf",
  "participants": [
    {
      "name": "tsmatsuzsamplebot01",
      "channelId": "skype",
      "address": "fc4a62b9-55de-49d8-b5ee-89a3235b6c7a"
    },
    {
      "name": "8:live:tsuyoshi.matsuzaki",
      "channelId": "skype",
      "address": "8:live:tsuyoshi.matsuzaki"
    }
  ],
  "totalParticipants": 2,
  "channelMessageId": "0",
  "channelConversationId": "8:live:tsuyoshi.matsuzaki"
}

いずれ解説しますが、例えば、Skype Bot Platform の場合は、こうした Requset-Reply 方式の場合も、受信と送信の 2-leg の HTTP 呼び出し (異なる HTTP 要求) をおこないます。Microsoft Bot Framework では、こうした Channel ごとの差異を吸収して、Channel に応じた方式に変換して処理してくれます。(Microsoft Bot Framework の開発者が、こうしたパターンの違いを意識する必要はありません。)

Bot-Triggered Conversation (Outbound)

利用者 (ユーザー) の要求に回答するのではなく、イベント通知やタイマー処理など、Bot がトリガーとなって利用者に通知することも可能です。(この場合でも、もちろん、最初に、利用者がこの Bot の購読をおこないます。)
例えば、下記は、”Hey, what’s up Tsuyoshi ?” という Message を、Bot から Skype アドレス (live:tsuyoshi.matsuzaki) 宛に投げています。(Channel として Skype を使用しています。)

なお、この際、Framework (Microsoft Bot Framework) に渡す Authorization ヘッダーには上記と同じ内容を指定します。つまり、上記 (Inbound の場合) とは逆に、Bot の App Id と Secret を Bot Framework に渡すことで、Bot Framework 側がこの渡された Credential を検証します。(Secret などが誤っていた場合、Bot Framework から Forbidden (403) が返されます。)

HTTP Request by Bot

POST https://api.botframework.com/bot/v1.0/messages HTTP/1.1
Authorization: Basic dHNtYXRzdX...
Content-Type: application/json; charset=utf-8

{
  "language": "en",
  "text": "Hey, what's up Tsuyoshi ?",
  "from": {
    "name": "tsmatsuzsamplebot01",
    "channelId": "skype",
    "address": "fc4a62b9-55de-49d8-b5ee-89a3235b6c7a",
    "isBot": true
  },
  "to": {
    "name": "8:live:tsuyoshi.matsuzaki",
    "channelId": "skype",
    "address": "8:live:tsuyoshi.matsuzaki"
  }
}

HTTP Response by Framework

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{
  "type": "Message",
  "id": "F2NEPKSEB6U",
  "conversationId": "Djx1lR8IAF5Y1VB8aEDla5s84dnDOb8PF6v2CWYFw05PqEHd",
  "created": "2016-05-19T06:50:05.045569Z",
  "text": "",
  "attachments": [],
  "from": {
    "name": "8:live:tsuyoshi.matsuzaki",
    "channelId": "skype",
    "address": "8:live:tsuyoshi.matsuzaki",
    "id": "949dPd8X5wF",
    "isBot": false
  },
  "to": {
    "name": "tsmatsuzsamplebot01",
    "channelId": "skype",
    "address": "fc4a62b9-55de-49d8-b5ee-89a3235b6c7a",
    "id": "tsmatsuzsamplebot01",
    "isBot": true
  },
  "replyToMessageId": "LT2mbTW4E1C",
  "participants": [
    {
      "name": "tsmatsuzsamplebot01",
      "channelId": "skype",
      "address": "fc4a62b9-55de-49d8-b5ee-89a3235b6c7a",
      "id": "tsmatsuzsamplebot01",
      "isBot": true
    },
    {
      "name": "8:live:tsuyoshi.matsuzaki",
      "channelId": "skype",
      "address": "8:live:tsuyoshi.matsuzaki",
      "id": "949dPd8X5wF",
      "isBot": false
    }
  ],
  "totalParticipants": 2,
  "mentions": [],
  "channelConversationId": "49eenlloav3",
  "hashtags": []
}

 

de:code 2016 (BUILD の recap イベント) の私のセッションでは、新しく出てきた Skype の Developer Platform のすべてを紹介します !
他に、Bot のセッション等もありますので、是非 イベントを楽しんでください。

 

Azure Resource (ARM) の REST API による管理

こんにちは。

以前投稿した「Azure Resource Manager の template の How-to」では、Template を使って宣言的 (Declarative) に Azure Resource を定義する手法を紹介しましたが、今回は、Azure Resource Manager (ARM) の REST API を使って命令型 (Imperative) で処理する方法を紹介します。

XML ベースの Azure Service Management 時代も REST API による管理が可能でしたが、新しい ARM (Azure Resource Manager) による REST API では、上述の Json フォーマットの Template と Symmetry な関係になっており、Template を扱ったことがあるエンジニアならすぐに扱えるようになっています。

 

User の Role 割り当て (RBAC)

このあと User で (OAuth による) Login をおこなって REST API を呼び出しますが、Azure Resource Manager (ARM) の権限 (ACL) 管理では RBAC (Role Base Access Control) を使用します。
このため、あらかじめ、ユーザーに対して Role を付与しておきます。

補足 : 後述の通り、現状は (2015/09 現在は) Azure AD の User を使用する必要があるので、Microsoft Account で管理をおこなっている場合には、必ずこの設定を事前におこなっておいてください。

例えば、今回扱う Resource Group (ResourceGroup01 と仮定します) に対して参照・更新などの権限を付与するには、まず、Azure Portal で対象の Resource Group を表示して、下図の [Access] アイコン [Access control (IAM)] メニューをクリックします。

表示される RBAC の設定画面で、(Azure AD の) User に Contributor の Role を割り当てれば完了です。
なお、参照 (GET) の操作のみで充分なら、Reader の Role を割り当てます。
(Windows PowerShell などコマンドを使った設定も可能です。)

Role は、Subscription、Resource Group、Resource の各単位で設定可能で、継承されますので、例えば、Subscription 単位で Contributor として設定すれば、その中のすべての Resource Group に対して Contributor が付与されます。(以前の「共同管理者」に似た権限付与をしたい場合は、このように設定すると良いでしょう。)

 

認証 (Authentication)

REST API を使用するために必要な Access Token を取得するには、現時点 (2015/09) では「Native Application で Azure AD に Login するプログラミング (OAuth 紹介)」で紹介した OAuth のフローを実装します。

まず、Azure AD の管理画面 (Azure Management Portal) で Application を登録します。(登録方法は「Native Application で Azure AD に Login するプログラミング (OAuth 紹介)」を参照してください。)
この際、現時点は、下図の通り、Azure Service Management 用の Permission 設定してください。(現在は、Azure Management Portal を使った Service Management API 用の設定をおこないます。)

あとは、下記の通り、「Native Application で Azure AD に Login するプログラミング (OAuth 紹介)」で紹介したフローで Access Token を取得します。

(1) Browser または Browser Component (Native App の場合) で下記 URI にアクセスすると、SignIn 画面が表示されるので、上記で権限付与した User の ID / Password を入力してログインします。
なお下記の通り、現在は、resource として https://management.core.windows.net/ を指定しておいてください。(将来、https://management.azure.com/{scope name} が使用できるようになるでしょう。)

GET https://login.microsoftonline.com/common/oauth2/authorize
  ?response_type=code
  &client_id={client id}
  &resource=https%3a%2f%2fmanagement.core.windows.net%2f
  &redirect_uri={redirect uri}

(2) ログインをおこなうと、上記で指定した redirect uri に code が付与されて返ってきます。例えば、redirect uri が https://localhost/test の場合、以下のような URL に戻されます。
この code を取得します。

https://localhost/test?code=AAABAAA...

(3) 上記で取得した code を使って、下記の POST 要求を投げます。(今回は Web アプリの場合のサンプルです。Native アプリの場合、client secret は使用しないでください。)

POST https://login.microsoftonline.com/common/oauth2/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&code={previous code}
&client_id={client id}
&client_secret={client secret}
&redirect_uri={redirect uri}

(4) 上記の POST 要求の結果として、下記の通り access token が返ってきます。以降の REST API で、この access token を使用します。

HTTP/1.1 200 OK

{
  "expires_in": "3599",
  "token_type": "Bearer",
  "scope": "user_impersonation",
  "expires_on": "1441770794",
  "not_before": "1441766894",
  "resource": "https://management.core.windows.net/",
  "access_token": "eyJ0eXA...",
  "refresh_token": "AAABAAA...",
  "id_token": "eyJ0eXA..."
}

 

Azure Resource Manager (ARM) REST API による CRUD 操作

では、上記で取得した access token を使用して、Azure Resource Manager の REST API を実行してみましょう。

例えば、Virtual Machine resource の情報を取得 (GET) するには、以下の通り要求します。(URI は、以降、いくつか改行して記載しています。。。)

GET https://management.azure.com/subscriptions
  /{subscription id}/resourceGroups/{resource group name}
  /providers/Microsoft.Compute/virtualMachines
  /{virtual machine resource name}?api-version={api version}
Accept: application/json
Authorization: Bearer {access token}

例えば、Id が「af254894-bed5-44c5-9f6b-288427de57c1」の Subscription の、「ResourceGroup01」という名前の Resource Group にある、「testmachine01」という名前の Virtual Machine の場合は、以下の通りです。
access token には、上記で取得した値を設定します。

GET https://management.azure.com/subscriptions
  /af254894-bed5-44c5-9f6b-288427de57c1/resourceGroups
  /ResourceGroup01/providers/Microsoft.Compute
  /virtualMachines/testmachine01
  ?api-version=2015-05-01-preview
Accept: application/json
Authorization: Bearer eyJ0eXA...

この結果として、以下の通り Virtual Machine の内容が返ってきます。(「Azure Resource Manager の template の How-to」で解説した Template と同じ構造の内容が返されます。)

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{
  "properties": {
    "hardwareProfile": {
      "vmSize": "Basic_A1"
    },
    "storageProfile": {
      "imageReference": {
        "publisher": "MicrosoftWindowsServer",
        "offer": "WindowsServer",
        "sku": "2012-R2-Datacenter",
        "version": "latest"
      },
      "osDisk": {
        "osType": "Windows",
        "name": "testdisk01",
        "createOption": "FromImage",
        "vhd": {
          "uri": "http://tsmatsuzteststorage01.blob.core.windows.net/vhds/test01.vhd"
        },
        "caching": "ReadWrite"
      },
      "dataDisks": []
    },
    "osProfile": {
      "computerName": "mycomputer01",
      "adminUsername": "demouser",
      "windowsConfiguration": {
        "provisionVMAgent": true,
        "enableAutomaticUpdates": true
      },
      "secrets": []
    },
    "networkProfile": {
      "networkInterfaces":[
        { "id":"/subscriptions/af254894-bed5-44c5-9f6b-288427de57c1/resourceGroups/ResourceGroup01/providers/Microsoft.Network/networkInterfaces/testnic01" }
       ]
    },
    "provisioningState": "Succeeded"
  },
  "id": "/subscriptions/af254894-bed5-44c5-9f6b-288427de57c1/resourceGroups/ResourceGroup01/providers/Microsoft.Compute/virtualMachines/testmachine01",
  "name": "testmachine01",
  "type": "Microsoft.Compute/virtualMachines",
  "location": "eastus",
  "tags": {
    "displayName": "testmachine01"
  }
}

更新 Action (作成・変更・削除) も可能です。
例えば、上記 Virtual Machine の vmSize を変更する場合は、下記の通りです。(下記で location は必須ですが、この値は変更できないので注意してください。)

HTTP Request

PUT https://management.azure.com/subscriptions
  /af254894-bed5-44c5-9f6b-288427de57c1/resourceGroups
  /ResourceGroup01/providers/Microsoft.Compute
  /virtualMachines/testmachine01
  ?api-version=2015-05-01-preview
Accept: application/json
Authorization: Bearer eyJ0eXA...
Content-Type: application/json; charset=utf-8

{
  "location": "East US",
  "properties":
  {
    "hardwareProfile":
    {
      "vmSize": "Basic_A2"
    }
  }
}

HTTP Response

HTTP/1.1 200 OK
Azure-AsyncOperation: https://management.azure.com/subscriptions/af254894-bed5-44c5-9f6b-288427de57c1/providers/Microsoft.Compute/locations/eastus/operations/c5bf0c44-d8d8-4ef0-ae5b-8f9748e108b1?api-version=2015-05-01-preview

{
  "properties": {
    "hardwareProfile": {
      "vmSize": "Basic_A2"
    },
    "storageProfile": {
      "imageReference": {
        "publisher": "MicrosoftWindowsServer",
        "offer": "WindowsServer",
        "sku": "2012-R2-Datacenter",
        "version": "latest"
      },
      "osDisk": {
        "osType": "Windows",
        "name": "testdisk01",
        "createOption": "FromImage",
        "vhd": {
          "uri": "http://tsmatsuzteststorage01.blob.core.windows.net/vhds/test01.vhd"
        },
        "caching": "ReadWrite"
      },
      "dataDisks": []
    },
    "osProfile": {
      "computerName": "mycomputer01",
      "adminUsername": "demouser",
      "windowsConfiguration": {
        "provisionVMAgent": true,
        "enableAutomaticUpdates": true
      },
      "secrets": []
    },
    "networkProfile": {"networkInterfaces":[{"id":"/subscriptions/af254894-bed5-44c5-9f6b-288427de57c1/resourceGroups/ResourceGroup01/providers/Microsoft.Network/networkInterfaces/testnic01"}]},
    "provisioningState": "Updating"
  },
  "id": "/subscriptions/af254894-bed5-44c5-9f6b-288427de57c1/resourceGroups/ResourceGroup01/providers/Microsoft.Compute/virtualMachines/testmachine01",
  "name": "testmachine01",
  "type": "Microsoft.Compute/virtualMachines",
  "location": "eastus",
  "tags": {
    "displayName": "testmachine01"
  }
}

なお、上述の通り、例えば、Reader の Role しか付与されていないユーザーで上記の操作 (PUT) をおこなうと、下記の RBAC による権限エラーが返されます。

HTTP/1.1 403 Forbidden
Content-Type: application/json; charset=utf-8

{
  "error": {
    "code": "AuthorizationFailed",
    "message": "The client 'demouser02@demo.onmicrosoft.com' with object id '3da21de1-17b0-42e8-a20c-8d330ede1127' does not have authorization to perform action 'Microsoft.Compute/virtualMachines/write' over scope '/subscriptions/af254894-bed5-44c5-9f6b-288427de57c1/resourceGroups/ResourceGroup01/providers/Microsoft.Compute/virtualMachines/testmachine01'."
  }
}

Azure Resource Manager の template の How-to」で解説した Template の構成と比較すると、ARM の REST API は下記の構成になっているのがわかります。

 

Azure Resource Manager (ARM) REST API による Action

さて、ここまでだと、Template で可能なことを REST に変えただけと思われるかもしれませんが、ARM の REST API は OData v4 に対応しているため CRUD 以外の Action も実行できます。

例えば、Azure Resource Manager (ARM) を使った Virtual Machine (VM) の Stop, Generalize, Capture の流れを、下記の通り、REST API のみを使って簡単に操作できます。

Stop の Http Request

POST https://management.azure.com/subscriptions
  /af254894-bed5-44c5-9f6b-288427de57c1/resourceGroups
  /ResourceGroup01/providers/Microsoft.Compute
  /virtualMachines/testmachine01/powerOff
  ?api-version=2015-05-01-preview
Accept: application/json
Authorization: Bearer eyJ0eXA...

Stop の Http Response

HTTP/1.1 202 Accepted
Location: https://management.azure.com/subscriptions/af254894-bed5-44c5-9f6b-288427de57c1/providers/Microsoft.Compute/locations/eastus/operations/1d21a2f6-5c64-408c-b37a-3479c234b7df?monitor=true&api-version=2015-05-01-preview
Azure-AsyncOperation: https://management.azure.com/subscriptions/af254894-bed5-44c5-9f6b-288427de57c1/providers/Microsoft.Compute/locations/eastus/operations/1d21a2f6-5c64-408c-b37a-3479c234b7df?api-version=2015-05-01-preview

Generalize の Http Request

POST https://management.azure.com/subscriptions
  /af254894-bed5-44c5-9f6b-288427de57c1/resourceGroups
  /ResourceGroup01/providers/Microsoft.Compute
  /virtualMachines/testmachine01/generalize
  ?api-version=2015-05-01-preview
Accept: application/json
Authorization: Bearer eyJ0eXA...

Generalize の Http Response

HTTP/1.1 200 OK

Capture の Http Request

POST https://management.azure.com/subscriptions
  /af254894-bed5-44c5-9f6b-288427de57c1/resourceGroups
  /ResourceGroup01/providers/Microsoft.Compute
  /virtualMachines/testmachine01/capture
  ?api-version=2015-05-01-preview
Accept: application/json
Authorization: Bearer eyJ0eXA...
Content-Type: application/json; charset=utf-8

{
  "vhdPrefix": "mycap",
  "destinationContainerName": "myvhds",
  "overwriteVhds": "true"
}

Capture の Http Response

HTTP/1.1 202 Accepted
Location: https://management.azure.com/subscriptions/af254894-bed5-44c5-9f6b-288427de57c1/providers/Microsoft.Compute/locations/eastus/operations/664d304e-0a62-45f0-8239-4298d763eb69?monitor=true&api-version=2015-05-01-preview
Azure-AsyncOperation: https://management.azure.com/subscriptions/af254894-bed5-44c5-9f6b-288427de57c1/providers/Microsoft.Compute/locations/eastus/operations/664d304e-0a62-45f0-8239-4298d763eb69?api-version=2015-05-01-preview

なお、Capture など、Accepted Status (HTTP 202) が返っている操作は非同期に実行されています。(つまり、Capture の受け入れのみをおこなって HTTP Response を返し、バックグランドで Capture 操作が走ります。)
この進行状況を確認するには、上記の Azure-AsyncOperation ヘッダーに記載されている URI を使って状態を確認できます。(下記では、Capture が成功し、http://tsmatsuzteststorage01.blob.core.windows.net/system/Microsoft.Compute/Images/myvhds/mycap-osDisk.476ba657-8e4f-49af-9f50-5365de465543.vhd に image が保存されたことがわかります。)

HTTP Request

GET https://management.azure.com/subscriptions
  /af254894-bed5-44c5-9f6b-288427de57c1/providers
  /Microsoft.Compute/locations/eastus/operations
  /664d304e-0a62-45f0-8239-4298d763eb69
  ?api-version=2015-05-01-preview
Accept: application/json
Authorization: Bearer eyJ0eXA...

HTTP Response

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{
  "operationId": "664d304e-0a62-45f0-8239-4298d763eb69",
  "status": "Succeeded",
  "startTime": "2015-09-09T05:04:13.0664488+00:00",
  "endTime": "2015-09-09T05:04:15.8476583+00:00",
  "properties": {
    "output": {
      "$schema": "http://schema.management.azure.com/schemas/2014-04-01-preview/VM_IP.json",
      "contentVersion": "1.0.0.0",
      "parameters": {
        "vmName": {
          "type": "string"
        },
        "vmSize": {
          "type": "string",
          "defaultValue": "Basic_A1"
        },
        "adminUserName": {
          "type": "string"
        },
        "adminPassword": {
          "type": "securestring"
        },
        "networkInterfaceId": {
          "type": "string"
        }
      },
      "resources": [
        {
          "apiVersion": "2015-05-01-preview",
          "properties": {
            "hardwareProfile": {
              "vmSize": "[parameters('vmSize')]"
            },
            "storageProfile": {
              "osDisk": {
                "osType": "Windows",
                "name": "mycap-osDisk.476ba657-8e4f-49af-9f50-5365de465543.vhd",
                "createOption": "FromImage",
                "image": {
                  "uri": "http://tsmatsuzteststorage01.blob.core.windows.net/system/Microsoft.Compute/Images/myvhds/mycap-osDisk.476ba657-8e4f-49af-9f50-5365de465543.vhd"
                },
                "vhd": {
                  "uri": "http://tsmatsuzteststorage01.blob.core.windows.net/vmcontainerc4f164f5-1f47-4394-91b0-9b1316da66db/osDisk.c4f164f5-1f47-4394-91b0-9b1316da66db.vhd"
                },
                "caching": "ReadWrite"
              }
            },
            "osProfile": {
              "computerName": "[parameters('vmName')]",
              "adminUsername": "[parameters('adminUsername')]",
              "adminPassword": "[parameters('adminPassword')]"
            },
            "networkProfile": {
              "networkInterfaces": [
                {
                  "id": "[parameters('networkInterfaceId')]"
                }
              ]
            },
            "provisioningState": 0
          },
          "name": "[parameters('vmName')]",
          "type": "Microsoft.Compute/virtualMachines",
          "location": "eastus"
        }
      ]
    }
  }
}

Azure Resource Explorer を使って各 Resource で実行可能な Action を確認できますので、是非参考にしてみてください。

Azure Redis Cache の使用 (.NET, PHP, Node.js)

こんにちは。

Cache を使うことで、マルチ インスタンス構成 (ロード バランサー構成など) においてマシン間で情報 (session 情報など) を共有したり、頻繁に使用するデータの IO (入出力) 高速化などが期待できますが、これまで Azure で提供されてきた Cache (Managed Cache) は、主に .NET で使うことが想定されていました。(厳密には memcache プロトコルをサポートしていたので他言語でも使えましたが、あくまでもオプションの機能でした。)

新しく Azure に Built-in された Redis Cache のサービスを使うと、Redis による高いパフォーマンスを享受しながら、PHP, Python, Node.js など、さまざまな言語環境からストレスなく使うことができます。
ここでは、この Redis Cache の使い方を (session における利用方法も含め) 紹介したいと思います。(今後、数多くのハンズオンで使用するため、準備 (リファレンス) としてメモしておきます。)

 

Redis Cache の作成と構成

プログラミングの前に、まず、Azure Portal を使用して Redis Cache を作成します。

Redis Cache は、現行の Azure Management Portal (https://manage.windowsazure.com) では作成できないので、新しい Portal (https://portal.azure.com) を使用して新規作成してください。([New] – [Data + storage] – [Redis Cache] を選択して作成できます。)

Redis Cache の作成が完了したら、この後のプログラミングのために、Host name, Port number をコピー (メモ) しておきましょう。

また、access key もコピー (メモ) しておきましょう。

eviction policy (LRU など データが増えてきた場合の削除方針や優先度) をはじめとした構成作業は、redis.conf ではなく、Azure Portal を使用します。(下図)

また、現時点では persistence は提供されていないので、Read 主体のデータや、Session 情報のような揮発的なデータなど、使い方に注意してください。(RDB とプログラムの中間層として使用したい場合、現状は、Azure Virtual Machines に自身で Redis を立てるほうが無難かもしれません。。。)
また、Azure Managed Cache の頃のような 3 node による HA option は提供されていませんが、Azure Redis Cache の Standard 版を使用した場合、Master / Slave の 2 node 構成となり、failover がおこなわれるようなので、Critical な処理では Standard 版を使用すると良いでしょう。

補足 (2015/09) : Azure Redis Cache の Premium 版を使って、Max 10 台までの Sharding (Cluster 構成) が可能になりました

 

.NET (C#) から使う

.NET から使用する場合には、NuGet の StackExchange.Redis が使用できます。

上記の NuGet パッケージを取得したら、以下の通りプログラミングします。(下記は ASP.NET MVC のサンプル コードです。)
なお、下記の password には、上記で取得した access key を設定してください。(Host 名、Port 番号も同様に、皆さんの環境にあわせて設定してください。)

. . .
using StackExchange.Redis;
. . .

private static IDatabase _cache = null;
private IDatabase Cache
{
  get
  {
    if(_cache == null)
    {
      var con =
        ConnectionMultiplexer.Connect("tsmatsuz01.redis.cache.windows.net:6380,ssl=true,password=PcDPRkroG6…");
      _cache = con.GetDatabase();
    }
    return _cache;
  }
}

public async Task<ActionResult> TestSet()
{
  var cache = Cache;
  await cache.StringSetAsync("key1", "hogehoge");
  await cache.StringSetAsync("key2", 100);
  return View();
}

public async Task<ActionResult> TestGet()
{
  var cache = Cache;
  var key1 = (string) await cache.StringGetAsync("key1");
  var key2 = (int) await cache.StringGetAsync("key2");
  ViewBag.key1 = key1;
  ViewBag.key2 = key2;
  return View();
}
. . .

Redis Cache を ASP.NET の sessionState で使用する場合は、NuGet から RedisSessionStateProvider を取得して使用できます。(Web.config に、下記を使用した sessionState を定義します。)

 

PHP から使う

PHP から使用する場合も、Redis の普通のライブラリー (クライアント) を使用すれば OK です。
今回は、MSDN でも紹介している Predis を使用します。

まず、ここで使用する Predis は SSL に対応していないので、あらかじめ、作成した Redis Cache の SSL の設定で Non-SSL を有効にして保存してください。(下図の [Allow access only via SSL] で [No] を選択します。)
使用する Port 番号も、下図の通り 6379 となります。

Predis を取得 (ダウンロード) します。Composer を使用する場合は、下記のコマンドで取得できます。(実際の開発では、deploy.sh などに記載しておくと良いでしょう。)

curl http://getcomposer.org/installer | php
php composer.phar require predis/predis dev-master

あとは、プログラムを作成するだけです。
この際、上述の通り Non-SSL Port (上図の 6379) を使ってアクセスしてください。

<?php
require "vendor/autoload.php";
PredisAutoloader::register();

$r = getRedis();
$r->set("key1", "hogehoge");
$val = $r->get('key1′);
echo $val;

function getRedis() {
  static $redis = false;
  if ($redis)
    return $redis;
  // set non-ssl host
  $redis =
    new PredisClient("tcp://tsmatsuz01.redis.cache.windows.net:6379");
  // set access key
  $redis->auth("PcDPRkroG6. . .");
  return $redis;
}
?>

Predis 0.8.2 以降では Session Handler も実装されているので、Redis を php の session でも使用できます。(なお、この Session Handler を使用するには PHP 5.4 以上が必要ですので注意してください。)

<?php
require "vendor/autoload.php";
PredisAutoloader::register();
$redis = new PredisClient("tcp://tsmatsuz01.redis.cache.windows.net:6379");
$redis->auth("PcDPRkroG6…");
$handler =
  new PredisSessionHandler($redis, array('gc_maxlifetime' => 1800)); // hold 30 mins
$handler->register();
session_start();
. . .

// use session !
$_SESSION['foo'] = 'hogehoge';
. . .

Node.js から使う

Node.js の場合も同様です。今回は、Node の redis (または node-redis) パッケージ を使います。

redis パッケージも、同様に SSL に対応していないようなので、Redis Cache で Non-SSL を使用できるように設定しておきましょう。(上述)

下記のコマンドを入力して redis パッケージをインストールします。

npm install redis

値の set と get をおこなうには、以下の通りプログラミングします。

var http = require('http');
var redis = require('redis');
var redisClient = redis.createClient(
  6379,
  'tsmatsuz01.redis.cache.windows.net');
redisClient.auth(
  'PcDPRkroG6…',
  function (err) {
    // some step …
  });
http.createServer(function (req, res) {
  redisClient.set('key1′, 'hogehoge', function (err, rply) {
    console.log('set ended');
    console.log(rply);
  });

  redisClient.get('key1′, function (err, rply) {
    // result is in 'rply'
    console.log('get ended');
    console.log(rply);
  });

  res.writeHead(200, {'Content-Type': 'text/html'});
  res.write('Cache Test');
  res.end();
}).listen(process.env.PORT);

NodeJS でも、同じく、Express の session で Redis を使うことができます。これには、connect-redis というパッケージを使用します。

// install express generator
npm install express-generator -g
// create express files
express -f
// build express using package.json
npm install
// additional install for express-session
npm install express-session
// additional install for redis
npm install redis
// additional install for connect-redis
npm install connect-redis

以下の通りプログラム (app.js) を記述することで、express において、Redis を session として使用できます。

. . .
var session = require('express-session');
var RedisStore = require('connect-redis')(session);
. . .

var app = express();
. . .

app.use(cookieParser());
app.use(session(
 {
    store: new RedisStore({
      host: 'tsmatsuz01.redis.cache.windows.net',
      port: 6379,
      pass: 'PcDPRkroG6…'
    }),
    secret: 'yoursecretcode',
    saveUninitialized: true,
    resave: false
 }
));
. . .

app.use('/settest', function (req, res) {
  req.session.value = 'hogehoge';
  res.send('test value set !');
});
app.use('/gettest', function (req, res) {
  res.send('session value = ' + req.session.value);
});
. . .

 

Redis は「Redis – Clients」に列挙されているような、さまざまな言語環境から使用可能なオープン プラットフォームですので、その他の言語環境でもストレスなく使って頂くことができます。

 

 

Azure AD と Active Directory (AD FS) の Federation の手順

開発者にとっての Microsoft Azure Active Directory

こんにちは。

ここでは、Azure Active Directory (Azure AD) を企業内の Active Directory と Federation させる手順をメモします。(ハンズオンで使用します)
この設定をおこなうと、企業内の Active Directory で認証をおこなって、Azure AD 上の Application (Office 365, Google Apps, Salesforce, etc) に SignIn できます。(企業内から接続する場合は、Windows 統合認証でログイン画面を表示せずに SignIn できます。)

今回は Azure AD の観点で記載しますが、Office 365 を Active Directory と Federation させる場合も似た手順です。

準備

あらかじめ、Active Directory (AD), Active Directory Federation Services (AD FS), Web Application Proxy をセットアップしておきましょう。(今回、これらは既に用意されているものとします。)
なお、すぐに試したい方は、「Azure VM に Active Directory (および ADFS) をセットアップする」に Azure 上に構築する手順を記載しましたので、この方法でセットアップしてみてください。
なお、後述する Azure AD Connect を使って AD FS と Web Application Proxy のインストールも可能です。(今回はあらかじめインストール済の環境を使用します。)

つぎに、連携 (Federation) 対象の Windows のドメインで、同期で使用する Active Directory の読み込み権限と書き込み権限を持つドメイン ユーザーを作成します。今回は、Domain Admins 権限の tsmatsuzsyncuser を作成したと仮定します。

使用する Azure AD の Directory 内にも、あらかじめ全体管理者 (Global Administrator) のユーザーを作成しておきます。管理権限を持つ Microsoft Account は使用できないため、必ず、別途管理者を追加してください。今回は admin@aaddemo01.onmicrosoft.com と仮定します。
なお、このユーザーは、初期セットアップで使用するもので、普段の同期の際には、Azure AD 上に自動で追加された同期用のユーザーが使われます。

今回使用する同期サーバーを準備 (新規作成) します。
Azure を使用する場合は、「Azure VM に Active Directory (および ADFS) をセットアップする」と同じ方法で、AD FS などで使用している Viratul Network (VNET) と同じサブネットに入れて Windows Server を新規作成します。
なお、このマシン (同期サーバー) には .NET Framework、Microsoft PowerShell, SQL Server などが必要ですが、これらは後述する Azure AD Connect で自動セットアップできるのでそのままで結構です。

作成後、この同期用のマシンを AD (および、AD FS) と同じドメインに参加させます。

後述の Azure AD Connect では、AD FS の構成の際に PowerShell Remoting を使って構成します。
このため、接続先の AD FS のサーバーと、上記の同期サーバーの双方で、管理者権限で PowerShell を起動して、下記のコマンドを入力しておきます。

Enable-PSRemoting -force

念のため、下記コマンドを実行して、同期サーバーから AD FS のサーバーに対して PowerShell Remoting が実行できるか確認してみてください。(なお、今回のように、双方のマシンが同一ドメインに参加している場合、ドメインに登録されているマシン名を使って接続してください。FQDN などを使うと、SPN が登録されていないため、エラーとなります。)
下記で、tsmatsuz-adfs は AD FS のインストールされたマシン名、tsmatsuzuser01 は Domain ユーザーと仮定します。(皆さんの環境に応じて、適切な値を設定してください。)

Enter-PSSession -ComputerName tsmatsuz-adfs -Credential tsmatsuzuser01

Custom Domain の追加

Azure Portal で Azure AD の管理画面を表示して [Domains] (ドメイン) タブをクリックし、[Add a custom domain] (カスタムドメインの追加) をクリックしてカスタム ドメインの追記をおこないます。

つぎに、Domain Registrar (レジストラー) に指定された DNS レコードを設定して、Domain の所有を検証 (Verify) します。
この方法は PowerShell を使う方法などいくつかありますが、例えば、Office 365 のポータル (https://portal.office.com) に Azure AD の全体管理者でログインをおこなうと、Domain の Verify が可能ですので、指示された TXT レコード、MX レコードをレジストラーに設定して検証 (Verify) をおこなってください。(Office 365 のライセンスは不要です。)

Azure AD Connect のインストールと構成

上記で準備した同期用のマシン (Windows Server) に Microsoft Azure Active Directory Connect (以降、Azure AD Connect) のインストールと構成をおこなうため、同期用のサーバーに権限を持つドメイン ユーザーでログインします。(今回は、上記で作成した tsmatsuzsyncuser でログインします。このユーザーに、このマシンの管理者権限を設定しておく必要がありますが、今回は Domain Admins 権限のユーザーのため追加の設定は不要です。)

まず、ダウンロード センターなどから Azure AD Connect をダウンロードして、同期サーバーにインストールします。インストールが完了すると Azure AD Connect の構成画面が起動するので、以降、ウィザードに従って進めます。

まず最初に、簡易設定 (Express Settings) かカスタマイズ (Customize) のいずれかを選択します。
Password Sync (ハッシュを使ったパスワード同期) の場合は、簡易設定 (Express Settings) を使うことで簡単にセットアップできますが、今回は Federation による SSO (シングル・サインオン) 環境を構築するため、[Customize] ボタンを押します。

SQL Server などインストール済のコンポーネントや設定があって、それを使う場合は、下記でチェックを付けます。(チェックを付けない場合は、Azure AD Connect でインストールや既定の設定がおこなわれます。)
今回は、上記で作成した tsmatsuzsyncuser を service account として設定し、他はチェックを付けずに既定のままインストール (セットアップ) します。

つぎに表示される画面で、今回は [Federation with AD FS] を選択します。

つぎに表示される画面で、上記で準備した Azure AD の全体管理者のアカウント (今回の場合、admin@aaddemo01.onmicrosoft.com) の Credential を入力します。

つぎに表示される画面で、連携元の Directory (Forest) の情報と、その情報にアクセスするための Credential (今回は上記の Domain Admins 権限を持った tsmatsuzsyncuser) を入力して [Add Directory] をクリックします。
接続先の Directory (Forest) が下部に追加されるので、先に進みます。(このように、Directory を複数指定可能です。今回は 1 つだけ設定します。)

以降の下記の画面は、すべて既定の設定のまま先に進みます。
これらの設定によって、属性による同期ユーザーのフィルター、ユーザーのマッピング方法の変更、Password Writeback (Azure AD 側のパスワード変更を AD 側に書き戻す機能) など、細かな構成 (カスタマイズ) が可能です。

つぎの画面では、AD FS、Web Application Proxy などのインストールが可能ですが、上述の通り、今回は、既にこれらはインストール (構成) 済のため、下図で [Use an existing Windows Server 2012 R2 AD FS farm] を選択します。

なお、この際、使用する AD FS の Server Name と Credential (AD FS の管理権限を持つ Credential) を入力しますが、AD FS の構成 (Relying Party の登録など) では PowerShell Remoting を使って接続するため、同一ドメインの場合には、必ず、ドメインに登録されているサーバー名を入力してください。(FQDN など別のサーバー名を使用すると、ドメインの SPN が登録されていないため Remoting のエラーが発生します。)

つぎの画面で、Domain Admins 権限を持つ AD ドメインのユーザーの Credential を入力します。

つぎの画面で、AD FS の Service Account の Credential を入力します。

つぎの画面で、フェデレーション対象となるオンプレ側の AD ドメインを選択します。

さいごに下記の画面が表示されるので、[Install] ボタンを押して構成を開始します。(AD FS、Azure AD、同期サービスの構成が開始されます。完了まで、しばらく時間がかかります。)

完了すると、下図の通り表示されるので [Verify] ボタンを押します。

イントラ経由の AD FS 接続の確認と、Web Application Proxy 経由の Extranet Configuration の確認がおこなわれ、問題なければ終了します。

設定内容の確認

同期サーバーにログインしてセットアップ内容を確認してみましょう。
なお、セットアップ完了後、同期サーバーに ADSyncAdmins という Windows のグループが作成されていますが、Synchronization Service を管理するには、このグループのユーザーである必要があるので、このグループのユーザーでマシンにログインしてください。

まず、同期の設定を確認するには、PowerShell を管理者権限で起動して、Get-ADSyncScheduler コマンドを実行します。設定されている同期の周期、次回の同期時間などの基本情報が表示されます。(「Delta」は「差分」を意味します。)

Get-ADSyncScheduler

AllowedSyncCycleInterval            : 00:30:00
CurrentlyEffectiveSyncCycleInterval : 00:30:00
CustomizedSyncCycleInterval         :
NextSyncCyclePolicyType             : Delta
NextSyncCycleStartTimeInUTC         : 6/13/2015 4:21:20 AM
PurgeRunHistoryInterval             : 7.00:00:00
SyncCycleEnabled                    : True
MaintenanceEnabled                  : True
StagingModeEnabled                  : False

手動 (manual) で同期 (sync) を強制したい場合は、Start-ADSyncSyncCycle コマンドを使用します。

Start-ADSyncSyncCycle -PolicyType Delta

同期サーバー側の同期結果 (Result) を確認するには、Event Viewer などで「Directory Synchronization」の Source に絞って確認できます。(同期のエラーが出ていないか確認してみてください。)

また、Azure AD 側で同期の状況を確認するには、[Directory Integration] タブを選択します。(下図)
下図で「never run」となっていた場合は、まだ同期されていないという意味です。

正しく同期されている場合、Azure AD で [Users] タブを選択すると、下図の通り、AD のユーザーが同期 (コピー) されているのがわかります。
このように、既定の設定のままだとシステムアカウントも含めてすべて同期されるので、現実の運用では Filtering などを使って設定をおこなうと良いでしょう。

また、AD FS のサーバー (Windows) にログインして AD FS Management Console を開き、[Relying Party Trusts] (証明書利用者信頼) を確認すると、Azure AD (Office 365 Identity Platform) が Replying Party (RP) として AD FS に登録されています。

同期のさまざまな設定 (使用している Credential、Filtering など) をあとから変更する際には、同期サーバー上にインストールされた Azure AD Connect を起動して再設定できます。

動作の確認

さいごに、SSO (シングル・サインオン) と Federation の動作を確認してみましょう。

例えば、「Azure AD を使った API (Service) 連携の Client 開発 (OAuth 2.0)」で記載されている方法で、Azure AD への Application 登録と Permission 設定をおこなって、以下の URL にアクセスしてみてください。

https://login.microsoftonline.com/common/oauth2/authorize?response_type=code&client_id={client id}&resource={resource uri}&redirect_uri={redirect url}

例えば、以下のような感じです。

https://login.microsoftonline.com/common/oauth2/authorize?response_type=code&client_id=52bee189-4cb8-4382-987e-32e59e4c0d46&resource=https%3a%2f%2flocalhost%2ftest1&redirect_uri=https%3a%2f%2flocalhost%2ftest2

上記の URL にアクセスすると、まず、普通に Azure AD のログイン画面に Redirect されます。(下図)
このログイン画面で、ユーザー ID に連携 (Federation) している Custom Domain (今回の場合、tsmatsuz.com) の User ID を入力して Tab キーを押します。

すると、下図の通り、AD FS のログイン画面に Redirect されるので、ここにドメインのユーザー ID とパスワードを入力します。

ドメインのログイン確認に成功すると、Application の URL に code が付与されて戻ってきます。上記の例の場合、下記のような URL に戻されます。

https://localhost/test2?code=...

この Federation では、Application -> Azure AD (ログイン画面の表示) -> AD FS (ログイン画面の表示と確認) -> Azure AD (確認) -> Application の順番で遷移 (Redirect, HTTP POST など) します。AD FS で確認が完了すると、その結果が Token (または Assertion) として Azure AD に渡されます。(AD FS からは、Azure AD が Application として見えています。) Azure AD 側ではこの Token を受け取って検証し、問題なければ Application (上記の例の場合、https://localhost/test2) に結果を返します。

===============

ご参考 : PowerShell と Azure AD Sync Tool (DirSync) による構成

以下、Azure AD Connect を使わず、PowerShell を使って Federation をおこない、Azure Active Diretory Sync Tool (Azure AD 同期ツール, Azure AD Sync, DirSync) を使って同期させる方法を記載します。
なお、ここで紹介する DirSync は、今後 廃止予定であり、DirSync で構成した内容を Azure AD Connect へ Upgrade できます。(ここでは、Upgrade 方法は解説しません。)

まず、Azure AD と AD FS の相互で、WS-Federation による Federation をおこなうための信頼関係を PowerShell を使って設定します。
具体的には、ADFS 側への Relying Party (証明書利用者信頼) の登録や、Azure AD 側へのドメイン、ADFS 登録などがおこなわれます。(デジタル署名付きのメタデータなどを交換します。)
なお、内部で PowerShell Remoting を使って ADFS 側の設定を書き換えるので、あらかじめそのための設定をおこなってください。(設定の詳細は省略します。直接、ADFS Server のマシン上で実行しても構いません。)

まず、「Azure Active Directory とは (事前準備)」で解説した Microsoft Azure Active Directory PowerShell Module をインストールした環境 (マシン) で、PowerShell を使って以下のコマンドを入力し、Azure AD にログインします。(この際、Azure AD 上の全体管理者のアカウントでログインしてください。Azure サブスクリプションで使用している Microsoft Account は不可です。)

import-module MSOnline
import-module MSOnlineExtended
$cr = Get-Credential
(Prompted. Please login using Global Administrator ...)
Connect-MsolService -Credential $cr

今回、インターネット上の Web Application Proxy (AD FS Proxy) のエンドポイントを sts.tsmatsuz.com と仮定します。(上述であらかじめ準備したサーバーです。)
まず、下記のコマンドを実行して、Azure AD 側に、この ADFS Server を設定します。

Set-MsolAdfscontext -Computer sts.tsmatsuz.com

補足 : ADFS Server 上で実行した場合 (かつ、AD が同じマシンにインストールされている場合)、Invalid Credential のエラーになるようですが、無視して先に進んで OK です。(下記参照)
http://community.office365.com/en-us/f/613/t/243053.aspx

つぎに、以下のコマンドを入力し、上記の設定に基づいて信頼関係を設定します。(今回は tsmatsuz.com ドメインと Federation させています。)
この際、初回の実行では下記の Waring が表示されるので、この Waring に従って、ドメイン レジストラ (Domain Registrar) に TXT レコードと MX レコードを登録します。(下記の XXX には適当な数字が設定されています。)

New-MsolFederatedDomain -DomainName tsmatsuz.com

WARNING: Verify tsmatsuz.com domain ownership by adding a DNS TXT record with a text value of MS=msXXXXXXXX or a DNS MX
 record targeting msXXXXXXXX.msv1.invalid with a priority of 32767 at your domain registrar.

レジストラへの登録をおこなったら、再度、同じコマンドを実行します。
今度はドメインの検証 (Verify) がおこなわれ、Azure AD 側のドメイン等の構成と、ADFS Server 側の Relying Party (証明書利用者信頼) の登録が完了します。

New-MsolFederatedDomain -DomainName tsmatsuz.com

以降では、Azure Active Diretory Sync Tool (Azure AD 同期ツール, Azure AD Sync, DirSync) を使って、User などの同期を構成します。

まず、構成前の準備として、Azure Portal で対象の Azure AD の Directory を表示し、[Directory Integration] (ディレクトリー統合) のタブを選択し、下図の [Activated] を選択して保存します。

Windows Server Active Directory (Domain Services) のドメイン内のマシン (新規に同期用のサーバーを立てても OK) に同期ツールをインストールしますが、.NET Framework 3.5.1 と 4.5 が必要なので、あらかじめ、Server Manager の [Add Roles and Features] (役割と機能の追加) でこれらをインストールしておいてください。

準備ができたら、Azure Active Directory Sync tool (64 bit) をダウンロードしてインストールします。

補足 : Microsoft Online Services Sign-in Assistant が既にインストールされている場合には、いったんアンインストールしてから、DirSync をインストールしなおしてください。(Azure Active Directory Sync tool がインストールします。)

補足 : Azure Active Directory Sync tool では、インストール ユーザーが FIMSyncAdmins グループに属している必要があり、インストールによりこのグループを付与します。もし、権限エラーで失敗したら、マシンを再起動するなどして、この Permission を反映してから、再度、インストールしなおしてください。

インストールが完了したら、Azure Active Directory Sync tool (DisSync) の構成ウィザードを起動します。
このウィザードで、Sync をおこなう Azure AD 側の全体管理者 (Global Administrator) の ID・パスワードや、Server Active Directory 側のユーザー・パスワードを設定します。Server Active Directory 側のユーザーは、ドメインの管理者で構いません。(このユーザーで同期サービスが実行されるのではなく、同期サービス用のアカウントを新規作成する際に使用する一時的なユーザーです。ドメインの Domain Admins, Enterprise Admins グループの権限が必要です。)

補足 : 今回は、パスワード同期による Federation ではなく、AD FS を使用した WS-Federation のプロトコルによる Federation を構成しています。
このため、ウィザードの途中で表示される [Password Synchronization] のチェックは外してください。(Password Synchronization は、AD FS を使わず、パスワード ハッシュも含むユーザー情報を Azure AD にコピーして Federation する方法です。)

===============

 

※ 変更履歴

2016/06  今後の DirSync 廃止 (Deprecate) に伴い、DirSync から Azure AD Connect を使用した手順に変更

 

Azure VM に Active Directory (および ADFS) をセットアップする

ここでは、Microsoft Azure に Active Directory (AD)、および AD FS をセットアップする手順をメモします。(ハンズオン用に手順を記載します。)
Web Application Proxy を使うことで、インターネット上から AD FS を利用することもできます。

今回は、下図の構成と仮定します。
AD と ADFS は仮想マシン tsmatsuz-ad, Web Application Proxy は仮想マシン tsmatsuz-proxy にセットアップし、それぞれを仮想ネットワークで接続します。

なお、実運用上はあまり望ましくありませんが (障害発生時の切り分けや、スケーリングなどの観点から)、今回はテスト目的で、Active Directory (AD DS) と同一のマシン (tsmatsuz-ad) に Active Directory Federation Services (AD FS) をセットアップします。

 

Active Directory のセットアップ

Azure Portal などを使って、まず、仮想ネットワーク (Virtual Network) を新規作成します。

Virtual Network が作成できたら、つぎに、Active Directory をインストールする Virtual Machine (Windows) を新規作成します。(今回は、Windows Server 2012 R2 を使用します。)

Virtual Machine の作成の際、Network として上記で作成した Virtual Network を選択します。

Virtual Machine の作成が完了したら、以下のコマンドで 静的 IP (Static IP) を設定します。下記では、tsmatsuz-ad という Virtual Machine に 10.0.0.5 の Static IP を設定しています。

このコマンドを実行すると、仮想マシンが再起動されます。

Add-AzureAccount
(Prompted. Please login . . .)

$vm = Get-AzureVM -ServiceName tsmatsuz-ad -Name tsmatsuz-ad
Set-AzureStaticVNETIP -VM $vm -IPAddress 10.0.0.5 | Update-AzureVM

なお、割り当てる IP が使えるか調べたい場合は、以下のコマンドで事前に確認できます。(既に使用されている IP アドレスは使用できません。)

Test-AzureStaticVNetIP -VNetName adnet -IPAddress 10.0.0.5

なお、上記は Azure の Classic モードの場合の設定方法です。Resource Manager モード (IaaS v2) の場合、Azure Portal の画面を使って Static IP を割り当て可能であり、Virtual Machine の [Network Interface] に対して [IP Addresses] を [Static] に変更して、アドレスを入力します。(Windows PowerShell は必要ありません。)
以降も同様に (並行して)、Resource Manager モードの場合について補足していきます。

補足 : Azure の Virtual Machine に Static IP を割り当てても、OS 上で Static な IP Address が割り当てられるのではなく、起動時に Azure の DHCP によって固定の IP Address が割り当てられます。
このため、後述の Active Directory Domain Services のインストールの際などに警告が表示されますが、無視して進めてください。

つぎに、この Virtual Machine に、SYSVOL など AD の情報を格納するためのディスクを新規に Attach します。(今回、20 GB で、Host Caching は None にしておきます。)

Remote Desktop を使ってこの Virtual Machine にログインすると、上記で追加した Disk が見えるので、Drive Letter の割り当てや NTFS フォーマットなどをおこなっておきましょう。(Server Manager で [File and Storage Services] を選択して設定できます。)

AD インストールの準備として、コントロール パネルで System Properties を開き、DNS Suffix を設定します。(今回は「tsmatsuz.com」と仮定します。)
マシンの再起動を要求されるので、再起動してください。

Server Manager の [Add Roles and Features] (役割と機能の追加) で、[Active Directory Domain Services] (AD DS) をインストールします。
インストール自体はすぐに完了します。

つぎに、Domain の構成 (dcpromo) を開始します。

この構成では、新しいフォレストを作成し ([Add a new forest] を選択)、DNS Server の設定 (インストール) もおこなってください。
また、Database folder、SYSVOL folder は、上記で Attach した Disk (Drive) を選択します。

補足 : Azure は既定で DNS を持っていますが、AD 用に DNS Server をインストールし、この後、仮想ネットワークで、この DNS Server を使うように構成します。
なお、DNS の hint root の設定がされているため、この設定でもちゃんと Virtual Machine 内から Internet に接続できます。

構成が完了したら、マシンの再起動をおこないます。

補足 : マシンの再起動以降は、Windows のログイン ID は {your domain}{user id} に変更されます。

さいごに、Virtual Network に上記でインストールされた DNS Server を登録します。
Azure Portal で作成した Virtual Network を選択し、DNS Server として上記で作成した Virtual Machine の IP アドレス (10.0.0.5) を設定します。

補足 : 完了したら、仮想マシンの snapshot を作成しておくと良いでしょう。(以降、失敗した場合などに、いつでも戻せます。)
Save-AzureVMImage -ServiceName tsmatsuz-ad -Name tsmatsuz-ad -NewImageName tsmatsuzadbk01 -NewImageLabel tsmatsuzadbk01 -OSState Specialized

 

Active Directory Federation Services (AD FS) のセットアップ

つぎに、Active Directory Federation Services (AD FS) のインストールと構成をおこないます。
上述の通り、今回は、テスト目的で、上記の Active Directory (AD DS) と同一のマシン (tsmatsuz-ad) にセットアップします。

まず、準備として、Server Manager の [Add Roles and Features] (役割と機能の追加) で [Web Server (IIS)] をインストールしておきます。

つぎに、証明機関から、sts.tsmatsuz.com (このあと、このマシン名で構成します) の SSL サーバー証明書を取得し、IIS に設定しておきます。(この構成方法の詳細は省略します。MMC を使って Local Computer の証明書管理と import が可能です。) 最近は、RapidSSL など安価に入手できるものもあるので、テスト用に作成しても良いでしょう。

補足 : イントラネットからのアクセスに限定される場合や、テスト目的の場合 (証明書の種類には特に拘らない場合) などには、Windows Server の証明機関が発行する証明書を使っても構いません。(ただし、ブラウザーに Trust Root などの設定をしていない場合、インターネット上からアクセスした場合は警告が表示されます。)
Windows Server の証明機関のインストールは、[Add Roles and Features] (役割と機能の追加) で [Active Directory Certificate Services] (証明書サービス) を選択します。(インストールするサブ コンポーネントは [Certificate Authority] (証明機関, CA) のみで充分です。)

CA のインストールが完了したら、Web Server 用の証明書 (Certification) の新規作成をおこないます。IIS Manager を開いて、左のツリーからサーバー名 (今回の場合、TSMATSUZ-AD) を選択し、[Server Certificates] (サーバー証明書) を選択し、表示される画面の右ペインから [Create Domain Certificate] を選択します。
この際、表示されるウィザードの [Common Name] に sts.tsmatsuz.com と入力し、Certificate Authority (CA) として、上記でインストールした CA を選択してください。

つぎに、ADFS のサービスとして使用するユーザー (Account) をドメインに新規作成します。(このユーザーを tsmatsuzadfssvc と仮定します。今回、このユーザーには Domain Admin 権限を付与しておきます。)

そして、下記コマンドを実行して ServicePrincipalName (SPN) を登録します。

setspn -a host/adfssrv tsmatsuzadfssvc

Server Manager の [Add Roles and Features] (役割と機能の追加) で [Active Directory Federation Services] をインストールします。

インストールが完了したら、Active Directory Federation Services (AD FS) の構成を開始します。

この際、SSL Certificates (SSL 証明書) として上記で取得したサーバー証明書を選択し、ADFS の Service Account として上記のアカウント (tsmatsuzadfssvc) を設定します。(本来なら、AD FS で使用するデータベースも、あらかじめ上記で追加した Drive 上に作成すると良いですが、今回は省略します。)

さいごに、DNS Manager を開き (Server Manager から [DNS] を選択します)、ネットワーク内部から sts.tsmatsuz.com でアクセスできるようにホスト「sts」を登録しておきましょう。

ここまでの設定が完了すると、ネットワーク内部から https://sts.tsmatsuz.com/adfs/ls で下図の通り表示されるはずです。

うまく設定できていない場合には、Server Manager を起動して、AD FS の Event (Error など) を確認してください。

 

Web Application Proxy のセットアップ

ここまでで、ネットワーク内部からの ADFS の利用が可能です。
例えば、Office 365 と Federation を構成して、ネットワーク内部のマシンからつないだ場合、ADFS にリダイレクトされて Federation が可能です。

以降では、Web Applicatoin Proxy をセットアップすることで、インターネット上から AD FS を使用できるように構成します。
ここで述べる Web Application Proxy を使用すると、例えば、家の PC など、ドメインに入っていないインターネット上の環境から企業内の Active Directory で認証をおこなってトークンを取得できます。(企業内の AD と Federation された Office 365 などが使えます。)

まず、Web Application Proxy として使用する Virtual Machine (今回、tsmatsuz-proxy とします) を Azure で新規作成します。(Windows Server 2012 R2 以上を使用してください。)
今回もネットワークとして、上記で作成した仮想ネットワークを選択します。(本来なら、サブネットを複数構成するなど、安全性や運用性を考慮して構成を検討しますが、ここでは省略します。)
なお、この新しいマシンはドメインには参加させません

この Virtual Machine (tsmatsuz-proxy) にログインし、ping コマンドなどで sts.tsmatsuz.com (上記) に接続できることを確認してください。
ネットワーク構成によっては、sts.tsmatsuz.com で名前解決ができるように hosts の設定などをおこないます。(今回は接続可能なため不要です。)

C:>ping sts.tsmatsuz.com

Pinging sts.tsmatsuz.com [10.0.0.5] with 32 bytes of data:
Reply from 10.0.0.5: bytes=32 time<1ms TTL=128
Reply from 10.0.0.5: bytes=32 time<1ms TTL=128
Reply from 10.0.0.5: bytes=32 time=1ms TTL=128
Reply from 10.0.0.5: bytes=32 time<1ms TTL=128

Ping statistics for 10.0.0.5:
    Packets: Sent = 4, Received = 4, Lost = 0 (0% loss),
Approximate round trip times in milli-seconds:
    Minimum = 0ms, Maximum = 1ms, Average = 0ms

つぎに、下記の 2 種類の証明書を準備 (Export) して、Web Application Proxy の Server (tsmatsuz-proxy) に証明書を設定 (Import) します。

  • 上述の AD / AD FS Server (tsmatsuz-ad) から、public key のみの Certificate Authority (CA) 証明書 (CA 宛てに発行された証明書) を Export します。(このファイルを CA.cer と仮定します。)
    IIS Management Console などから Export できます。
  • 上述の AD FS Server (tsmatsuz-ad) から、private key を含む Server 証明書 (上述の sts.tsmatsuz.com 宛に発行された証明書) を export します。(このファイルを server.pfx と仮定します。)
    同様に、IIS Management Console などから export できます。
  • Web Application Proxy Server (tsmatsuz-proxy) 上で mmc を起動し、[Add/Remove snap-in…] で [Certificates] を選択、[Computer Account] (Local Machine) を選択して、Local Machine の証明書管理コンソールを表示します。
  • 上記で export した private key を含む Server 証明書 (server.pfx) を Personal に Import します
  • 上記で export した public key のみの CA 証明書 (CA.cer) を Trusted Root Certification Authority に Import します

Server Manager の [Add Roles and Features] (役割と機能の追加) で [Remote Access] をインストールします。

表示されるウィザードで、[Web Applicatoin Proxy] を選択します。

つぎに、インストールされた Web Application Proxy の構成を開始します。
なお、ここで、AD FS Server のホスト名と、上記で設定した証明書と、AD FS Server 上の管理者アカウント (一時的に使用するため、Domain Admins 権限のユーザーであれば何でも可) の情報が必要です。
AD FS Server のホスト名には、上記で設定した sts の Alias (今回の場合、sts.tsmatsuz.com) を指定します。(tsmatsuz-ad.tsmatsuz.com ではありません。)

Azure Portal で、この Virtual Machine (tsmatsuz-proxy) の Endpoints を表示し、443 の Port を開けておきます。(private port も 443 です。)

補足 : Resource Manager モード (v2) の場合、[Virtual Machine] – [Network Interface] – [Network Security Group (NSG)] – [Inbound Security Rules] で https の Rule を追加してください。 を * (asterisk), [destination port range] を 443 に設定します。

さいごに、お使いのドメイン レジストラー (eNom など) で、CNAME (Alias) として sts をこの tsmatsuz-proxy の Cloud Service の URL (tsmatsuz-proxy.cloudapp.net など) に割り当てておきます。(https://sts.tsmatsuz.comhttps://tsmatsuz-proxy.cloudapp.net となるように設定します。)
これにより、インターネット上から https://sts.tsmatsuz.com で、この Web Application Proxy が使用できます。

補足 : Resource Manager モード (v2) の場合、あらかじめ、[Public IP Addresses] – [Configuration] で [DNS name label] を設定します。例えば、[DNS name label] が「tsmatsuz-proxy」で、Japan East の VM の場合、FQDN は「tsmatsuz-proxy.japaneast.cloudapp.azure.com」です。

設定が完了すると、インターネット上のブラウザーから https://sts.tsmatsuz.com/adfs/ls で下図の通り表示されるはずです。

補足 : 上記補足で解説した Windows Server の CA (証明機関) が発行する証明書 (イントラネット用の証明書) を使用している場合、インターネットから接続した際に証明書の警告が表示されるはずです。この場合には、クライアント側 (ブラウザー側) の証明書の設定 (この CA の証明書を Trusted Root Certification Authority に登録する、など) をおこなってください。

エラーが発生している場合は、Server Manager で Error を確認するか、OS (Web Application Proxy の Server) にインストールされている Remote Access Management Console を表示してエラーや起動状態を確認してみてください。

もし、証明書の期限切れなどで上述の Configuration が再度必要になった場合には、Windows PowerShell の Install-WebApplicationProxy コマンドを実行します。 (AD FS Server 名、証明書情報、管理者アカウント情報など、上記と同様の値を設定します。) 上述の Web Application Proxy の構成 (Configuration) においても、内部で、この Cmdlet が実行されています。

Twilio 開発で Azure をおすすめする理由 (ワケ)

こんにちは。

今日は Mac OS などをお使いの方のために、Node.js と Azure を組み合わせた Twilio API 開発の圧倒的メリットをご紹介します。
(Windows をお使いの方は、.NET (C# など) を使って、同等、もしくはそれ以上の開発生産性を得られます。「Visual Studio で Azure のプロジェクトを Remote Debug する」を参照してください。)

先日実施した Twilio X Azure ハンズオンでは、TypeScript 定義ファイルなど、いくつかテキストにない余談も入れたので、これらもあわせて記載しておきます。
なお、次回ハンズオンは 12/11 を予定していますが、前回はすぐにうまってしまったので、もう少しキャパを広げて実施したいと思います。(募集サイトはまだこれからです。)

 

Twilio API を使った開発 (はじめに)

まず、メリットを紹介する前に、Twilio 開発における特徴を説明します。

Twilio API を使ったアプリケーションでは、ざっと下図のような流れでアプリケーションが動作します。

  1. 皆さんが構築するアプリケーションや電話受信などをトリガとして、クラウド上で動作している Twilio Service は処理を開始します。(下図の 1)
  2. Twilio Service は、処理すべき応答 (通話内容、送信内容など) を取得するため、皆さんが構築したアプリケーションにアクセスします。(下図の 2)
  3. その受け取った内容を元に、通話や SMS 送信などをおこないます。(下図の 3)

IVR などでは、この 2 と 3 のキャッチボールが続くことになります。

さて、通常の開発 (プログラミングやテスト) では、localhost で動かしたものをクラウドに配置するという流れになるかと思いますが、上図のように、Twilio Service (https//api.twilio.com) が皆さんのアプリケーションに頻繁にアクセスするため、多くの場合、開発時 (初期のプログラミング段階) でも動作させるためにクラウドに配置しておく必要があります。(なお、いくつか回避方法はありますが、実用的ではありませんので省略します。)
つまり、Twilio API を使った開発において、localhost を使った開発・検証は極めて限定的で、一般には、プログラミングの初期段階 (単体テスト含む) からクラウドを使った開発が必要になってきます。

そして、ここに、Azure の大きな特徴 (差別化) の 1 つでもある「開発プラットフォームとしてのクラウド」のメリットが生きてきます。

 

Browser Only でデスクトップ並みの開発

まず 1 つ目のメリットは、こうしたクラウド上での開発 (初期段階でのプログラミング等) がデスクトップ上と変わらないほど快適にできる点です。
以前、「App Service Editor で Node.js アプリ開発」で紹介したように、ブラウザー エディターを使って直接クラウド上でコードの開発や管理をおこなうことができます。しかも、下記の通り生産性高く !

  • Azure Web App (旧 Azure WebSite) を作成したら、App Service Editor のエディターを開きます。
    (App Service Editor のエディターの表示手順は、「App Service Editor で Node.js アプリ開発」を参照してください。ここでは、手順を省略します。)
  • App Service Editor の Console を開いて (Ctrl-Shift-c)、下記のコマンドを入力して Node.js 用の Twilio ライブラリーをインストールします。(-g ではないので注意!)
npm install twilio
  • App Service Editor の Explorer から server.js を作成して、下記の通り入力します。
    Account SID, Auth Token, From (Phone Number) は Twilio のホーム から取得してください。(なお、Twilio の trial account を使用されている方は、To には決められた電話番号しか指定できないので注意してください。)
// This time, we don't use express (pure node.js)
var http = require('http');
var url = require('url');
var twilio = require('twilio');
http.createServer(function (req, res) {
  var urlopts = url.parse(req.url);
  if(urlopts.pathname == '/hello') {
    var twiRes = twilio.TwimlResponse();
    twiRes.say({language : 'ja-jp'}, 'こんにちは');
    res.writeHead(200, { 'Content-Type': 'text/xml' });
    res.end(twiRes.toString());
  }
  else if (urlopts.pathname == '/test') {
    var client = twilio(
      '<Account>',
      '<Auth Token>');
    client.makeCall({
      from: '+81-XX-XXXX-XXXX',  /* your account from */
      to: '+81-XX-XXXX-XXXX',  /* call to */
      url: 'http://<your domain>.azurewebsites.net/hello'
    }, function(error, data) {
      console.log('makeCall error');
    });
    res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
    res.end('電話をかけました!');
  }
  else {
    res.writeHead(404, { 'Content-Type': 'text/html; charset=utf-8' });        
    res.end('Not Found');
  }
}).listen(process.env.PORT);

もちろん、コードの作成中は Suggestion (Intellisense) も表示されますが、以下の通り TypeScript という AltJS を使うと、さらに大きな生産性を得ることができます。
TypeScript は JavaScript のスーパーセットであり (つまり、JavaScript で書いたコードは TypeScript のコードでもあります)、 TypeScript 独自の構文で無理に記述しなくても、JavaScript 単体でも恩恵を受けられますので、最初はこれだけでも充分です。一例を見てみましょう。

TypeScript は、既にコミュニティ (エコシステム) が充実しており、Node.js そのものや、Express フレームワーク、AngularJS、jquery などの多くの著名ライブラリーの定義ファイルが提供されています。
例えば、今回の場合、下記の通り Console で実行して、Node.js 用の TypeScript 定義ファイル (.d.ts ファイル) をインストールできます。(下記の -a は action の意味です。tsd query で検索をおこない、検索結果をインストールしています。)
これにより、node.d.ts という定義ファイルが typings というフォルダーの下にコピーされるはずです。

npm install tsd -g
tsd query node -a install

このダウンロードした定義ファイルを開発中のソース (上記の server.js) で参照するため、ソース (server.js) の先頭で「ref」と入力して Ctrl-Space を入力してみてください。「reference」を選択すると、定義ファイルを reference するコードのひな形が挿入されます。(注意 : Mac OS の方は、既定で Ctrl-Space キーとして Spotlight 検索が割り当てられているので、すみませんが、あらかじめ key map を変更しておいてください。)

つぎに、ファイル名の挿入箇所で Ctrl-Space を選択すると、ダウンロードされている TypeScript 定義ファイルの一覧が表示されるので、下図の通り node.d.ts を選択します。

この 2 ステップで、下図の通り、TypeScript 定義ファイルの参照 (コメントアウトされた reference タグ) が挿入されます。
すると、下図の通り、強い型付けによって、多くのオブジェクトでメンバーの候補が表示されるようになります。

なお、残念ながら、現時点 (2014 Oct) では Twilio API Libarary の TypeScript 定義ファイル (.d.ts) は提供されていませんが、今回のハンズオン用に twilio.d.ts (http://1drv.ms/1r5eLfW) を作成しましたので試しにダウンロードして使ってみてください。(定義ファイル作成では、一部、こちらのツールを使わせていただきました。)
このファイルを typings フォルダーに Upload し、上記同様、ソースで参照すると、下図の通り、Twilio API ライブラリーの関数についても Suggestion (Intellisense) が表示されるようになるでしょう。

慣れてきたら、コード自体も TypeScript で記述すると、さらに生産性が向上します。また、TypeScript コンパイルの際には、grunt と組み合わせて自動ビルドのフローを構成することもできます。(ただし、grunt を使用する場合は、Azure Web App の [Free] インスタンスではなく、[Basic] または [Standrd] インスタンスに設定しておいてください。[Free] では CPU の利用が大きく制限されています。)

 

すぐできる Remote Debugging

さて、ローカル環境が使えない場合のもう 1 つの問題がデバッグです。上記の場合、console.log で結果を出力するなどして簡単な確認はできますが、コードが複雑になってくるとかなりつらいですね。
Azure Web App では、「Azure による Node.js の Remote Debug (node-inspector 編)」で紹介したように、Node-inspector というデバッガー プロセスが Built-in されており、簡単な設定ですぐに Debug を開始できます。

ただし、この Debug 環境は、環境依存性があるので注意してください。
まず、Debug で使えるブラウザーは、いまのところ Chrome か Opera のみです。Mac OS の方も、どちらかをインストールしておいてしてください。(後述する Debug 実行の画面だけこれらのブラウザーを使用すれば良く、デバッグ対象のアプリの呼び出しはどのブラウザーからでも構いません。)
また、Azure の Debugger プロセスと Debug UI の間の通信として Web Socket を使用しています。このため、企業プロキシーの設定などで Web Socket がブロックされる場合は使用できません。(画面上部のバーだけが表示され、止まってしまいます。)

Debug 開始前の準備は、下記の 2 つだけです。

  • Azure Portal で [Configure] (構成) タブをクリックして、[Web Socket] をオン (On) にします。
  • App Service Editor で、ソース内にある Web.config の debuggingEnabled を true に変更します。(下記太字)
    なお、この Web.config は、一度 [Run] (Ctrl-F5) を実行しないと自動生成されないので、事前に一度 Run しておいてください。
<?xml version="1.0" encoding="utf-8"?>
<configuration>
   <system.webServer>
      . . .
      <rewrite>
         <rules>
          <rule name="NodeInspector" patternSyntax="ECMAScript" stopProcessing="true">          
            <match url="^server.js/debug[/]?" />
          </rule>

          . . .
         </rules>
      </rewrite>
      . . .

      <iisnode watchedFiles="web.config;*.js" debuggingEnabled="true" />
   </system.webServer>
</configuration>

上記の Web.config を見ると、/server.js/debug/ というパスが NodeInspector という rule に割り当てられているのがわかります。
つまり、ブラウザー (Chrome) で http://<your domain>.azurewebsites.net/server.js/debug/ に接続することで、Debug 用の UI が起動します。 (下図)

Debug UI では、左上の矢印をクリックすると、下図の通り、サーバー側のフォルダ階層やソース (server.js) が表示できます。

例えば、上述の server.js で、下記のソースの箇所の番号をクリックして breakpoint を設定します。

var urlopts = url.parse(req.url);

他のブラウザー (Safari でも何でも構いません) から、このアプリケーション (/test) にアクセスしてみましょう。すると、breakpoint を設定したコード (上記の行) で停止します。

F10 で 1 行進めると、urlopts の中に値が設定されますので、右の [Watch Expression] にこの変数を入力して Watch してみてください。pathname に「/test」と入っているのが確認できます。(下図)

続けて F8 で Continue すると、電話がかかり、再度、このソースの箇所 (breakpoint) で停止します。今度は、Twilio Service (https://api.twilio.com) から、TwiML を取得するために呼ばれています。

F10 で進めると、下図の通り、今度は、pathname に「/hello」と入っているのが確認できます。

このように、Twilio では、さきに電話をかけて、そのあとで TwiML を取得して音声通話しているのがわかります。Debug をおこなうと、変数の内部や、こうした処理の順序などが、非常によくわかりますね。(手の込んだデバッグでは、どんな風に動いているか、という検証も必要になります。)

Azure Web App には Git も入っていますので、このように Azure Web App を開発環境として使い、完成後に本番環境やソース管理のリポジトリーなどに push しても構いません。

なお、ハンズオンでは PHP を使った演習もしましたが、残念ながら PHP ではこうした Remote Debug は不可能ですのでご注意ください。

補足 :  PHP の Twilio API を使用する際は、「Azure Web App で curl を使って SSL (https) でアクセスする際の注意点」を参照してください。

 

※ 変更履歴

2015/03/26  Azure WebSite から Azure Web App に名称変更

2016/07/19  Visual Studio Online (Monaco) から App Service Editor に名称変更