作成者別アーカイブ: netweblog

R で日本語 OCR (tesseract)

今回実際に必要になった例だが、大量の伝票画像を電子化 (文字化) して、そこから情報を得るようなケースを考えてみたい。
伝票のヘッダー・明細や、項目の位置などは DNN を使ってあらかた学習できるかもしれないが、問題なのは、そこから文字を読み取る作業だ。

MNIST のサンプルなど、文字認識はいまや使い古された画像認識の手法だが、アルファベットや数字とは違い、日本語の場合、漢字も含め相当な数の文字があるため、一から教師あり画像を準備してトレーニングするのは (実際のビジネスでは) 現実的ではないからだ。

そこで、今回、tesseract の R のパッケージを使うことを検討してみた。

使い方

使い方は簡単だ。(今回は、Mac または PC 上の環境を想定する。)
まず、R コンソールなどから、tesseract のパッケージ (バイナリ) をインストールする。

install.packages("tesseract")

既定のバイナリには英語のデータしか入っていないので、Github から日本語の Trained データを落とせば良いが注意がある。
現在 Github にある最新は tesseract 4 のデータだが、CRAN のリポジトリにある tesseract 1.4 パッケージ (R のパッケージ) は tesseract 3.0X がベースになっているので (つまり、tesseract 4 ではない)、tesseract 3.04 ベースの Trained データを落としてくる必要があるのだ。
このため、現在は、以下の通り Branch を指定してダウンロード (Clone) する。

git clone --branch 3.04.00 https://github.com/tesseract-ocr/tessdata.git

下図の通り、各言語ごとの .traineddata がダウンロードされるので、この中の jpn.traineddata を、tesseract のインストール・ディレクトリである %userprofile%\Documents\R\win-library\3.4\tesseract\tessdata にコピーする。

以上で準備完了だ。

今回は Windows や Mac 上でのセットアップ方法だが、本番運用などで Linux に環境を作る際には下記を参照してほしい。

https://github.com/ropensci/tesseract

プログラミング

今回は、下図のサンプル画像を読み込ませよう。

下記の通り、R を使って、日本語用の Trained データ (エンジン) をロードし、これで OCR を行えば良い。

library(tesseract)
tseng <- tesseract(language = "jpn")
text <- ocr(
  "C:\\tmp\\sample.png",
  engine = tseng)
cat(text)

実行結果は、一見、良い感じだ。(下図参照)
今回学習済みデータ (.traineddata) をダウンロードして使ったが、オープンソースなので元データのカスタマイズなども可能らしい。

が、しかし、、、

いろいろやってみたが、今回のような機械で出力した伝票のようなケース (大きさも均一で、一定精度以上のフォント画像) には使えるが、精度の問題で、フリーな手書きの認識などにはまだ耐えられない感じだ。

例えば、文字の大きさ (解像度) を変えると認識率が大きく変わってしまうので、上記の例も、画像サイズによっては誤った結果が返ってくるので注意が必要だ。
また、下図のような手書きのデータもひらがなの「すばる」をまったく認識してくれない状況だった。(解像度を変えると、このひらがなの箇所の結果がいろいろ変化する状況。。。)

日本語については、現状はまだ用途を限定して使う感じだ。(英語の精度はすこぶる良いが)

 

How to create pfx file from crt and key (pem file)

すみません、自分用のメモ。

開発中のテスト目的などで https (SSL/TLS) の証明書設定が必要になった際、わざわざお金を払って証明書の申請をするのは面倒。
こうした場合、ご存じの通り、「Let’s Encrypt」が使用できるので是非。(日本の場合、「SSL なう !」から使用しても良い。)

さて、その際、Certificate の入った crt ファイル (pem 形式) と private key を含む key ファイルが別々に生成されるが、Azure などではご存じの通り .pfx ファイル (private key を含む Personal Information Change) が必要になるため、以下のコマンドで簡単に作成すれば良い。
(Windows 環境の場合、cygwin や git に付属の bash、Windows が提供する bash などを使うと良い。)

openssl pkcs12 -export -out server.pfx -inkey {.key including private key} -in {.crt including certificate}

新規にパスワードを入力するよう促される。

Json.NET (Newtonsoft.Json) の基本的な使い方

意外と困っている人が多いので、今さらだが、まとめ。

JToken を使った基本操作

まず大前提として、使用するオブジェクト構成の理解が超重要。
構成は非常に簡単だ。オブジェクト (例: { "firstname" : "Subaru", "surname" : "Kokubun" })JObject、配列 (例 : [ { "surname" : "Kokubun" }, { "surname" : "Matsuzaki" }, ... ] ) は JArray、文字列や数値などのプリミティブは JValue、そして、これらに共通の親 (継承元) が JToken だ。(この構成を理解していないと、このあとの文書は理解できない !)

例えば、HTTP (REST API) を呼び出して、

{
  "results": [
    {
      "id": 1,
      "name": "pc",
      "price": 100000
    },
    {
      "id": 2,
      "name": "mouse",
      "price": 5000
    },
    {
      "id": 3,
      "name": "phone",
      "price": 30000
    }
  ]
}

の値が返ってきた場合、下記のサンプルコードの通りオブジェクト間を変換して最終的な値を取り出せる。

