読者です 読者をやめる 読者になる 読者になる

まいんだーのはてなブログ

はてブなのはてブロなのどっちなの

ISUCON6参加者が実践したInfrastructure as CodeあるいはISUCON6のAzureインフラ解説

ISUCON6予選参加者の皆様、お疲れさまでした。
今回の ISUCON は Microsoft Azure ということで、多くの方にとってなじみのない環境だったと思います。

そこで、今回の ISUCON6 の予選問題がどのような仕組みで展開されているのか、Azure IaaSの仕組みを交えて解説します。

はじめに

@matsuuさんの多大な努力によって、運営に提案しようと思っていた構成の大半がすでに作られていました。
使ったことのないクラウドを触り始めてすぐに作り上げてしまうのは、さすがです!

復習される際はぜひ、@matsuuさんの作ったテンプレートを活用するとよいでしょう。

Azure IaaSの概要

Azureはおおむね国ごとにジオという単位で分けられており、その中に2つ以上のリージョンを持っています。
リージョンはそれぞれペアになるものが存在していて、日本では東日本と西日本がそれぞれペアリージョンになります。
そして、リージョンの中には複数のデータセンターが存在するという構成です。
f:id:myfinder:20160918132458p:plain

データセンター内では、物理サーバの配置を"クラスター"という単位で配置しています。
クラスターは同種のハードウェアの集まりで、例えばXeon E5-2673 v3の乗った10G NICのサーバなど、スペックが統一されたものです。
ユーザが仮想マシンを起動すると、裏側では "ファブリックコントローラ" と呼ばれるものが、その仮想マシンをどのクラスターに配備するかを決定します。

仮想マシンをファブリックコントローラが配備することを "デプロイメント" と呼び、これは一つのクラスターを対象に配備が行われます。デプロイメントはクラスターを跨ぐことはありません。
デプロイメントは、リソースマネージャーモデルの仮想マシンであれば、後述の "可用性セット" 単位で1つのデプロイメントとして扱われます。

f:id:myfinder:20160918135925p:plain

これは管理画面にも一切出てこないもので。通常ユーザはこのクラスターを意識することはありません。
ユーザがクラスターを意識するタイミングがあるとすれば、インスタンスタイプ変更の時です。

クラスターは "同一スペックのサーバの集合である" と説明した通り、インスタンスタイプの変更はこのクラスターの範囲の中で行うことになります。
ですので、最初にAシリーズなどの古めのスペックのインスタンスタイプで仮想マシンを作ると、古いクラスターに着地することになり、その後Dシリーズへ変更するにはVMを一度削除する必要があります。

f:id:myfinder:20160918134715p:plain

クラスター内部の構造

クラスター内部では "障害ドメイン" という単位で電源やネットワーク機器が配置されていて、これらの装置の障害がほかの障害ドメインに影響を及ぼさないように設計されています。ひとつのクラスターは、数十の障害ドメインで構成されています。

先に少し触れた "可用性セット" ですが、仮想マシンのメニューにそういうものがある事に気付いた人もいるかと思います。
これは、複数の仮想マシンを同一の可用性セットに入れると、最大で3つの障害ドメインに分散配置されるようになるものです。
f:id:myfinder:20160918141159p:plain

Azureの各種サービスはこれらの基盤の上に成立しています。

Azureにおけるリソース管理の概念

ISUCON6予選では "Deploy to Azure" ボタンを押して "リソースグループ" を作成し、その中に仮想マシンやネットワーク、予約済みIPといった "リソース" を展開しました。
Azureにおけるリソース管理はこの "リソースグループ" という単位で複数のリソースを行うのが基本です。
f:id:myfinder:20160918151204p:plain
Azureにおいては全てのものがリソースであり、必ずどこかのリソースグループに属します。
リソースグループは図にある通り、リージョンを超えて広げることができます。もちろん海外のリージョンでもOKです。
リソースへのアクセス制御は、リソース、リソースグループごとにアクセス制御(IAM)で設定をすることができます。
f:id:myfinder:20160918152120p:plain
これとは別に、タグも存在しています。タグはリソースに対して "キー = 値" 形式で付与することができます。
例えば "所属プロジェクトAに属するリソースグループ" とか "本番環境である" とか、そういった分類をするときに利用します。
f:id:myfinder:20160918154210p:plain

ISUCON6予選で皆さんが行った "Deploy to Azure" の仕組み

Azureではリソースを宣言型のテンプレートを用いて管理しています。
ポータル画面から何かのリソースを作った場合、裏側ではすべてテンプレート定義として生成され、管理されています。
逆に言うと、独自のテンプレートを定義して、それを読み込ませることも可能ということです。

今回のISUCON6予選ではこの手法を用いて、運営の用意したテンプレートを各自のAzureアカウント内へサーバ環境をデプロイする手法を取りました。
下記が、そのテンプレートの基本構成です

{
   "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
   "contentVersion": "",
   "parameters": {  },
   "variables": {  },
   "resources": [  ],
   "outputs": {  }
}

テンプレートの要素詳細はドキュメントを見ていただくとして、定義の中で特に重要なのが resouces セクションです。
resouces セクションでは、作成若しくは更新されるリソースを定義します。と、書いてもわかりにくいと思うので、実際の仮想マシンの定義内容を見ていきましょう。

