21 May 2016, 11:00

Azure X-Plat CLIでResource Policyを設定する

Azure X-Plat CLIのリリースサイクル

OSS/Mac/Linux派なAzurerの懐刀、Azure X-Plat CLIのリリースサイクルは、おおよそ月次です。改善と機能追加を定期的にまわしていくことには意味があるのですが、いっぽう、Azureの機能追加へタイムリーに追随できないことがあります。短期間とはいえ、次のリリースまで空白期間ができてしまうのです。

たとえば、今回のテーマであるResource Policy。GA直後に公開されたドキュメントに、X-Plat CLIでの使い方が2016/5/21現在書かれていません。おやCLIではできないのかい、と思ってしまいますね。でもその後のアップデートで、できるようになりました。

機能リリース時点ではCLIでできなかった、でもCLIの月次アップデートで追加された、いまはできる、ドキュメントの更新待ち。こんなパターンは多いので、あきらめずに探ってみてください。

ポリシーによるアクセス管理

さて本題。リソースの特性に合わせて、きめ細かいアクセス管理をしたいことがあります。

  • VMやストレージのリソースタグに組織コードを入れること強制し、費用負担の計算に使いたい
  • 日本国外リージョンのデータセンタを使えないようにしたい
  • Linuxのディストリビューションを標準化し、その他のディストリビューションは使えなくしたい
  • 開発環境リソースグループでは、大きなサイズのインスタンスを使えないようにしたい

などなど。こういう課題にポリシーが効きます。

従来からあるRBACは「役割と人」目線です。「この役割を持つ人は、このリソースを読み取り/書き込み/アクションできる」という表現をします。組み込みロールの一覧を眺めると、理解しやすいでしょう。

ですが、RBACは役割と人を切り口にしているので、各リソースの多様な特性にあわせた統一表現が難しいです。たとえばストレージにはディストリビューションという属性はありません。無理してカスタム属性なんかで表現すると破綻しそうです。

リソース目線でのアクセス管理もあったほうがいい、ということで、ポリシーの出番です。もちろんRBACと、組み合わせできます。

X-Plat CLIでの定義方法

2016/4リリースのv0.9.20から、X-Plat CLIでもResource Policyを定義できます。

ポリシーの定義、構文はPowerShellと同じなので、公式ドキュメントに任せます。ご一読を。

ポリシーを使用したリソース管理とアクセス制御

X-Plat CLI固有部分に絞って紹介します。

ポリシー定義ファイルを作る

CLIでインラインに書けるようですが、人類には早すぎる気がします。ここではファイルに。

例として、作成できるVMのサイズを限定してみましょう。開発環境などでよくあるパターンと思います。VM作成時、Standard_D1~5_v2に当てはまらないVMサイズが指定されると、拒否します。

{
  "if": {
    "allOf": [
      {
        "field": "type",
        "equals": "Microsoft.Compute/virtualMachines"
      },
      {
        "not": {
          "field": "Microsoft.Compute/virtualMachines/sku.name",
          "in": [ "Standard_D1_v2", "Standard_D2_v2","Standard_D3_v2", "Standard_D4_v2", "Standard_D5_v2" ]
        }
      }
    ]
  },
  "then": {
    "effect": "deny"
  }
}

policy_deny_vmsize.json というファイル名にしました。では投入。ポリシー名は deny_vmsize とします。

$ azure policy definition create -n deny_vmsize -p ./policy_deny_vmsize.json
info:    Executing command policy definition create
+ Creating policy definition deny_vmsize
data:    PolicyName:             deny_vmsize
data:    PolicyDefinitionId:     /subscriptions/mysubscription/providers/Microsoft.Authorization/policyDefinitions/deny_vmsize
data:    PolicyType:             Custom
data:    DisplayName:
data:    Description:
data:    PolicyRule:             allOf=[field=type, equals=Microsoft.Compute/virtualMachines, field=Microsoft.Compute/virtualMachines/sku.name, in=[Standard_D1_v2, Standard_D2_v2, Standard_D3_v2, Standard_D4_v2, Standard_D5_v2]], effect=deny
info:    policy definition create command OK

できたみたいです。

ポリシーをアサインする

では、このポリシーを割り当てます。割り当ての範囲(スコープ)はサブスクリプションとします。リソースグループなど、より細かいスコープも指定可能です。