このサンプルでは、まず、{ "results" : ... } 全体は JObject で受け取り (10 – 11 行目)、results の中は配列なので JArray で受け取り (17 行目)、その配列の要素 1 つ 1 つは object なので JObject で受け取り (18 行目)、その中の値を JValue で受け取って (20, 22 行目)、最後に JValue の Value プロパティで .NET の object 型 (string, long, など) として中身を取得している (21, 23 行目)。

なお、いったん継承元である JToken で受けて、中身の構成に応じて JObject や JArray にキャストしても良い。(以降も同様)

using System.Net.Http;
using System.Net.Http.Headers;
using System.Net.Http.Formatting;
using Newtonsoft.Json.Linq;

HttpClient cl = new HttpClient();
cl.DefaultRequestHeaders.Accept.Add(
  new MediaTypeWithQualityHeaderValue("application/json"));
HttpResponseMessage res = await cl.GetAsync("http://example.com/someapi");
JObject jres =
  await res.Content.ReadAsAsync<JObject>(new[] { new JsonMediaTypeFormatter() });

// The following result is :
// name is pc, price is 100000.
// name is mouse, price is 5000.
// name is phone, price is 30000.
JArray jarr = (JArray)jres["results"];
foreach (JObject jobj in jarr)
{
  JValue nameValue = (JValue)jobj["name"];
  string name = (string)nameValue.Value;
  JValue priceValue = (JValue)jobj["price"];
  long price = (long)priceValue.Value;
  Console.WriteLine($"name is {name}, price is {price}.");
}

なお、上記の 10 行目で受け取った jres で、jres.ToString() とすれば、Json 全体が (改行や空白など) きれいにフォーマットされた文字列として取得できる。

例えば、下記のサンプルコードのように、いったん JToken (JObject, JArray, JValue など) を使ってオブジェクトを組み立て、それを 14 行目のように HTTP Body の文字列に変換して HTTP (REST) を投げることができる。

using System.Net.Http;
using Newtonsoft.Json.Linq;

// Sent body is :
// {
//   "eventname": "Developer Meeting",
//   "eventdate": "2016-10-24T17:30:00"
// }
JObject newObj = new JObject();
newObj.Add("eventname", new JValue("Developer Meeting"));
newObj.Add("eventdate", new JValue(new DateTime(2016, 10, 24, 17, 30, 0)));
HttpClient cl = new HttpClient();
HttpContent jsonContent = new StringContent(
  newObj.ToString(),
  Encoding.UTF8,
  "application/json");
HttpResponseMessage res = cl.PostAsync(uri, jsonContent).Result;

逆に、文字列から Json.NET の Object (JObject, JArray, 等々) を生成するには、Parse メソッドを使う。
例えば、上述の HTTP (REST API) から Json.NET のオブジェクトを取得するサンプル コードは、下記の通りいったん string で受けて (9 -10 行目)、それを Parse メソッドでオブジェクトに変換する方法で記述しても良い。(以降の処理は、上述と同じソースコードになるので省略。)

using System.Net.Http;
using System.Net.Http.Headers;
using Newtonsoft.Json.Linq;

HttpClient cl = new HttpClient();
cl.DefaultRequestHeaders.Accept.Add(
  new MediaTypeWithQualityHeaderValue("application/json"));
HttpResponseMessage res = await cl.GetAsync("http://example.com/someapi");
string jstr =
  await res.Content.ReadAsStringAsync();
JObject jres = JObject.Parse(jstr);
...

なお、今回は、返ってくる結果が { "results" : ... } なので JObject で受けているが (上記の 11 行目)、下記の例のように、受け取る内容によって型を変える必要があるので注意してほしい。

using Newtonsoft.Json.Linq;

var obj = JObject.Parse(@"{ ""firstname"" : ""Subaru"", ""surname"" : ""Kokubun"" }");
// Result : "My family name is Kokubun."
Console.WriteLine($"My family name is {obj["surname"]}.");

var arr = JArray.Parse(@"[ { ""surname"" : ""Kokubun"" }, { ""surname"" : ""Matsuzaki"" } ]");
// Result : "Kokubun", "Matsuzaki"
foreach(var item in arr)
{
  Console.WriteLine(item["surname"]);
}

LINQ to JSON

.NET プログラマーなら IEnumerable のオブジェクトで LINQ to Object のクエリー (Select, Where など) が使えることは知っていると思うが、上記で紹介した JToken (JObject, JArray, など) も、for 文をわざわざ使わず、LINQ to JSON を使ってクエリーが実行できる。

本投稿ではこの説明は割愛するが、詳細は「Json.NET Documentation : LINQ to Json」を参照してほしい。

JsonConvert による Serialize / Desirialize

なお、Json のオブジェクトと文字列の変換には、もう 1 つ別の方法もある。

Json.NET 以前の DataContractJsonSerializer の頃から使われていた手法だが、対応するクラス構成を作成しておき、一気に Serialize / Deserialize する方法だ。
下記のサンプルコードは、上述の { "results" : ... } を変換するサンプルをこの方法で書き直したコードだ。

変換後のオブジェクトは自然なクラスなので扱いやすい一方、この方法で注意してほしいのは、あくまで静的に構成が決まっている場合しか使えないという点。中身を確認しながら動的に処理をするケースでは使えないので注意してほしい。

using System.Net.Http;
using System.Net.Http.Headers;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json;

