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でした。ぜひ運用前には、利用予定のファイルシステムやオプションで、事前に検証してみてください。

17 Apr 2016, 10:30

AzureとDockerでDeep Learning(CNTK)環境をサク作する

気軽に作って壊せる環境を作る

Deep Learning環境設計のお手伝いをする機会に恵まれまして。インフラおじさんはDeep Learningであれこれする主役ではないのですが、ちょっとは中身を理解しておきたいなと思い、環境作ってます。

試行錯誤するでしょうから、萎えないようにデプロイは自動化します。

方針

  • インフラはAzure Resource Manager Templateでデプロイする
    • Linux (Ubuntu 14.04) VM, 仮想ネットワーク/ストレージ関連リソース
  • CNTKをビルド済みのdockerリポジトリをDocker Hubに置いておく
    • Dockerfileの元ネタはここ
      • GPUむけもあるけどグッと我慢、今回はCPUで
    • Docker Hub上のリポジトリは torumakabe/cntk-cpu
  • ARM TemplateデプロイでVM Extensionを仕込んで、上物のセットアップもやっつける
    • docker extensionでdocker engineを導入
    • custom script extensionでdockerリポジトリ(torumakabe/cntk-cpu)をpull
  • VMにログインしたら即CNTKを使える、幸せ

使い方

Azure CLIでARM Templateデプロイします。WindowsでもMacでもLinuxでもOK。

リソースグループを作ります。

C:\Work> azure group create CNTK -l "Japan West"

ARMテンプレートの準備をします。テンプレートはGithubに置いておきました。

  • azuredeploy.json
    • 編集不要です
  • azuredeploy.parameters.json
    • テンプレートに直で書かきたくないパラメータです
    • fileUris、commandToExecute以外は、各々で
    • fileUris、commandToExecuteもGist読んでdocker pullしているだけなので、お好みで変えてください
    • ファイル名がazuredeploy.parameters.“sample”.jsonなので、以降の手順では”sample”を外して読み替えてください

うし、デプロイ。

C:\Work> azure group deployment create CNTK dep01 -f .\azuredeploy.json -e .\azuredeploy.parameters.json

10分くらい待つと、できあがります。VMのパブリックIPを確認し、sshしましょう。

docker engine入ってますかね。

yourname@yournamecntkr0:~$ docker -v
Docker version 1.11.0, build 4dc5990

CNTKビルド済みのdockerイメージ、pullできてますかね。

yourname@yournamecntkr0:~$ docker images
REPOSITORY            TAG                 IMAGE ID            CREATED             SIZE
yournamebe/cntk-cpu   latest              9abab8a76543        9 hours ago         2.049 GB

問題なし。ではエンジョイ Deep Learning。

yourname@yournamecntkr0:~$ docker run -it torumakabe/cntk-cpu
root@a1234bc5d67d:/cntk#

CNTKの利用例は、Githubにあります。

今後の展開

インフラおじさんは、最近LinuxむけにPreviewがはじまったAzure Batchと、このエントリで使った仕掛けを組み合わせて、大規模並列Deep Learning環境の自動化と使い捨て化を企んでいます。

これだけ簡単に再現性ある環境を作れるなら、常時インフラ起動しておく必要ないですものね。使い捨てでいいです。

もちろんdockerやGPUまわりの性能など別の課題にぶつかりそうですが、人間がどれだけ楽できるかとのトレードオフかと。

06 Apr 2016, 17:00

Azureの監査ログアラートからWebhookの流れで楽をする

監査ログからアラートを上げられるようになります

Azureの監査ログからアラートを上げる機能のプレビューがはじまりました。これ、地味ですが便利な機能です。日々の運用に効きます。

どんな風に使えるか

ルールに合致した監査ログが生成された場合、メール通知とWebhookによる自動アクションができます。可能性無限大です。

たとえば、「特定のリソースグループにVMが生成された場合、そのVMに対し強制的にログ収集エージェントをインストールし、ログを集める」なんてことができます。

これは「生産性を上げるため、アプリ開発チームにVMの生成は委任したい。でもセキュリティなどの観点から、ログは集めておきたい」なんてインフラ担当/Opsの課題に効きます。開発チームに「VM生成時には必ず入れてね」とお願いするのも手ですが、やはり人間は忘れる生き物ですので、自動で適用できる仕組みがあるとうれしい。