$ azure policy assignment create -n deny_vmsize_assignment -p /subscriptions/mysubscription/providers/Microsoft.Authorization/policyDefinitions/deny_vmsize -s /subscriptions/mysubscription
info:    Executing command policy assignment create
+ Creating policy assignment deny_vmsize_assignment
data:    PolicyAssignmentName:     deny_vmsize_assignment
data:    Type:                     Microsoft.Authorization/policyAssignments
data:    DisplayName:
data:    PolicyDefinitionId:       /subscriptions/mysubscription/providers/Microsoft.Authorization/policyDefinitions/deny_vmsize
data:    Scope:                    /subscriptions/mysubscription
info:    policy assignment create command OK

割り当て完了。では試しに、このサブスクリプションに属するユーザで、Gシリーズのゴジラ級インスタンスを所望してみます。

$ azure vm quick-create -g RPPoC -n rppocvm westus -y Linux -Q "canonical:ubuntuserver:14.04.4-LTS:latest" -u "adminname" -p "adminpass" -z Standard_G5
info:    Executing command vm quick-create
[...snip]
+ Creating VM "rppocvm"
error:   The resource action 'Microsoft.Compute/virtualMachines/write' is disallowed by one or more policies. Policy identifier(s): '/subscriptions/mysubscription/providers/Microsoft.Authorization/policyDefinitions/deny_vmsize'.
info:    Error information has been recorded to /root/.azure/azure.err
error:   vm quick-create command failed

拒否られました。

許可されているVMサイズだと。

$ azure vm quick-create -g RPPoC -n rppocvm westus -y Linux -Q "canonical:ubuntuserver:14.04.4-LTS:latest" -u "adminname" -p "adminpass" -z Standard_D1_v2
info:    Executing command vm quick-create
[...snip]
info:    vm quick-create command OK

成功。

13 May 2016, 18:00

VagrantとDockerによるAzure向けOSS開発・管理端末のコード化

端末だってコード化されたい

Infrastructure as Codeは特に騒ぐ話でもなくなってきました。このエントリは、じゃあ端末の開発環境やツール群もコード化しようという話です。結論から書くと、VagrantとDockerを活かします。超絶便利なのにAzure界隈ではあまり使われてない印象。もっと使われていいのではと思い、書いております。

解決したい課題

こんな悩みを解決します。

  • WindowsでOSS開発環境、Azure管理ツールのセットアップをするのがめんどくさい
  • WindowsもMacも使っているので、どちらでも同じ環境を作りたい
  • サーバはLinuxなので手元にもLinux環境欲しいけど、Linuxデスクトップはノーサンキュー
  • 2016年にもなって長いコードをVimとかEmacsで書きたくない
  • Hyper-VとかVirtualboxで仮想マシンのセットアップと起動、後片付けをGUIでするのがいちいちめんどくさい
  • 仮想マシン起動したあとにターミナル起動->IP指定->ID/Passでログインとか、かったるい
  • Azure CLIやTerraformなどクラウド管理ツールの進化が頻繁でつらい(月一回アップデートとか)
  • でもアップデートのたびに超絶便利機能が追加されたりするので、なるべく追いかけたい
  • 新メンバーがチームに入るたび、セットアップが大変
  • 不思議とパソコンが生えてくる部屋に住んでおり、セットアップが大変
  • 毎度作業のどこかが抜ける、漏れる、間違う 人間だもの

やり口

VagrantとDockerで解決します。

  • Windows/Macどちらにも対応しているVirtualboxでLinux仮想マシンを作る
  • Vagrantでセットアップを自動化する
  • Vagrantfile(RubyベースのDSL)でシンプルに環境をコード化する
  • Vagrant Puttyプラグインを使って、Windowsでもsshログインを簡素化する
  • 公式dockerイメージがあるツールは、インストールせずコンテナを引っ張る
  • Windows/MacのいまどきなIDEなりエディタを使えるようにする

セットアップ概要

簡単す。

  1. Virtualboxをインストール
  2. Vagrantをインストール
  3. Vagrant Putty Plugin(vagrant-multi-putty)をインストール #Windowsのみ。Puttyは別途入れてください
  4. 作業フォルダを作り、Vagrant ファイルを書く

もしWindowsでうまく動かない時は、Hyper-Vが有効になっていないか確認しましょう。Virtualboxと共存できません。

サンプル解説

OSSなAzurerである、わたしのVagrantfileです。日々環境に合わせて変えてますが、以下は現時点でのスナップショット。