[JsonObject]
public class ReturnedData
{
  [JsonProperty("results")]
  public ItemData[] Results { get; set; }
}

[JsonObject]
public class ItemData
{
  [JsonProperty("id")]
  public int Id { get; set; }
  [JsonProperty("name")]
  public string Name { get; set; }
  [JsonProperty("price")]
  public int Price { get; set; }
}
...

// メインの処理
HttpClient cl = new HttpClient();
cl.DefaultRequestHeaders.Accept.Add(
  new MediaTypeWithQualityHeaderValue("application/json"));
HttpResponseMessage res = await cl.GetAsync("http://example.com/someapi");
string jstr =
  await res.Content.ReadAsStringAsync();
ReturnedData jres = JsonConvert.DeserializeObject<ReturnedData>(jstr);
...

Json.NET は、いまや空気のように普通に使われるので、この程度のことは知っておいて損はない。

 

ngrok を ASP.NET で使う (Debug と Capture)

昨今の WebHook や BOT 開発のように、インターネット上で Inbound 呼び出しされる Web アプリの Debug や HTTP Protocol (Raw) Capture をする場合に欠かせない ngrok を ASP.NET と組み合わせて使う場合に必要な設定と動作確認方法を簡単に紹介する。

ASP.NET プロジェクトの設定

ngrok を使うには、まず、ASP.NET のプロジェクトに以下の設定をおこなっておく。

まず、Visual Studio のプロジェクトのプロパティで、[すべてのユーザーにサーバー設定を適用] (Apply server settings to all users) が選択されていることを確認する。(既定では選択されているはず)

apply_settings

開いている Visual Studio ソリューションのフォルダーの .vs\config にある applicationhost.config (IIS Express が使用) を開き、下記の binding を追加しておく。

<configuration>
  . . .

  <system.applicationHost>
    . . .

    <sites>
      . . .

      <site name="WebApplication1" id="2">
        <application path="/" applicationPool="Clr4IntegratedAppPool">
          <virtualDirectory path="/" physicalPath="C:\Demo\WebApplication1\WebApplication1" />
        </application>
        <bindings>
          <binding protocol="http" bindingInformation="*:60587:localhost" />
          <binding protocol="http" bindingInformation="*:60587:*" />
        </bindings>
      </site>
      . . .

ngrok の実行と動作確認

上記の ASP.NET Web アプリケーションを localhost で Debug 実行したら、ngrok をダウンロード (および展開) して、コマンド プロンプトから下記の通り実行する。(60587 は、この ASP.NET Web アプリケーションが実行されている IIS Express でのポート番号)
特に ASP.NET では、Host ヘッダーをちゃんと指定しないと Bad Request (400) になるので、下記の通り指定する。

ngrok http 60587 --host-header="localhost:60587"

上記の実行結果として下記の通り表示されるが、これは、インターネット上の http://ae8b8019.ngrok.io のアドレスが Tunnel されて http://localhost:60587 に行くよ、という意味。

Tunnel Status                 online
Version                       2.0.25/2.1.1
Region                        United States (us)
Web Interface                 http://127.0.0.1:4040
Forwarding                    http://ae8b8019.ngrok.io -> localhost:60587
Forwarding                    https://ae8b8019.ngrok.io -> localhost:60587

Connections                   ttl     opn     rt1     rt5     p50     p90
                              0       0       0.00    0.00    0.00    0.00

そこで、あとは、ブレークポイントを置いて、http://ae8b8019.ngrok.io に接続すると、Tunnel されてきて、ちゃんとブレークポイントで止まる。

HTTP Capture

Web ブラウザを使って上記出力の http://127.0.0.1:4040 (http://localhost:4040) を表示すると、下図の通り HTTP Protocol (Raw) を Capture できる。
WebHook や BOT で、どのような request が来ているか簡単に確認可能だ。

HTTP_Capture

BOT 開発のように双方向連携をおこなうものは、この ngrok と Fiddler を一緒に使えば、inbound と outbound の双方がキャプチャーできるので、最強のデバッグ環境を手に入れることができる。

 

Office アドイン (JavaScript API for Office) をはじめる人の必読記事

Web の Office Add-in (Excel, Word, PowerPoint, Outlook, etc) をはじめる人のために「Office Add-ins とは ?」、「何ができるの ?」、「どう作るの ?」などをザッと読み物として短くまとめたので告知。(もう何ヶ月か前に記事追加したけど、今更告知ですみません 。。。)

連載1回「JavaScriptで誰でも簡単に作って稼げる Officeアドインとは?」
http://www.atmarkit.co.jp/ait/articles/1301/25/news063.html

連載2回「Office アドインの作り方と、できること/できないこと」
http://www.atmarkit.co.jp/ait/articles/1303/04/news091.html

連載3回「Office 2016 における Office アドイン進化のポイント」
http://www.atmarkit.co.jp/ait/articles/1601/15/news026.html

Office アドインは、Office が掲げるマルチ プラットフォーム戦略 (Mac でも、iPad でも、スマホでも、すべての人に快適に Office を使わせる戦略) に沿ったこれからの時代のアドインだ。

 

PowerShell Gallery の Module の利用方法 (よく使うコマンド集)

完全な自分のためのメモ。

PowerShell Gallery の Module を探したり (Find)、持って来たり (Install)、使えるようにしたり (Import) といった、最低限おぼえておくべき、よく使うコマンドを列挙する。