これまでは監視用のVMを立てて、「新しいVMがあるかどうか定期的にチェックして、あったらエージェントを叩き込む」なんてことをしていたわけですが、もうそのVMは不要です。定期的なチェックも要りません。アラートからアクションを実現する仕組みを、Azureがマネージドサービスとして提供します。

実装例

例としてこんな仕組みを作ってみましょう。

  • 西日本リージョンのリソースグループ”dev”にVMが作成されたら、自動的にメール通知とWebhookを実行
  • WebhookでAzure AutomationのRunbook Jobを呼び出し、OMS(Operations Management Suite)エージェントを該当のVMにインストール、接続先OMSを設定する
  • OMSでログ分析

準備

以下の準備ができているか確認します。

  • Azure Automation向けADアプリ、サービスプリンシパル作成
  • サービスプリンシパルへのロール割り当て
  • Azure Automationのアカウント作成
  • Azure Automation Runbook実行時ログインに必要な証明書や資格情報などの資産登録
  • Azure Automation Runbookで使う変数資産登録 (Runbook内でGet-AutomationVariableで取得できます。暗号化もできますし、コードに含めるべきでない情報は、登録しましょう。後述のサンプルではログイン関連情報、OMS関連情報を登録しています)
  • OMSワークスペースの作成

もしAutomationまわりの作業がはじめてであれば、下記記事を参考にしてください。とてもわかりやすい。

勤務時間中だけ仮想マシンを動かす(スケジュールによる自動起動・停止)

Azure Automation側の仕掛け

先にAutomationのRunbookを作ります。アラート設定をする際、RunbookのWebhook URLが必要になるので。

ちなみにわたしは証明書を使ってログインしています。資格情報を使う場合はログインまわりのコードを読み替えてください。

param ( 
    [object]$WebhookData          
)

if ($WebhookData -ne $null) {  
    $WebhookName    =   $WebhookData.WebhookName
    $WebhookBody    =   $WebhookData.RequestBody  
    $WebhookBody = (ConvertFrom-Json -InputObject $WebhookBody)

    $AlertContext = [object]$WebhookBody.context

    $SPAppID = Get-AutomationVariable -Name 'SPAppID'
    $Tenant = Get-AutomationVariable -Name 'TenantID'
    $OMSWorkspaceId = Get-AutomationVariable -Name 'OMSWorkspaceId'
    $OMSWorkspaceKey = Get-AutomationVariable -Name 'OMSWorkspaceKey'
    $CertificationName = Get-AutomationVariable -Name 'CertificationName'
    $Certificate = Get-AutomationCertificate -Name $CertificationName
    $CertThumbprint = ($Certificate.Thumbprint).ToString()    

    $null = Login-AzureRmAccount -ServicePrincipal -TenantId $Tenant -CertificateThumbprint $CertThumbprint -ApplicationId $SPAppID   

    $resourceObj = Get-AzureRmResource -ResourceId $AlertContext.resourceId
    $VM = Get-AzureRmVM -Name $resourceObj.Name -ResourceGroupName $resourceObj.ResourceGroupName

    $Settings = @{"workspaceId" = "$OMSWorkspaceId"}
    $ProtectedSettings = @{"workspaceKey" = "$OMSWorkspaceKey"}

    if ($VM.StorageProfile.OsDisk.OsType -eq "Linux") {  
        Set-AzureRmVMExtension -ResourceGroupName $AlertContext.resourceGroupName -Location $VM.Location -VMName $VM.Name -Name "OmsAgentForLinux" -Publisher "Microsoft.EnterpriseCloud.Monitoring" -ExtensionType "OmsAgentForLinux" -TypeHandlerVersion "1.0" -Settings $Settings -ProtectedSettings $ProtectedSettings;
    }
    elseif ($VM.StorageProfile.OsDisk.OsType -eq "Windows")
    {
        Set-AzureRmVMExtension -ResourceGroupName $AlertContext.resourceGroupName -Location $VM.Location -VMName $VM.Name -Name "MicrosoftMonitoringAgent" -Publisher "Microsoft.EnterpriseCloud.Monitoring" -ExtensionType "MicrosoftMonitoringAgent" -TypeHandlerVersion "1.0" -Settings $Settings -ProtectedSettings $ProtectedSettings;
    }
    else
    {
        Write-Error "Unknown OS Type."
    }
}
else 
{
    Write-Error "This runbook is meant to only be started from a webhook." 
}