# -*- mode: ruby -*-
# vi: set ft=ruby :

# Vagrantfile API/syntax version. Don't touch unless you know what you're doing!
VAGRANTFILE_API_VERSION = "2"

$bootstrap=<<SCRIPT

#Common tools
sudo apt-get update
sudo apt-get -y install wget unzip jq

#Docker Engine
sudo apt-get -y install apt-transport-https ca-certificates
sudo apt-get -y install linux-image-extra-$(uname -r)
sudo apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys 58118E89F3A912897C070ADBF76221572C52609D
sudo sh -c "echo deb https://apt.dockerproject.org/repo ubuntu-trusty main > /etc/apt/sources.list.d/docker.list"
sudo apt-get update
sudo apt-get -y purge lxc-docker
sudo apt-cache policy docker-engine
sudo apt-get -y install docker-engine=1.11.1-0~trusty
sudo gpasswd -a vagrant docker
sudo service docker restart

#Docker Machine
sudo sh -c "curl -L https://github.com/docker/machine/releases/download/v0.7.0/docker-machine-`uname -s`-`uname -m` >/usr/local/bin/docker-machine && chmod +x /usr/local/bin/docker-machine"

#Azure CLI
echo "alias azure='docker run -it --rm -v \\\$HOME/.azure:/root/.azure -v \\\$PWD:/data -w /data microsoft/azure-cli:latest azure'" >> $HOME/.bashrc

#Terraform
echo "alias terraform='docker run -it --rm -v \\\$PWD:/data -w /data hashicorp/terraform:0.6.14'" >> $HOME/.bashrc

#Packer
echo "alias packer='docker run -it --rm -v \\\$PWD:/data -w /data hashicorp/packer:latest'" >> $HOME/.bashrc

#nodebrew
curl -L git.io/nodebrew | perl - setup
echo 'export PATH=$HOME/.nodebrew/current/bin:$PATH' >> $HOME/.bashrc
$HOME/.nodebrew/current/bin/nodebrew install-binary 5.9.1
$HOME/.nodebrew/current/bin/nodebrew use 5.9.1

#Python3
wget -qO- https://bootstrap.pypa.io/get-pip.py | sudo -H python3.4

SCRIPT

Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
  # Every Vagrant virtual environment requires a box to build off of.

  config.vm.box = "ubuntu/trusty64"

  # Create a private network, which allows host-only access to the machine
  # using a specific IP.

  config.vm.network "private_network", ip: "192.168.33.10"

  config.vm.provider "virtualbox" do |vb|
     vb.customize ["modifyvm", :id, "--memory", "2048"]
  end

  config.vm.provision :shell, inline: $bootstrap, privileged: false

end

$bootstrap=<<SCRIPT から SCRIPT が、プロビジョニングシェルです。初回のvagrant up時とvagrant provision時に実行されます。

Common tools

一般的なツールをaptでインストールします。wgetとかjqとか。

Docker Engine & Machine

この後前提となるDockerをインストール。Dockerのバージョンは1.11.1を明示しています。Dockerは他への影響が大きいので、バージョンアップは慎重めの方針です。

Azure CLI

インストールせずにMS公式のDockerイメージを引っ張ります。なのでalias設定だけ。 -v オプションで、ホストLinuxとコンテナ間でデータを共有します。CLIが使う認証トークン($HOME/.azure下)やCLI実行時に渡すjsonファイル(作業ディレクトリ)など。詳細は後ほど。 また、azureコマンド発行ごとにコンテナが溜まっていくのがつらいので、–rmで消します。

Terraform & Packer

Azure CLIと同様です。Hashicorpが公式イメージを提供しているので、それを活用します。 方針はlatest追いですが、不具合があればバージョンを指定します。たとえば、現状Terraformのlatestイメージに不具合があるので、0.6.14を指定しています。 -v オプションもAzure CLIと同じ。ホストとコンテナ間のファイルマッピングに使います。

なお、公式とはいえ他人のイメージを使う時には、Dockerfileの作りやビルド状況は確認しましょう。危険がデンジャラスですし、ENTRYPOINTとか知らずにうっかり使うと途方に暮れます。

nodebrew

nodeのバージョンを使い分けるため。セットアップ時にv5.9.1を入れています。Azure Functions開発向け。

Python3

Ubuntu 14.04では標準がPython2なので別途入れてます。Azure Batch向け開発でPython3使いたいので。