PowerShell Gallery から希望する Module を探す場合

Find-Module | Where-Object { $_.Name -match "{search string}" }

探した Module で、利用可能な Version (すべて) を表示する場合

Find-Module -Name {module name} -AllVersions

見つけた Module をマシンに Install する (ローカルに持ってくる) 場合

Install-Module {module name}

特定の Version を決めうちで Install する場合

Install-Module {module name} -RequiredVersion {version}

マシンに Install 済の使用可能な Module の一覧を取得する場合

Get-Module -ListAvailable

Install された Module (つまり、その Module 内の Command) を使える状態にする場合
(ただし、Windows PowerShell のウィンドウを閉じてセッションを終了したら終わり。いつも使う場合は、毎回 Windows PowerShell 起動時に、このコマンドを実行すること。)

Import-Module {module name}

Import 済の有効な (使える) Module の一覧を取得する場合

Get-Module

その Module 内で使える Command (Cmdlet) の一覧を取得する場合

Get-Command -Module {module name}

利用可能な Command を文字列で絞り込む場合

Get-Command *{search string}* -Module {module name}

Module をマシンから Uninstall する場合

Uninstall-Module {module name}

ただし、上記は単一の Module だけを追加・削除するので注意。
例えば、Web PI などインストーラー経由で複数の Module をまとめてインストールするケースでは、[プログラムと機能] (programs and features) などから Uninstall するほうが良い。そうすると、Install 時に入れた関連する Module すべてを削除してくれるためだ。(例えば、Azure PowerShell など)

Azure で Docker Cluster を構成するさまざまな方法

ここでは、Azure を使ってマルチ インスタンスや Cluster を構成するさまざまな方法をまとめたい。(Swarm, Mesos, Kubernetes 自体の構成方法の話ではなく、Azure に構成する際の考え方。)
いろいろと使える情報 (サンプルの構成ファイルなど) もあるので、それらの紹介 (リンク) もあわせて以下に記載する。

Azure + Docker の超基本

クラスタの話をする前に、まず、Azure で Docker を使う場合の超基本的な話を整理しておく。(知らない人のために)

Azure 上で Docker を使う場合、ざっくり下記の 2 つが使える。
(この他に、Docker 社が提供している Docker Cloud、Mesosphere が提供している DCOS でも、Azure を正式サポートしているが、これらについては本投稿の対象外とする。)

  • 普通に Azure Virtual Machine (IaaS, VM) を立ち上げて Container を立てる方法
  • Azure Container Service (ACS) と呼ばれるコンテナのホスティングを管理するサービス型の Azure リソースを使って Container 作成と管理をおこなう方法。
    やや乱暴な説明だが、AWS, Google を使っている人は、Amazon EC2 Container Service (ECS), Google Container Engine (GCE) と同じ位置づけのいわゆる「サービス」型のホスティング環境と思えば良い。(内部的な話は後述)

前者の VM (Azure Virtual Machine) を使うケースについて、Azure Virtual Machine (IaaS 環境) に docker container を構成するには、直球で Virtual Machine に docker のダウンロード・インストールをおこなっても良いが、Azure の場合は、Virtual Machine Extension と呼ばれる「VM に対する追加の構成要素 (リソースの 1 つ)」を使って docker のインストールと構成が可能だ。(よって、Azure 上で Docker を扱う場合、この “Extension” という言葉は良く出てくることになる。)
この構成をおこなうには、大きく下記の 3 通りの方法がある。

まずもっとも一般的な方法として、Azure CLI (Command-line Interface) のコマンド ユーティリティ (Azure 専用のコマンド ユーティリティ) を使う方法がある。(Azure CLI の azure vm docker create コマンドですぐに構成できる。)

Azure CLI を使って Docker VM Extension を構成する方法 :
https://azure.microsoft.com/en-us/documentation/articles/virtual-machines-docker-with-xplat-cli/

VM + Docker の 2 番目の方法として、ARM (Azure Resource Manager) の Resource template と呼ばれる展開用の json ファイルを使って、Virtual Machine Extension を (json で) 記述する方法がある。
VM Extension にはさまざまな種類があるが、その 1 つとして Docker Extension と呼ばれるものがあるので、これを使えば簡単に構成できるようになっている。(なお、sh を実行するための VM Extension も用意されているので、この sh を使って Docker を構成する方法もある。)
展開をコード化して自動化したい場合にはすごく使える方法なので、上記 (コマンド ユーティリティ) で展開がうまくいったら、さいごに ARM の template としてコード化してみると良いだろう。(なお、このあと紹介する template を見る前に、最低限、ここで紹介している How-to 程度は理解できるようにしておいてほしい。)

Docker を構成した resource template の例 :
(下記の azuredeploy.json が開始点だ。以降も同様。。。)
https://github.com/Azure/azure-quickstart-templates/tree/master/docker-simple-on-ubuntu

抜粋すると以下のような感じだ。(ハイライト部分が DockerExtension)

. . .

