Check! Azure Web Apps + MySQL をARMテンプレートでデプロイ(シブヤクハックでつくった環境解説 (428チーム))

こんにちは、 @dz_ こと大平かづみです。

Prologue - はじめに

先日開催された シブヤクハック (ハッカソン)では、大学生のメンバーと一緒に挑戦しました!

メンバーが少なく、それぞれの作業がクリティカルパスという状況。その中でわたしが担当したインフラ構築についてご紹介します。

なお、ここでは全体の説明が長くなってしまったので、デプロイ部分だけ知りたい方は「ARM テンプレートで各種リソースを構築する」「デプロイ後の作業」の項をご参照ください。

テーマ

「価値観の違いで埋もれている魅力的なスポットを見つける!」

私たちのチームでは、国外からいらした方々にとって魅力的な渋谷区のスポットをを、探しやすく、活性化させるためには?をテーマにしました。

様々なアプローチのアイディアが集まり、ハッカソンの段階では下記に注目し、それらを検索できるアプリからはじめることにしました。よく検索・利用される条件データをバックエンドで蓄積し、区役所側でそのデータを可視化できることにより、地域活性に活かせるというシナリオです。

  • 支払い方法の違い(クレジットカード、アプリ決済文化)
  • 食の制限(ビーガン、グルテンフリー、ハラールなど)

先にいいますと、残念ながら、アプリ完成までは至らなかったのです。ですが、担当部分のバックエンドのつなげこみはできたので、主にその部分をここでご紹介します。

開発のまとめ

システム要件

ひとりはモバイルアプリ開発、ひとりはサーバーサイドをPHPで開発ができるということで、それらをつなげる全体設計とインフラ構築をわたしが担当しました。

用途 構成
Web サーバー CakePHP 3.5, PHP 7. x
データベース MySQL
ソース管理 git
  • サーバーサイド担当者の希望により CakePHP で開発。
  • データベースは、Power BI からもつなげたく、SQL Server と併せて検討の末、Power BI for Desktop でコネクタをインストールすればつなげるとわかったので、CakePHP と相性の良い MySQL を採用。

システム構成

これを、Azure で手っ取り早く構築するべく、以下の構成にしました。

20180407_shibuyaku-hack_001.png

この環境 Azure Web App + MySQL をほぼ一発で構築する(再現版)

当日は手探りで Azureポータルからポチポチ作ってた上記の構成を、せっかくなので ARM (Azure Resouce Manager) テンプレートに落とし込んで公開します。

インフラの構成

役割 構成 説明
Web サーバー Azure Web App Windows (IIS)、PHP 7.1
データベース Azure Database for MySQL MySQL 5.7、Azure内からの接続のみ許可
ソース管理 GitHub Web App に CI を設定して反映
  • 当初、Web App on Linux で試みたが、 ext-intl を読み込めないようで断念。( phpinfo をみるかぎりでは Itnl は入ってるのに。)
  • Web App の Windows (IIS)では、PHP 7.2 が最新だが、 pdo_mysql が含まれていなかったので、 7.1 を採用。

ソースコードを Web App に最適化する(対応済み)

PHPの開発は、メンバーのローカル(Mac)で行われていたので、 Web App (IIS) で動作するように、ソースコードをフォークして一部手を加えました。以下は手を加えた差分です。(一部、検証コードもあります。)

最適化の内容

  • データベース接続情報( host, username, password, database ) を環境変数から取得するよう修正。
    • 開発リポジトリにマージする際は、 .env などで同環境変数指定することにより対応可能の見込み。
  • デフォルトの Apache 用 .htaccess を、 IIS のルーティングファイル web.config に置換え

ARM テンプレートで各種リソースを構築する

ということで、Azure Web App と Azure Database for MySQL をデプロイする ARMテンプレートを作りました。下記リポジトリに、にハッカソン当日の環境をデプロイする用のパラメータとともに置きましたのでご参考になればさいわいです。Azure CLI などでデプロイ可能です。

参考までに、ARMテンプレートを掲載します。