みなさん他にもいろいろあるでしょう。シェルなのでお好みで。

さて、ここまでがプロビジョニング時の処理です。以降の”Vagrant.configure~”は仮想マシンの定義で、難しくありません。ubuntu/trusty64(14.04)をboxイメージとし、IPやメモリを指定し、先ほど定義したプロビジョニング処理を指しているだけです。

どれだけ楽か

では、環境を作ってみましょう。Vagrantfileがあるフォルダで

vagrant up

仮想マシンが作成されます。初回はプロビジョニング処理も走ります。

できましたか。できたら、

vagrant putty

はい。Puttyが起動し、ID/Passを入れなくてもsshログインします。破壊力抜群。わたしはこの魅力だけでTeraterm(Terraformではない)からPuttyに乗り換えました。ちなみにMacでは、vagrant sshで済みます。

あとはプロビジョニングされたLinuxを使って楽しんでください。そして、必要なくなったら or 作り直したくなったら

vagrant destroy

綺麗さっぱりです。仮想マシンごと消します。消さずにまた使う時は、vagrant haltを。

なお、vagrant upしたフォルダにあるファイルは、Virtualboxの共有フォルダ機能で仮想マシンと共有されます。shareとかいう名のフォルダを作って、必要なファイルを放り込んでおきましょう。その場合、仮想マシンのUbuntuからは/vagrant/shareと見えます。双方向で同期されます。

わたしは長いコードを書くときは、Windows/Mac側のIDEなりエディタを使って、実行は仮想マシンのLinux側、という流れで作業しています。

ちなみに、改行コードの違いやパーミッションには気を付けてください。改行コードはLFにする癖をつけておくと幸せになれます。パーミッションは全開、かつ共有領域では変えられないので、問題になるときは仮想マシン側で/vagrant外にコピーして使ってください。パーミッション全開だと怒られる認証鍵など置かないよう、注意。

また、Dockerコンテナを引っ張るAzure CLI、Terraform、Packerの注意点。

  • 初回実行時にイメージのPullを行うので、帯域の十分なところでやりましょう
  • サンプルでは -v $PWD:/data オプションにて、ホストのカレントディレクトリをコンテナの/dataにひもづけています。そして、-w /data にて、コンテナ内ワーキングディレクトリを指定しています。コマンドの引数でファイル名を指定したい場合は、実行したいファイルがあるディレクトリに移動して実行してください
    • (例) azure group deployment create RG01 DEP01 -f ./azuredeploy.json -e ./azuredeploy.parameters.json

Bash on Windowsまで待つとか言わない

「WindowsではOSSの開発や管理がしにくい。Bash on Windowsが出てくるまで待ち」という人は、待たないで今すぐGoです。思い立ったが吉日です。繰り返しますがVagrantとDocker、超絶便利です。

インフラのコード化なんか信用ならん!という人も、まず今回紹介したように端末からはじめてみたらいかがでしょう。激しく生産性上がると思います。

夏近し、楽して早く帰ってビール呑みましょう。

08 May 2016, 14:00

Azure FunctionsとFacebook Messenger APIで好みなんて聞いてないBotを作る

まだ好みなんて聞いてないぜ

Build 2016で、Azure Functionsが発表されました。

Azure Functionsは、

  1. アプリを放り込めば動く。サーバの管理が要らない。サーバレス。 #でもこれは従来のPaaSもそう
  2. 利用メモリ単位での、粒度の細かい課金。 #現在プレビュー中にて、詳細は今後発表
  3. Azure内外機能との、容易なイベント連動。

が特徴です。AWSのLambdaと似てるっちゃ似ています。

何が新しいかというと、特に3つ目の特徴、イベント連動です。触ってみなければわからん、ということで、流行りのBotでも作ってみたいと思います。

基本方針

  • FunctionsはAzure内の様々な機能とイベント連動できるが、あえてサンプルの少ないAzure外とつないでみる
  • Facebook Messenger APIを使って、webhook連動する
  • Facebook Messenger向けに書き込みがあると、ランダムでビールの種類と参考URLを返す
  • ビールはCraft Beer Associationの分類に従い、協会のビアスタイル・ガイドライン参考ページの該当URLを返す
  • Botらしく、それらしい文末表現をランダムで返す
  • 好みとか文脈は全く聞かないぜSorry
  • アプリはNodeで書く。C#のサンプルは増えてきたので
  • 静的データをランダムに返す、かつ少量なのでメモリ上に広げてもいいが、せっかくなのでNodeと相性のいいDocumentDBを使う
  • DocumentDBではSQLでいうORDER BY RAND()のようなランダムな問い合わせを書けないため、ストアドプロシージャで実装する #サンプル
  • FunctionsとGithubを連携し、GithubへのPush -> Functionsへのデプロイというフローを作る
  • 拡張性はひとまず目をつぶる #この辺の話