{
  "apiVersion": "2015-06-15",
  "type": "Microsoft.Compute/virtualMachines",
  "name": "vmtest01",
  "location": "East US",
  . . . (途中省略)
},
{
  "type": "Microsoft.Compute/virtualMachines/extensions",
  "name": "testvm01/dockerex01",
  "apiVersion": "2015-06-15",
  "location": "East US",
  "dependsOn": [
    "Microsoft.Compute/virtualMachines/vmtest01"
  ],
  "properties": {
    "publisher": "Microsoft.Azure.Extensions",
    "type": "DockerExtension",
    "typeHandlerVersion": "1.0",
    "autoUpgradeMinorVersion": true,
    "settings": {}
  }
}

. . .

VM + Docker のおまけとして、docker 標準の client でも構成可能だ。
Azure の Docker サポートは MS が勝手に対応しているのではなく、Azure と Docker の双方が対応している形で、docker driver の 1 つとして azure がサポートされているからだ。上述の ARM のような “Azure かぶれ” な方法ではなく、オープンなツールやフレームワークなどから呼び出す場合は、この方法が使えるだろう。
(この具体的方法はここでは省略するが、下記ブログを参照してほしい。)

Docker client のみを使って Azure 上の Docker container を作成する例 :
https://blogs.msdn.microsoft.com/tsmatsuz/2015/10/08/azure-docker-machine-on-linux-from-pc-mac/

また、Azure Container Service (ACS) については、本投稿でテーマとしている「Cluster」の話と大きく関連しているので後述する。

Compose

まずは、IaaS v2 (Azure Virtual Machine) を使った構成を中心に整理しよう。

最初に、クラスタ (Cluster) ではなく、単なるマルチインスタンス (複数台) 構成の話だが、Azure では docker compose も柔軟に扱えるようになっている。

Azure の ARM (Azure Resource Manager) template の場合は、上述の Docker Extension で Compose の記述がサポートされていて、下記の通り ARM template の json に混ぜて構成が可能だ。(なお、コマンドを使った場合は「Azure VM Docker Compose Quickstart」で記載しているように普通に Compose を構成するだけ。)

Docker Compose を使って WordPress と MySQL を構成する template の例 :
https://github.com/Azure/azure-quickstart-templates/tree/master/docker-wordpress-mysql

抜粋すると以下のような感じだ。

. . .
{
  "apiVersion": "2015-06-15",
  "type": "Microsoft.Compute/virtualMachines",
  "name": "testvm01",
  "location": "East US",
  . . .
},
{
  "type": "Microsoft.Compute/virtualMachines/extensions",
  "name": "testvm01/dockerex01",
  "apiVersion": "2015-06-15",
  "location": "East US",
  "dependsOn": [
    "Microsoft.Compute/virtualMachines/rtestvm01"
  ],
  "properties": {
    "publisher": "Microsoft.Azure.Extensions",
    "type": "DockerExtension",
    "typeHandlerVersion": "1.0",
    "autoUpgradeMinorVersion": true,
    . . .
    "settings": {
      "compose": {
        "db": {
          "image": "mysql",
          "ports": [
            "3306:3306"
          ],
          "volumes": [
            "/var/lib/mysql:/var/lib/mysql"
          ],
          "environment": [
            "MYSQL_ROOT_PASSWORD"
          ]
        },
        "wordpress": {
          "image": "wordpress",
          "ports": [
            "80:80"
          ],
          "links": [
            "db:mysql"
          ]
        }
      }
    }
  }
}
. . .

Docker Compose の scale コマンドで、起動する container の数も柔軟に制御できる。

分散クラスタ

Production を考える場合、単に複数のコンテナが起動していれば良いというわけではない。
複数の物理マシンに点在する複数のコンテナを柔軟に管理するために、Swarm、Mesos などのオーケストレーション エンジンを使うのが一般的だ。

Azure VM (IaaS v2) を使って Docker + Swarm, Mesos, Marathon 等で分散クラスタを構成する場合は、専用の Extension はないので、上述の Compose を使うか、sh の実行をおこなうことになる。(コマンドの場合の例は「Azure Virtual Machine : How to use docker with swarm」参照。)

例えば、下記は、ARM (Azure Resource Manager) template で、上述した Compose を使って Swarm をインストールしている例だ。

Docker Compose と使って Swarm を構成する template の例 :
https://github.com/Azure/azure-quickstart-templates/tree/master/docker-swarm-cluster

Azure VM Extension には Linux の sh を実行する Extension (CustomScriptForLinux Extension) もあるので、Compose を使わずにすべて sh だけで構成する方法もある。
例えば、下記は mesos, zookeeper 等も含む構成を sh で記述しているが (適宜パラメータ化をおこなっている)、こうした sh を CustomScriptForLinux Extension を使って実行すれば良い。(下記では docker 自体も sh でインストールしている。)

#!/bin/bash

###########################################################
# Configure Mesos One Box
#
# This installs the following components
# - zookeepr
# - mesos master
# - marathon
# - mesos agent
###########################################################

set -x

echo "starting mesos cluster configuration"
date
ps ax

SWARM_VERSION="1.0.0-rc2"

#############
# Parameters
#############

MASTERCOUNT=$1
MASTERMODE=$2
MASTERPREFIX=$3
SWARMENABLED=$4
MARATHONENABLED=$5
CHRONOSENABLED=$6
ACCOUNTNAME=$7
set +x
ACCOUNTKEY=$8
set -x
AZUREUSER=$9
SSHKEY=${10}
HOMEDIR="/home/$AZUREUSER"
VMNAME=`hostname`
VMNUMBER=`echo $VMNAME | sed 's/.*[^0-9]\([0-9]\+\)*$/\1/'`
VMPREFIX=`echo $VMNAME | sed 's/\(.*[^0-9]\)*[0-9]\+$/\1/'`