azuredeploy.json
{
    "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
    "contentVersion": "1.0.0.0",
    "parameters":{
        "hostingPlanSkuName": {
            "type": "string",
            "defaultValue": "B1",
            "allowedValues": ["FREE", "SHARED", "D1", "B1", "B2", "B3",
                "S1", "S2", "S3", "F1", "P1", "P1V2", "P2", "P2V2", "P3", "P3V2"]
        },
        "databaseForMySqlAdminName": {
            "type": "string"
        },
        "databaseForMySqlAdminPassword": {
            "type": "securestring"
        },
        "databaseForMySqlDatabaseName": {
            "type": "string"
        },
        "databaseForMySqlTier": {
            "type": "string",
            "defaultValue": "Basic",
            "allowedValues": ["Basic", "GeneralPurpose", "MemoryOptimized"]
        },
        "databaseForMySqlFamily": {
            "type": "string",
            "defaultValue": "Gen4",
            "allowedValues": ["Gen4", "Gen5"]
        },
        "databaseForMySqlCores": {
            "type": "int",
            "defaultValue": 1,
            "allowedValues": [1, 2, 4, 8, 16, 32]
        },
        "databaseForMySqlVersion": {
            "type": "string",
            "defaultValue": "5.7",
            "allowedValues": ["5.7", "5.6"]
        },
        "phpVersion": {
            "type": "string",
            "defaultValue": "7.1",
            "allowedValues": ["5.6", "7.0", "7.1", "7.2"]
        },
        "ciRepositoryUrl": {
            "type": "string",
            "defaultValue": "git@github.com:dzeyelid/428.git"
        },
        "ciRepositoryBranch": {
            "type": "string",
            "defaultValue": "dev"
        }
    },
    "variables": {
        "location": "[resourceGroup().location]",
        "webAppName": "[concat(resourceGroup().name, 'web')]",
        "hostingPlanName": "[concat(resourceGroup().name, 'appplan')]",
        "databaseForMySqlName": "[concat(resourceGroup().name, 'mysql')]",
        "databaseForMySqlSku": "[concat(variables('tierSymbol')[parameters('databaseForMySqlTier')], '_', parameters('databaseForMySqlFamily'), '_', parameters('databaseForMySqlCores'))]",
        "tierSymbol": {
            "Basic": "B",
            "GeneralPurpose": "GP",
            "MemoryOptimized": "MO"
        }
    },
    "resources": [
        {
            "name": "[variables('hostingPlanName')]",
            "type": "Microsoft.Web/serverfarms",
            "apiVersion": "2016-09-01",
            "location": "[variables('location')]",
            "kind": "app",
            "sku": {
                "name": "[parameters('hostingPlanSkuName')]"
            }
        },
        {
            "name": "[variables('webAppName')]",
            "type": "Microsoft.Web/sites",
            "apiVersion": "2016-08-01",
            "location": "[variables('location')]",
            "dependsOn": [
                "[resourceId('Microsoft.Web/serverfarms', variables('hostingPlanName'))]"
            ],
            "kind": "app",
            "properties": {
                "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('hostingPlanName'))]",
                "siteConfig": {
                    "phpVersion": "[parameters('phpVersion')]",
                    "connectionStrings": [
                        {
                            "name": "host",
                            "ConnectionString": "[concat(variables('databaseForMySqlName'), '.mysql.database.azure.com')]",
                            "type": "MySql"
                        },
                        {
                            "name": "username",
                            "ConnectionString": "[concat(parameters('databaseForMySqlAdminName'), '@', variables('databaseForMySqlName'))]",
                            "type": "MySql"
                        },
                        {
                            "name": "password",
                            "ConnectionString": "[parameters('databaseForMySqlAdminPassword')]",
                            "type": "MySql"
                        },
                        {
                            "name": "database",
                            "ConnectionString": "[parameters('databaseForMySqlDatabaseName')]",
                            "type": "MySql"
                        }
                    ]
                }
            },
            "resources": [
                {
                    "apiVersion": "2015-08-01",
                    "name": "web",
                    "type": "sourcecontrols",
                    "dependsOn": [
                        "[resourceId('Microsoft.Web/sites', variables('webAppName'))]"
                    ],
                    "properties": {
                        "RepoUrl": "[parameters('ciRepositoryUrl')]",
                        "branch": "[parameters('ciRepositoryBranch')]",
                        "IsManualIntegration": false
                    }
                }
            ]
        },
        {
            "name": "[variables('databaseForMySqlName')]",
            "type": "Microsoft.DBforMySQL/servers",
            "apiVersion": "2017-12-01",
            "location": "[variables('location')]",
            "sku": {
                "name": "[variables('databaseForMySqlSku')]"
            },
            "properties": {
                "version": "[parameters('databaseForMySqlVersion')]",
                "administratorLogin": "[parameters('databaseForMySqlAdminName')]",
                "administratorLoginPassword": "[parameters('databaseForMySqlAdminPassword')]",
                "sslEnforcement": "Disabled",
                "createMode": "default"
            },
            "resources": [
                {
                    "name": "[parameters('databaseForMySqlDatabaseName')]",
                    "type": "databases",
                    "apiVersion": "2017-12-01",
                    "dependsOn": [
                        "[resourceId('Microsoft.DBforMySQL/servers', variables('databaseForMySqlName'))]"
                    ],
                    "properties": {
                        "charset": "utf8",
                        "collation": "utf8_general_ci"
                    }
                }
            ]
        }
    ]
}