ひとまずFunctionsとBotの枠組みの理解をゴールとします。ロジックをたくさん書けばそれなりに文脈を意識した返事はできるのですが、書かずに済む仕組みがこれからいろいろ出てきそうなので、書いたら負けの精神でぐっと堪えます。

必要な作業

以下が必要な作業の流れです。

  • Azureで
    • Function Appの作成 #1
    • Bot用Functionの作成 #2
    • Facebook Messenger APIとの接続検証 #6
    • Facebook Messenger API接続用Tokenの設定 #8
    • DocumentDBのデータベース、コレクション作成、ドキュメント投入 #9
    • DocumentDBのストアドプロシージャ作成 #10
    • Function Appを書く #11
    • FunctionsのサイトにDocumentDB Node SDKを導入 #12
    • Function AppのGithub連携設定 #13
    • Function Appのデプロイ (GithubへのPush) #14
  • Facebookで
    • Facebook for Developersへの登録 #3
    • Botをひも付けるFacebook Pageの作成 #4
    • Bot用マイアプリの作成 #5
    • Azure Functionsからのcallback URLを登録、接続検証 #6
    • Azure Functions向けTokenを生成 #7

アプリのコード書きの他はそれほど重くない作業ですが、すべての手順を書くと本ができそうです。Function Appの作りにポイントを絞りたいので、以下、参考になるサイトをご紹介します。

Function Appのサンプル

Githubにソースを置いておきます

ちなみにこのディレクトリ階層はGithub連携を考慮し、Function Appサイトのそれと合わせています。以下がデプロイ後のサイト階層です。

D:\home\site\wwwroot
├── fb-message-callback
│   ├── TestOutput.json
│   ├── function.json
│   └── index.js  #これが今回のアプリ
├── node_modules  #DocumentDB Node SDKが入っている
├── host.json
├── README.md

なお、DocumentDBのSDKパッケージは、なぜかfb-message-callbackローカルに置くと読み込まれないため、暫定的にルートへ配置しています。

ではFunction Appの実体、index.jsを見てみましょう。

var https = require('https');
var documentClient = require("documentdb").DocumentClient;
const databaseUrl = "dbs/" + process.env.APPSETTING_DOCDB_DB_ID;

var client = new documentClient(process.env.APPSETTING_DOCDB_ENDPOINT, { "masterKey": process.env.APPSETTING_DOCDB_AUTHKEY });

function sendTextMessage(sender, text, context) {
  getDataFromDocDB().then(function (value) {
    var msgAll = value[0].randomDocument.beer + " " + value[1].randomDocument.msg;
    var postData = JSON.stringify({
      recipient: sender,
      message: {
        "attachment":{
          "type":"template",
          "payload":{
            "template_type":"button",
            "text":msgAll,
            "buttons":[
              {
                "type":"web_url",
                "url":value[0].randomDocument.url,
                "title":"詳しく"
              }
            ]
          }
        }
      }
    });
    var req = https.request({
      hostname: 'graph.facebook.com',
      port: 443,
      path: '/v2.6/me/messages',
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer ' + process.env.APPSETTING_FB_PAGE_TOKEN
      }
    });
    req.write(postData);
    req.end();
  }).catch(function(err){
    context.log(err);
  });  
}

function getRandomDoc(sprocUrl){
  return new Promise(function (resolve, reject) {
    const sprocParams = {};
    client.executeStoredProcedure(sprocUrl, sprocParams, function(err, result, responseHeaders) {
      if (err) {
        reject(err);
      }
      if (result) {
        resolve(result);
      }
    });
  });
}

var results = {
  beer: function getBeer() {
    var collectionUrl = databaseUrl + "/colls/beer";
    var sprocUrl = collectionUrl + "/sprocs/GetRandomDoc";
    return getRandomDoc(sprocUrl).then(function (result) {
      return result;
    });
  },
  eom: function getEom() {
    var collectionUrl = databaseUrl + "/colls/eom";
    var sprocUrl = collectionUrl + "/sprocs/GetRandomDoc";
    return getRandomDoc(sprocUrl).then(function (result) {
      return result;
    });
  }
}