echo "Master Count: $MASTERCOUNT"
echo "Master Mode: $MASTERMODE"
echo "Master Prefix: $MASTERPREFIX"
echo "vmname: $VMNAME"
echo "VMNUMBER: $VMNUMBER, VMPREFIX: $VMPREFIX"
echo "SWARMENABLED: $SWARMENABLED, MARATHONENABLED: $MARATHONENABLED, CHRONOSENABLED: $CHRONOSENABLED"
echo "ACCOUNTNAME: $ACCOUNTNAME"

###################
# setup ssh access
###################

SSHDIR=$HOMEDIR/.ssh
AUTHFILE=$SSHDIR/authorized_keys
if [ `echo $SSHKEY | sed 's/^\(ssh-rsa \).*/\1/'` == "ssh-rsa" ] ; then
  if [ ! -d $SSHDIR ] ; then
    sudo -i -u $AZUREUSER mkdir $SSHDIR
    sudo -i -u $AZUREUSER chmod 700 $SSHDIR
  fi

  if [ ! -e $AUTHFILE ] ; then
    sudo -i -u $AZUREUSER touch $AUTHFILE
    sudo -i -u $AZUREUSER chmod 600 $AUTHFILE
  fi
  echo $SSHKEY | sudo -i -u $AZUREUSER tee -a $AUTHFILE
else
  echo "no valid key data"
fi

###################
# Common Functions
###################

ensureAzureNetwork()
{
  # ensure the host name is resolvable
  hostResolveHealthy=1
  for i in {1..120}; do
    host $VMNAME
    if [ $? -eq 0 ]
    then
      # hostname has been found continue
      hostResolveHealthy=0
      echo "the host name resolves"
      break
    fi
    sleep 1
  done
  if [ $hostResolveHealthy -ne 0 ]
  then
    echo "host name does not resolve, aborting install"
    exit 1
  fi

  # ensure the network works
  networkHealthy=1
  for i in {1..12}; do
    wget -O/dev/null http://bing.com
    if [ $? -eq 0 ]
    then
      # hostname has been found continue
      networkHealthy=0
      echo "the network is healthy"
      break
    fi
    sleep 10
  done
  if [ $networkHealthy -ne 0 ]
  then
    echo "the network is not healthy, aborting install"
    ifconfig
    ip a
    exit 2
  fi
  # ensure the host ip can resolve
  networkHealthy=1
  for i in {1..120}; do
    hostname -i
    if [ $? -eq 0 ]
    then
      # hostname has been found continue
      networkHealthy=0
      echo "the network is healthy"
      break
    fi
    sleep 1
  done
  if [ $networkHealthy -ne 0 ]
  then
    echo "the network is not healthy, cannot resolve ip address, aborting install"
    ifconfig
    ip a
    exit 2
  fi
}
ensureAzureNetwork
HOSTADDR=`hostname -i`

ismaster ()
{
  if [ "$MASTERPREFIX" == "$VMPREFIX" ]
  then
    return 0
  else
    return 1
  fi
}
if ismaster ; then
  echo "this node is a master"
fi

isagent()
{
  if ismaster ; then
    if [ "$MASTERMODE" == "masters-are-agents" ]
    then
      return 0
    else
      return 1
    fi
  else
    return 0
  fi
}
if isagent ; then
  echo "this node is an agent"
fi

zkhosts()
{
  zkhosts=""
  for i in `seq 1 $MASTERCOUNT` ;
  do
    if [ "$i" -gt "1" ]
    then
      zkhosts="${zkhosts},"
    fi

    IPADDR=`getent hosts ${MASTERPREFIX}${i} | awk '{ print $1 }'`
    zkhosts="${zkhosts}${IPADDR}:2181"
    # due to mesos team experience ip addresses are chosen over dns names
    #zkhosts="${zkhosts}${MASTERPREFIX}${i}:2181"
  done
  echo $zkhosts
}

zkconfig()
{
  postfix="$1"
  zkhosts=$(zkhosts)
  zkconfigstr="zk://${zkhosts}/${postfix}"
  echo $zkconfigstr
}

######################
# resolve self in DNS
######################

echo "$HOSTADDR $VMNAME" | sudo tee -a /etc/hosts

################
# Install Docker
################

echo "Installing and configuring docker and swarm"

time wget -qO- https://get.docker.com | sh

# Start Docker and listen on :2375 (no auth, but in vnet)
echo 'DOCKER_OPTS="-H unix:///var/run/docker.sock -H 0.0.0.0:2375"' | sudo tee /etc/default/docker
# the following insecure registry is for OMS
echo 'DOCKER_OPTS="$DOCKER_OPTS --insecure-registry 137.135.93.9"' | sudo tee -a /etc/default/docker
sudo service docker restart

ensureDocker()
{
  # ensure that docker is healthy
  dockerHealthy=1
  for i in {1..3}; do
    sudo docker info
    if [ $? -eq 0 ]
    then
      # hostname has been found continue
      dockerHealthy=0
      echo "Docker is healthy"
      sudo docker ps -a
      break
    fi
    sleep 10
  done
  if [ $dockerHealthy -ne 0 ]
  then
    echo "Docker is not healthy"
  fi
}
ensureDocker