{
      "type": "Microsoft.Compute/virtualMachines",
      "name": "[parameters('vmName')]",
      "apiVersion": "2015-06-15",
      "location": "[variables('location')]",
      "tags": {},
      "properties": {
        "hardwareProfile": {
          "vmSize": "Standard_D2_v2"
        },
        "storageProfile": {
          "imageReference": {
            "publisher": "Canonical",
            "offer": "UbuntuServer",
            "sku": "16.04.0-LTS",
            "version": "latest"
          },
          "osDisk": {
            "name": "[concat(parameters('vmName'), '-os')]",
            "createOption": "FromImage",
            "vhd": {
              "uri": "[concat('https', '://', variables('storageAccountsName'), '.blob.core.windows.net', '/', variables('commonName'), '/', parameters('vmName'), '-os.vhd')]"
            },
            "caching": "None"
          }
        },
        "osProfile": {
          "computerName": "[parameters('vmName')]",
          "adminUsername": "[variables('adminUsername')]",
          "customData": "BASE64Encoded-userdata",
          "linuxConfiguration": {
            "disablePasswordAuthentication": true,
            "ssh": {
              "publicKeys": [
                {
                  "path": "[concat('/home/', variables('adminUsername'), '/.ssh/authorized_keys')]",
                  "keyData": "[parameters('sshPublicKey')]"
                }
              ]
            }
          },
          "secrets": []
        },
        "networkProfile": {
          "networkInterfaces": [
            {
              "id": "[resourceId('Microsoft.Network/networkInterfaces', parameters('vmName'))]"
            }
          ]
        }
      },
      "resources": [],
      "dependsOn": [
        "[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountsName'))]",
        "[resourceId('Microsoft.Network/networkInterfaces', parameters('vmName'))]"
      ]
    },

"type": "Microsoft.Compute/virtualMachines" 部が仮想マシンを表す記述です。Azureで提供されているtypeはCLIの "azure provider list" コマンドで確認することができます。
"name": "[parameters('vmName')]" 部はその名の通りこのリソースの名前を定義する部分です。parametersは運営の用意したテンプレートにあるparametersセクションに記述されたものを指定しています。
locationはvariablesセクションにある "location": "[resourceGroup().location]" という記述の通り、そのリソースグループが定義されたロケーションに合わせる設定になっています。
"properties" で、それぞれのtypeが要求する属性を指定していきます、仮想マシンの場合は "仮想マシンタイプ" "ストレージ設定" "OSプロファイル" "ネットワーク設定" "依存しているリソース" が主に設定を必要とする項目です。

この定義で仮想マシンが立ち上がるわけですが、実際のプロビジョニングは別に走ります。その指定をしているのが "osProfile" -> "customeData" です。
ここにBASE64エンコードしたスクリプト(今回はbashスクリプト)を書いておくことで、起動後にcloud-initによってプロビジョニングする形です。

#!/bin/sh
# init script for isucon6-qualifier

set -ex

export DEBIAN_FRONTEND=noninteractive
apt update
apt install -y --no-install-recommends ansible git aptitude
apt remove -y snapd

cd /tmp &&  wget -O- https://isucon6qimage.blob.core.windows.net/isucon6q/isucon6-qualifier.tar.gz | tar zxvf -
cd /tmp/isucon6-qualifier/provisioning/image/ansible
PYTHONUNBUFFERED=1 ANSIBLE_FORCE_COLOR=true ansible-playbook -i localhost, *.yml --connection=local -t prod
cd /tmp/isucon6-qualifier/provisioning/image
./db_setup.sh
cd /tmp && rm -rf /tmp/isucon6-qualifier && rm -rf /tmp/ansible-tmp-*
shutdown -r now

こういうJSONを書く時に、どういう構成になっているか視覚的に見られる方が良いでしょう。そんな時はARMVIZを使うと、図のようにどんな構成になっているかを可視化できますので、Azureでインフラ構築をする際にはぜひ活用してみてください。
f:id:myfinder:20160918184557p:plain

作成したJSONは、どこかパブリックな場所に置いて https://portal.azure.com/#create/Microsoft.Template/uri/ にエスケープしたURLを渡すか、Azureポータル管理画面の "テンプレートのデプロイ" から投入することもできます。
f:id:myfinder:20160918192239p:plain
他にはAzure CLIのコマンド "azure group deployment create -f " でもデプロイ可能です。

今自分が使っている環境の設定がどうなっているかを見たい、という場合 https://resources.azure.com/ にアクセスすると、閲覧可能なサブスクリプション配下の設定状況を見ることができるので、一度見てみると良いでしょう。

うぇー結局JSONかよめんどくせ〜と思ったあなたへ

AzureといえばMicrosoftが提供しているわけですが、MicrosoftといえばVSCodeも提供しているわけでして、このテンプレートもVSCodeで書くことができます。
azure.microsoft.com
このページに書いてある通り、"ext install azurerm-vscode-tools" すれば、補完を効かせて記述することができます。
VSCodeにはTerminalが統合されているので、そのまま "azure group deployment create -f " で手元からクラウド側に反映させることができるので、portalを触ることなくどんどんデプロイできます。

付録: ストレージアカウント?

初めて仮想マシンを作ったとき、"ストレージアカウント" という言葉が出てきて "???" となった方は多いのではないでしょうか。
Azureのストレージコンセプトは、ストレージサービスの中に複数の機能が存在する構成になっています。
f:id:myfinder:20160918155104p:plain
そして、そのストレージアカウントは、コンテナ―という箱と、その中に作られるVHDやTable、Queueで構成されています。
f:id:myfinder:20160918155224p:plain
単純なハードディスクサービスのようなものは今のところないので、この概念を頭の片隅に置いておいて下さい。