function getDataFromDocDB() {
  return Promise.all([results.beer(), results.eom()]);
}

module.exports = function (context, req) {
  messaging_evts = req.body.entry[0].messaging;
  for (i = 0; i < messaging_evts.length; i++) {
    evt = req.body.entry[0].messaging[i];
    sender = evt.sender;
    if (evt.message && evt.message.text, context) {
      sendTextMessage(sender, evt.message.text, context);
    }
  }
  context.done();
};
  • 最下部のmodule.export以降のブロックで、webhookイベントを受け取ります
  • それがmessageイベントで、テキストが入っていれば、sendTextMessage関数を呼びます
    • 好みは聞いてないので、以降、受け取ったテキストが読まれることはありませんが
  • sendTextMessage関数内、getDataFromDocDB関数呼び出しでDocumentDBへ問い合わせてビールと文末表現をランダムに取り出します
    • コレクション”beer”、”eom(end of message)“の構造はそれぞれこんな感じ
{
  "url": "http://beertaster.org/beerstyle/web/001A.html#japanese",
  "beer": "酵母なし、ライトアメリカン・ウィートビール",
  "id": "bf3636c5-4284-4e7a-b587-9002a771f214"
}
{
  "msg": "はウマい",
  "id": "acd63222-2138-4e19-894e-dc85a950be64"
}
  • DocumentDBの2つのコレクションへの問い合わせが終わった後、Facebookへメッセージを返すため、逐次処理目的でJavaScriptのPromiseを使っています

いかがでしょう。好みを聞かない気まぐれBotとはいえ、気軽に作れることがわかりました。ゼロからこの手のイベント処理を作るの、面倒ですものね。

“なお、Facebook Messenger API連動アプリの外部公開には、審査が必要とのことです”

29 Apr 2016, 17:00

Azure BatchとDockerで管理サーバレスバッチ環境を作る

サーバレスって言いたいだけじゃないです

Linux向けAzure BatchのPreviewがはじまりました。地味ですが、なかなかのポテンシャルです。

クラウドでバッチを走らせる時にチャレンジしたいことの筆頭は「ジョブを走らせる時だけサーバ使う。待機時間は消しておいて、 節約」でしょう。

ですが、仕組み作りが意外に面倒なんですよね。管理サーバを作って、ジョブ管理ソフト入れて、Azure SDK/CLI入れて。クレデンシャルを安全に管理して。可用性確保して。バックアップして。で、管理サーバは消せずに常時起動。なんか中途半端です。

その課題、Azure Batchを使って解決しましょう。レッツ管理サーバレスバッチ処理。

コンセプト

  • 管理サーバを作らない
  • Azure Batchコマンドでジョブを投入したら、あとはスケジュール通りに定期実行される
  • ジョブ実行サーバ群(Pool)は必要な時に作成され、処理が終わったら削除される
  • サーバの迅速な作成とアプリ可搬性担保のため、dockerを使う
  • セットアップスクリプト、タスク実行ファイル、アプリ向け入力/出力ファイルはオブジェクトストレージに格納

サンプル

Githubにソースを置いておきます

バッチアカウントとストレージアカウント、コンテナの作成とアプリ、データの配置

公式ドキュメントで概要を確認しましょう。うっすら理解できたら、バッチアカウントとストレージアカウントを作成します。

ストレージアカウントに、Blobコンテナを作ります。サンプルの構成は以下の通り。

.
├── blob
│   ├── application
│   │   ├── starttask.sh
│   │   └── task.sh
│   ├── input
│   │   └── the_star_spangled_banner.txt
│   └── output

applicationコンテナに、ジョブ実行サーバ作成時のスクリプト(starttask.sh)と、タスク実行時のスクリプト(task.sh)を配置します。

  • starttask.sh - docker engineをインストールします
  • task.sh - docker hubからサンプルアプリが入ったコンテナを持ってきて実行します。サンプルはPythonで書いたシンプルなWord Countアプリです

また、アプリにデータをわたすinputコンテナと、実行結果を書き込むoutputコンテナも作ります。サンプルのinputデータはアメリカ国歌です。

コンテナ、ファイルには、適宜SASを生成しておいてください。inputではreadとlist、outputでは加えてwrite権限を。