Runbookができたら、Webhookを作ります。詳しくはこちら

WebhookのURLを控えておいてください。

Azure 監査ログアラート側の仕掛け

Powershellでアラートルールを作ります。実行アカウントの権限に気をつけてください。

PS C:\work> $actionEmail = New-AzureRmAlertRuleEmail -CustomEmail yourname@example.com

PS C:\work> $actionWebhook = New-AzureRmAlertRuleWebhook -ServiceUri https://abcdefgh.azure-automation.net/webhooks?token=your_token

PS C:\work> Add-AzureRmLogAlertRule -Name createdVM -Location "Japan West" -ResourceGroup dev -OperationName Microsoft.Compute/virtualMachines/write -Status Succeeded  -SubStatus Created -TargetResourceGroup dev -Actions $actionEmail,$actionWebhook

以上。これで”dev”リソースグループにVMが作られた場合、自動でOMSエージェントがインストールされ、ログ収集がはじまります。

なお、メールも飛んできますので、うっとおしくなったらメール通知はアクションから外すか、ルールでさばいてくださいね。

27 Mar 2016, 20:00

書評: Site Reliability Engineering

英語だけどぜひ読んでほしい

Site Reliability Engineering: How Google Runs Production Systems

参考になったのでご紹介。Googleのインフラ/Ops系技術チームの働き方や考え方を題材にした本です。GoogleのSREについては断片的に知っていたのですが、まとめて読むと違いますね。背景やストーリーがあって、理解しやすいです。

共感できるネタがどんどん繰り出されるので、一気読みしました。読み込みが浅いところもあったので、改めて読む予定。

以下、印象に残ったこと。

  • Site Reliability Engineering teamは、インフラ/Ops担当であるが、Unix内部やネットワークなどインフラの知見を持つソフトウェアエンジニアの集団。自分たちのオペレーションを効率的に、迅速に、確実にするために、コードを書く。

  • インシデント対応、問い合わせ対応、手作業は仕事の50%に収まるように調整する。残りの時間は自分たちの仕事をより良く、楽にするために、コードを書く。

  • 日々のリアクティブな活動に忙殺されるインフラ/Ops担当はどうしても減点評価になりがちだが、仕事の半分がプロアクティブな活動であり、成果を加点評価できる。昇格、昇給の根拠になりやすい。

  • アプリ/製品チームとSREチームは”Error Budget”を定義、共有する。これは四半期ごとに定義される、サービスレベル目標である。ユーザがサービスを使えなくなると、その時間が、このError Budgetから取り崩されていく。Budgetが残り少なくなると、リスクを伴うデプロイなどは控える。

  • インフラ/Ops担当は「サービスを少しでもダウンさせたら悪」となりがちだが、サービスごとにアプリ/製品チームとSREチームがError Budgetを共有することで、利害関係を一致できる。

  • Error Budgetの大きさはサービスごとに異なり、定義は製品チームの責任。当然Error Budgetが少ない = サービスレベルが高い = コストがかかる ので、製品チームはいたずらに高いサービスレベルを定義しない。Google Apps for WorkとYoutubeのError Budgetは異なる。Appsはサービスレベル重視であり、Youtubeは迅速で頻繁な機能追加を重視する。

  • SLA違反など、重大な障害では”Postmortem(過激だが死体解剖の意)“を作成し、失敗から学ぶ。客観的に、建設的に。誰かや何かを責めるためにやるわけではない。マサカリ投げない。

  • 他の産業から学ぶ。製造業のビジネス継続プラン、国防のシミュレーションや演習、通信業の輻輳対策など。

もう一回読んだら、また違う発見があるんじゃないかと。

自分ごととして読みたい

今後の働き方や所属組織に行き詰まりを感じているインフラ/Ops技術者に、参考になるネタが多いと思います。

DevOpsムーブメントが来るか来ないかという今、Opsとしてのスタンスを考え直すのにも、いいかもしれません。

もちろん、Googleの圧倒的物量、成長スピードゆえのミッションと働き方である事は否定しません。でも、自分とは無関係、と無視するにはもったいないです。

なお、このSREチーム、できてから10年以上たっているそうです。それだけ持続できるということは、そこに何か本質的な価値があるのではないでしょうか。

オススメです。