############
# setup OMS
############

if [ $ACCOUNTNAME != "none" ]
then
  set +x
  EPSTRING="DefaultEndpointsProtocol=https;AccountName=${ACCOUNTNAME};AccountKey=${ACCOUNTKEY}"
  docker run --restart=always -d 137.135.93.9/msdockeragentv3 http://${VMNAME}:2375 "${EPSTRING}"
  set -x
fi

##################
# Install Mesos
##################

sudo apt-key adv --keyserver keyserver.ubuntu.com --recv E56151BF
DISTRO=$(lsb_release -is | tr '[:upper:]' '[:lower:]')
CODENAME=$(lsb_release -cs)
echo "deb http://repos.mesosphere.io/${DISTRO} ${CODENAME} main" | sudo tee /etc/apt/sources.list.d/mesosphere.list
time sudo add-apt-repository -y ppa:openjdk-r/ppa
time sudo apt-get -y update
time sudo apt-get -y install openjdk-8-jre-headless
if ismaster ; then
  time sudo apt-get -y --force-yes install mesosphere
else
  time sudo apt-get -y --force-yes install mesos
fi

#########################
# Configure ZooKeeper
#########################

zkmesosconfig=$(zkconfig "mesos")
echo $zkmesosconfig | sudo tee /etc/mesos/zk

if ismaster ; then
  echo $VMNUMBER | sudo tee /etc/zookeeper/conf/myid
  for i in `seq 1 $MASTERCOUNT` ;
  do
    IPADDR=`getent hosts ${MASTERPREFIX}${i} | awk '{ print $1 }'`
    echo "server.${i}=${IPADDR}:2888:3888" | sudo tee -a /etc/zookeeper/conf/zoo.cfg
    # due to mesos team experience ip addresses are chosen over dns names
    #echo "server.${i}=${MASTERPREFIX}${i}:2888:3888" | sudo tee -a /etc/zookeeper/conf/zoo.cfg
  done
fi

#########################################
# Configure Mesos Master and Frameworks
#########################################
if ismaster ; then
  quorum=`expr $MASTERCOUNT / 2 + 1`
  echo $quorum | sudo tee /etc/mesos-master/quorum
  hostname -i | sudo tee /etc/mesos-master/ip
  hostname | sudo tee /etc/mesos-master/hostname
  echo 'Mesos Cluster on Microsoft Azure' | sudo tee /etc/mesos-master/cluster
fi

if ismaster  && [ "$MARATHONENABLED" == "true" ] ; then
  # setup marathon
  sudo mkdir -p /etc/marathon/conf
  sudo cp /etc/mesos-master/hostname /etc/marathon/conf
  sudo cp /etc/mesos/zk /etc/marathon/conf/master
  zkmarathonconfig=$(zkconfig "marathon")
  echo $zkmarathonconfig | sudo tee /etc/marathon/conf/zk
  # enable marathon to failover tasks to other nodes immediately
  echo 0 | sudo tee /etc/marathon/conf/failover_timeout
  #echo false | sudo tee /etc/marathon/conf/checkpoint
fi

#########################################
# Configure Mesos Master and Frameworks
#########################################
if ismaster ; then
  # Download and install mesos-dns
  sudo mkdir -p /usr/local/mesos-dns
  sudo wget https://github.com/mesosphere/mesos-dns/releases/download/v0.2.0/mesos-dns-v0.2.0-linux-amd64.tgz
  sudo tar zxvf mesos-dns-v0.2.0-linux-amd64.tgz
  sudo mv mesos-dns-v0.2.0-linux-amd64 /usr/local/mesos-dns/mesos-dns
  RESOLVER=`cat /etc/resolv.conf | grep nameserver | tail -n 1 | awk '{print $2}'`

  echo "
{
  \"zk\": \"zk://127.0.0.1:2181/mesos\",
  \"refreshSeconds\": 1,
  \"ttl\": 0,
  \"domain\": \"mesos\",
  \"port\": 53,
  \"timeout\": 1,
  \"listener\": \"0.0.0.0\",
  \"email\": \"root.mesos-dns.mesos\",
  \"resolvers\": [\"$RESOLVER\"]
}
" > mesos-dns.json
  sudo mv mesos-dns.json /usr/local/mesos-dns/mesos-dns.json

  echo "
description \"mesos dns\"
# Start just after the System-V jobs (rc) to ensure networking and zookeeper
# are started. This is as simple as possible to ensure compatibility with
# Ubuntu, Debian, CentOS, and RHEL distros. See:
# http://upstart.ubuntu.com/cookbook/#standard-idioms
start on stopped rc RUNLEVEL=[2345]
respawn
exec /usr/local/mesos-dns/mesos-dns -config /usr/local/mesos-dns/mesos-dns.json" > mesos-dns.conf
  sudo mv mesos-dns.conf /etc/init
  sudo service mesos-dns start
fi