さて、いよいよジョブをJSONで定義します。詳細は公式ドキュメントを確認してください。ポイントだけまとめます。

  • 2016/04/29 05:30(UTC)から開始する - schedule/doNotRunUntil
  • 4時間ごとに実行する - schedule/recurrenceInterval
  • ジョブ実行後にサーバプールを削除する - jobSpecification/poolInfo/autoPoolSpecification/poolLifetimeOption
  • ジョブ実行時にtask.shを呼び出す - jobSpecification/jobManagerTask/commandLine
  • サーバはUbuntu 14.04とする - jobSpecification/poolInfo/autoPoolSpecification/virtualMachineConfiguration
  • サーバ数は1台とする - jobSpecification/poolInfo/autoPoolSpecification/pool/targetDedicated
  • サーバプール作成時にstarttask.shを呼び出す - jobSpecification/poolInfo/autoPoolSpecification/pool/startTask
  {
  "odata.metadata":"https://myaccount.myregion.batch.azure.com/$metadata#jobschedules/@Element",
  "id":"myjobschedule1",
  "schedule": {
    "doNotRunUntil":"2016-04-29T05:30:00.000Z",
    "recurrenceInterval":"PT4H"
  },
  "jobSpecification": {
    "priority":100,
    "constraints": {
      "maxWallClockTime":"PT1H",
      "maxTaskRetryCount":-1
    },
    "jobManagerTask": {
      "id":"mytask1",
      "commandLine":"/bin/bash -c 'export LC_ALL=en_US.UTF-8; ./task.sh'",
      "resourceFiles": [ {
        "blobSource":"yourbloburi&sas",
        "filePath":"task.sh"
      }], 
      "environmentSettings": [ {
        "name":"VAR1",
        "value":"hello"
      } ],
      "constraints": {
        "maxWallClockTime":"PT1H",
        "maxTaskRetryCount":0,
        "retentionTime":"PT1H"
      },
      "killJobOnCompletion":false,
      "runElevated":true,
      "runExclusive":true
      },
      "poolInfo": {
        "autoPoolSpecification": {
          "autoPoolIdPrefix":"mypool",
          "poolLifetimeOption":"job",
          "pool": {
            "vmSize":"STANDARD_D1",
            "virtualMachineConfiguration": {
              "imageReference": {
                "publisher":"Canonical",
                "offer":"UbuntuServer",
                "sku":"14.04.4-LTS",
                "version":"latest"
              },
              "nodeAgentSKUId":"batch.node.ubuntu 14.04"
            },
            "resizeTimeout":"PT15M",
            "targetDedicated":1,
            "maxTasksPerNode":1,
            "taskSchedulingPolicy": {
              "nodeFillType":"Spread"
            },
            "enableAutoScale":false,
            "enableInterNodeCommunication":false,
            "startTask": {
              "commandLine":"/bin/bash -c 'export LC_ALL=en_US.UTF-8; ./starttask.sh'",
              "resourceFiles": [ {
                "blobSource":"yourbloburi&sas",
                "filePath":"starttask.sh"
              } ],
              "environmentSettings": [ {
                "name":"VAR2",
                "value":"Chao"
              } ],
              "runElevated":true,
              "waitForSuccess":true
            },
            "metadata": [ {
              "name":"myproperty",
              "value":"myvalue"
            } ]
          }
        }
      }
    }
  }

そろそろ人類はJSONに変わるやり口を発明すべきですが、XMLよりはいいですね。

それはさておき、面白そうなパラメータたち。並列バッチやジョブリリース時のタスクなど、今回使っていないものもまだまだあります。応用版はまたの機会に。

ではスケジュールジョブをAzure BatchにCLIで送り込みます。

azure batch job-schedule create -f ./create_jobsched.json -u https://yourendpoint.location.batch.azure.com -a yourbatchaccount -k yourbatchaccountkey

以上です。あとはAzureにお任せです。4時間に1回、アメリカ国歌の単語を数える刺身タンポポなジョブですが、コツコツいきましょう。

Azure Automationとの使い分け

Azure Automationを使っても、ジョブの定期実行はできます。大きな違いは、PowerShellの要否と並列実行フレームワークの有無です。Azure AutomationはPowerShell前提ですが、Azure BatchはPowerShellに馴染みのない人でも使うことができます。また、今回は触れませんでしたが、Azure Batchは並列バッチ、オートスケールなど、バッチ処理に特化した機能を提供していることが特長です。うまく使い分けましょう。