なお、ハッカソンでスピード勝負のため、MySQL のSSL接続要求は解除しています。

デプロイ後の作業

さて、残念ながら Azure Database for MySQL がGAしたばかりのためか、下記の設定値は ARM テンプレートから設定できなかったので、デプロイ後に手動で設定します。

Azure Database for MySQL で、Azure サービスからの接続を許可する

Azure ポータルで、 Azure Database for Azure のプレートを開き、下記の設定を保存します。

image.png

データベースを初期化する

上記の接続を許可すると、Web App から MySQL に接続できるようになるので、データベースのマイグレーションを行います。

Azure ポータルで、Azure Web App のプレートを開き、「コンソール」を開いて、CakePHP のマイグレーションを行います。

image.png

CakePHP のマイグレーションについては、ドキュメントをご参照ください。

CakePHPマイグレーション
bin\cake migrations migrate

動作確認!

ここまでできましたら、 http://<resource group name>web.azurewebsites.net/users にアクセスし、エラーが出なければデプロイ完了です!

いえーい!(/・ω・)/

可視化環境について

さいごに、可視化については、Power Bi Desktop から上記の Azure Database for MySQL に対して接続する動作確認のみ行いました。

役割 構成 説明
可視化 Power BI Desktop 職員のPC上の利用を想定、MySQL コネクタ利用
  • Power BI は、ハッカソンの都合上、アカウント登録なしに使えるデスクトップ版を利用。(オンライン版は組織アカウントが必要なので割愛)
  • また、Power BI Desktop のデータソースで MySQL を利用するには、Power BI データ ソースの前提条件 - Power BI | Microsoft Docs の MySQL コネクタをインストールしたうえで利用。

実際には、オンライン版を利用したほうが、インストール不要で、データベースとの接続もセキュアにできるかと思います。ただ、こちらは現状 MySQL とは接続できないみたいなので、データベースを変えるか、Microsoftさんにリクエストするかが必要そうです(笑) :eyes:

ご報告は以上です!

Epilogur - おわりに

いざ、ARMテンプレートでやろうとすると、当日手作業でやった部分を思い出しつつ、あの時なんでこの選択したんだっけと改めて検証したりと、この記事を書き上げるまでに時間がかかってしまいました。

そのおかげで、Azure Web App と Azure Database for MySQL の見識が広がったので、私としては良い学びとなりました!

ふりかえり大事!

ということで、メンバーも、スタッフの皆々様方も、お疲れさまでした!

よい機会を設けてくださり、ありがとうございました!! :clap:

Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.