#########################
# Configure Mesos Agent
#########################
if isagent ; then
  # Add docker containerizer
  echo "docker,mesos" | sudo tee /etc/mesos-slave/containerizers
  # Add resources configuration
  if ismaster ; then
    echo "ports:[1-21,23-4399,4401-5049,5052-8079,8081-32000]" | sudo tee /etc/mesos-slave/resources
  else
    echo "ports:[1-21,23-5050,5052-32000]" | sudo tee /etc/mesos-slave/resources
  fi
  hostname -i | sudo tee /etc/mesos-slave/ip
  hostname | sudo tee /etc/mesos-slave/hostname

  # Add mesos-dns IP addresses at the top of resolv.conf
  RESOLV_TMP=resolv.conf.temp
  rm -f $RESOLV_TMP
  for i in `seq $MASTERCOUNT` ; do
      echo nameserver `getent hosts ${MASTERPREFIX}${i} | awk '{ print $1 }'` >> $RESOLV_TMP
  done

  cat /etc/resolv.conf >> $RESOLV_TMP
  mv $RESOLV_TMP /etc/resolv.conf
fi

##############################################
# configure init rules restart all processes
##############################################

echo "(re)starting mesos and framework processes"
if ismaster ; then
  sudo service zookeeper restart
  sudo service mesos-master start
  if [ "$MARATHONENABLED" == "true" ] ; then
    sudo service marathon start
  fi
  if [ "$CHRONOSENABLED" == "true" ] ; then
    sudo service chronos start
  fi
else
  echo manual | sudo tee /etc/init/zookeeper.override
  sudo service zookeeper stop
  echo manual | sudo tee /etc/init/mesos-master.override
  sudo service mesos-master stop
fi

if isagent ; then
  echo "starting mesos-slave"
  sudo service mesos-slave start
  echo "completed starting mesos-slave with code $?"
else
  echo manual | sudo tee /etc/init/mesos-slave.override
  sudo service mesos-slave stop
fi

echo "processes after restarting mesos"
ps ax

# Run swarm manager container on port 2376 (no auth)
if ismaster && [ "$SWARMENABLED" == "true" ] ; then
  echo "starting docker swarm:$SWARM_VERSION"
  echo "sleep to give master time to come up"
  sleep 10
  echo sudo docker run -d -e SWARM_MESOS_USER=root \
      --restart=always \
      -p 2376:2375 -p 3375:3375 swarm:$SWARM_VERSION manage \
      -c mesos-experimental \
      --cluster-opt mesos.address=0.0.0.0 \
      --cluster-opt mesos.port=3375 $zkmesosconfig
  sudo docker run -d -e SWARM_MESOS_USER=root \
      --restart=always \
      -p 2376:2375 -p 3375:3375 swarm:$SWARM_VERSION manage \
      -c mesos-experimental \
      --cluster-opt mesos.address=0.0.0.0 \
      --cluster-opt mesos.port=3375 $zkmesosconfig
  sudo docker ps
  echo "completed starting docker swarm"
fi
echo "processes at end of script"
ps ax
echo "Finished installing and configuring docker and swarm"
date
echo "completed mesos cluster configuration"

Kubernetes

上記では技術計算等で扱う大規模分散構成のようなクラスタを想定したが、例えば、同一構成の展開・管理では Kubernetes クラスタと組み合わせた Scaling も組み合わせたくなるだろう。(通常、Azure Virtual Machine で Web や AP の負荷分散をおこなう場合には Azure 標準の Load Balancer リソースが使えるが、ここに Docker が入ってきた場合には話が変わってくる。)
この場合も上記と同じ話で、compose か sh で書いて展開することになる。

なお、残念ながら Azure Quickstart Templates にこのサンプルはあがっていないが、下記の Kubernetes の Github に Azure の場合の構成方法について書かれているので、これを参考にスクリプト化などしてほしい。

[Kubernetes on Azure] Start Guide :
https://github.com/kubernetes/kubernetes/blob/release-1.1/docs/getting-started-guides/coreos/azure/README.md

Azure Container Service (ACS)

※ 以降、2016/02/18 追記

冒頭で述べた Azure Container Service (ACS) を使うと、自身で明示的に VM を立てずに Service のクラスタを立ち上げて管理できる。Swarm、Apache Mesos、さらに Marathon などに対応しているので、上述の分散クラスタを扱う場合にもって来いなサービスだ。(むしろ、クラスタを立てずに使うようなシンプルな使い方はできない。)

現在、下記の ARM template を使ってクラスタ (サービス インスタンス) を配置 (作成) するが、内部では結果的に、Virtual Machine Scale Sets (VM Scale Sets) や Load Balancer のリソース が起動している。(利用者にとって単一のサービス インスタンスとして管理できるだけで、実際には VM などの IaaS 環境が使われているということ。ECS 同様、GCE のような Full Managed なサービスではない。)
Amazon EC2 Container Service (ECS)、Google Container Engine (GCE) 同様、今後は Auto Scale に対応しようということだろう。

Mesos による ACS (Azure Container Service) クラスタの配置
https://github.com/Azure/azure-quickstart-templates/tree/master/acs-mesos

Swarm による ACS (Azure Container Service) クラスタの配置
https://github.com/Azure/azure-quickstart-templates/tree/master/acs-swarm

クラスタを作成した後は、Mesos UI などの標準のツールで管理できるという点は相当 魅力的だ。いったん配置してしまえば、Apache Mesos、Swarm の利用者にとって説明の必要はないだろう。(ただし、プログラムから管理する場合は、ACS が提供する REST API も使用可能。)