21 Apr 2016, 21:30

Azure Linux VMのディスク利用料節約Tips

定義領域全てが課金されるわけではありません

AzureのIaaSでは、VMに接続するディスクとしてAzure StorageのPage Blobを使います。Page Blobは作成時に容量を定義しますが、課金対象となるのは、実際に書き込んだ領域分のみです。たとえば10GBytesのVHD Page Blobを作ったとしても、1GBytesしか書き込んでいなければ、課金対象は1GBytesです。

なお、Premium Storageは例外です。FAQを確認してみましょう。

仮想マシンに空の 100 GB ディスクを接続した場合、100 GB 全体に対する料金が請求されますか? それとも使用したストレージ領域の分だけが請求されますか?

空の 100 GB ディスクが Premium Storage アカウントによって保持されている場合、P10 (128 GB) ディスクの料金が課金されます。その他の種類の Storage アカウントが使用されている場合、割り当てられたディスク サイズに関わらず、ディスクに書き込まれたデータを保存するために使用しているストレージ領域分のみ請求されます。

詳細な定義は、以下で。

Understanding Windows Azure Storage Billing – Bandwidth, Transactions, and Capacity

書き込み方はOSやファイルシステム次第

じゃあ、OSなりファイルシステムが、実際にどのタイミングでディスクに書き込むのか、気になりますね。実データの他に管理情報、メタデータがあるので、特徴があるはずです。Linuxで検証してみましょう。

  • RHEL 7.2 on Azure
  • XFS & Ext4
  • 10GBytesのPage Blobの上にファイルシステムを作成
  • mkfsはデフォルト
  • mountはデフォルトとdiscardオプションありの2パターン
  • MD、LVM構成にしない
  • 以下のタイミングで課金対象容量を確認
    • Page BlobのVMアタッチ時
    • ファイルシステム作成時
    • マウント時
    • 約5GBytesのデータ書き込み時 (ddで/dev/zeroをbs=1M、count=5000で書き込み)
    • 5GBytesのファイル削除時

課金対象容量は、以下のPowerShellで取得します。リファレンスはここ

$Blob = Get-AzureStorageBlob yourDataDisk.vhd -Container vhds -Context $Ctx

$blobSizeInBytes = 124 + $Blob.Name.Length * 2

$metadataEnumerator = $Blob.ICloudBlob.Metadata.GetEnumerator()
while ($metadataEnumerator.MoveNext())
{
    $blobSizeInBytes += 3 + $metadataEnumerator.Current.Key.Length + $metadataEnumerator.Current.Value.Length
}

$Blob.ICloudBlob.GetPageRanges() | 
    ForEach-Object { $blobSizeInBytes += 12 + $_.EndOffset - $_.StartOffset }

return $blobSizeInBytes

ストレージコンテキストの作り方はここを参考にしてください。

結果

XFS

 確認タイミング   課金対象容量(Bytes) 
Page BlobのVMアタッチ時 960
ファイルシステム作成時 10,791,949
マウント時 10,791,949
5GBytesのデータ書き込み時 5,253,590,051
5Gbytesのファイル削除時 5,253,590,051
5Gbytesのファイル削除時 (discard) 10,710,029

Ext4

 確認タイミング   課金対象容量(Bytes) 
Page BlobのVMアタッチ時 960
ファイルシステム作成時 138,683,592
マウント時 306,451,689
5GBytesのデータ書き込み時 5,549,470,887
5Gbytesのファイル削除時 5,549,470,887
5Gbytesのファイル削除時 (discard) 306,586,780

この結果から、以下のことがわかります。

  • 10GBytesのBlobを作成しても、全てが課金対象ではない
  • 当然だが、ファイルシステムによってメタデータの書き方が違う、よって書き込み容量も異なる
  • discardオプションなしでマウントすると、ファイルを消しても課金対象容量は減らない
    • OSがPage Blobに”消した”と伝えないから
    • discardオプションにてSCSI UNMAPがPage Blobに伝えられ、領域は解放される(課金対象容量も減る)
    • discardオプションはリアルタイムであるため便利。でも性能影響があるため、実運用ではバッチ適用(fstrim)がおすすめ

知っているとコスト削減に役立つTipsでした。ぜひ運用前には、利用予定のファイルシステムやオプションで、事前に検証してみてください。