はじめに
この記事は、Microsoft Azure Advent Calendar 2016の2日目の記事です。
前日は nntsugu@github さんですが、記事内容は… null pointer exceptionですね
TL;DR
- Azure運用で得たバッドノウハウを紹介します
- 公式で特に説明のない項目について言及しています
- 何がベストなのか、その正解はここには無いです
今回の内容
数々のバッドノウハウ(hacks)
1:複数リポジトリのデプロイ方法
2:デプロイスクリプトの待ち時間の伸ばし方
3:portalのCORS設定を使うとヘッダが触れなくなる
4:SiteExtensionの Reverse Proxy は無口である
5:設定値の保持に環境変数を使う
6:Web Appsの無料枠でsslを使う
良くない構成を回避するには
要件
よくあるwebサービスは以下のようなものでしょうか。
バックエンド
+ PHP
+ composerを使ってパッケージ管理する
+ DB接続あり
+ ユーザー認証あり
フロントエンド
+ Node.js
+ npmを使ってパッケージ管理する
+ npmを使ってビルドする
そんな中色々やりくりしたのが以下hacksです。
hacks01:アンチパターン:サブモジュール地獄
WebApps環境構築用リポジトリ
├フロントエンドのソース
└バックエンドのコード
repos | Dir |
---|---|
WebApps環境構築用リポジトリ | / |
フロントエンドのサブモジュール(PHP) | /app/ |
バックエンドのサブモジュール(Node.js) | /view/ |
公開領域のroot: /app/public/
そもそもサブモジュールにアクセス権ないし、認証に使われるDeployKeyって一つじゃない?
GitHubでsubmodule連携するとき、
WebAppsで生成されたDeployKeyをリポジトリのDeployKeyからいったん削除して、
GitHubユーザのSSH Keysに再登録することになります。
Deploy keyはインスタンス内の D:\home\.ssh\id_rsa.pub
に吐き出されているので、
それを拾ってきて貼り付けます。
尚、ハック的なやり方で手間もかかるのでお勧めはしません!
hacks02:アンチパターン:複雑すぎるビルドスクリプト
例えばNode.jsとPHPのハイブリッドアプリの場合、
-
npm install
を実行する -
npm run build
を実行する -
composer install
を実行する - 公開領域にビルドしたアプリを更新する
- データベースをmigrateする
などのタスクが考えられます。
それぞれをファイルに分割してコールすることもできますが、
@echo off
call deploy.node.cmd
call deploy.front.cmd
call deploy.api.cmd
echo Copy frontend components.
call xcopy "%DEPLOYMENT_TARGET%\front\dist" "%DEPLOYMENT_TARGET%\api\public" /E /H /R /Y
なんか一気にやっちゃってますが、これどこかでエラー出たら残りのタスクほったらかして死にます。
良くないですね。
綱渡りだし危険なのでやめましょう。
罠1:package.jsonのアレコレ
サブモジュールのデプロイ等で、プロジェクトのroot以外にpackage.jsonがある場合、
デプロイスクリプトを書き換えなければいけません。
:: 2. Install npm packages
IF EXIST "%DEPLOYMENT_TARGET%\package.json" (
pushd %DEPLOYMENT_TARGET%
echo Install npm components.
call npm install --no-optional
echo Build frontend components.
call npm run build
IF !ERRORLEVEL! NEQ 0 goto error
popd
)
これを↓のようにディレクトリ指定するなど
:: 2. Install npm packages
IF EXIST "%DEPLOYMENT_TARGET%\view\package.json" (
pushd %DEPLOYMENT_TARGET%\view
echo Install npm components.
call npm install --no-optional
echo Build frontend components.
call npm run build
IF !ERRORLEVEL! NEQ 0 goto error
popd
)
また、以下のようにpackage.json
でNode.jsのバージョンを指定する場合、
package.json
が/view/package.json
のように、プロジェクトのrootにない場合も、
ディレクトリの変更を追従しないと、Nodeのバージョンを変更してくれません。
{
"engines":{
"node": "^6.9.1"
}
}
罠2: SCM_COMMAND_TIMEOUT
アウトプットがないままの待ち時間がある処理 (PHP composer など)
は処理に時間がかかるとデプロイスクリプトのタイムアウトで落とされる。
あとデプロイするバッチの中でY/n確認など入力待ちさせる処理を殺し忘れるとタイムアウトを待つしかなくなる。
SCM_COMMAND_IDLE_TIMEOUT
の値に秒数を入れると伸ばせるが、諸刃の剣。(長すぎるとデプロイ終わらない地獄)
デフォルトは60が設定されている
hacks03:アンチパターン:無視されるhttpヘッダ
- Azure portal で各インスタンスごとにCORSを指定することができる。
- しかしそこで設定設定してしまうと、カスタムヘッダを蹴るようになります。その方法では
Access-Control-Allow-Credentials
ヘッダを付けられない。
Document Microsoct Azure | App Service の CORS と Web API の CORS
CORSを使う上で credencials
を有効にする場合、各インスタンスのCORSの設定に何もいれないようにします。
その上でアプリケーションでヘッダを書ききるか、 Web.config
ファイルに書きましょう。
参考:
http://m2wasabi.hatenablog.com/entry/2016/11/30/123538
hacks04:微妙ポイント:静かなる Reverse Proxy
Site ExtensionでReverseProxyがあるけど、
転送元の情報を何ら付与せずに、転送先サーバへリクエストを書き換えて送っているだけなので、
転送先サーバから見るとどこからアクセスしてきているのかわからない。
APIにアクセスさせるWebアプリを分けようとしたときに辛い。
また、転送先サーバはWeb.config
のrewrite roulesに記述するのですが、
Azure上では、ここに環境設定を組み込むことが難しいので、
直接書かなければいけません。
これが自動化を難しくしています。
Reverse Proxyのソースは公開されていないので、自分で書くしかないかもしれません。
自動デプロイに組み込み可能なナイスなリバースプロキシ拡張があれば紹介してください。
hacks05:環境変数にサーバ固有の設定を投入する
アプリ設定の項目で、アプリごとの環境変数を指定できます。
環境変数の拾い方は、処理系によって様々です。
実際の拾い方は以下です
Node.js
process.envから拾うことができます
Webpack with Node.js
Webpack definision プラグインで、リテラルを指定することにより、
ビルドした後にも、ビルド時の環境変数を使うことができます。
参考:
http://qiita.com/mikakane/items/5ab96c4c7e187ab6c9f1
PHP
phpdotenvを使って、
.env
ファイルに書き込むのが良いかもしれません。
アプリケーション内では、.env
ファイルを通して環境設定させ、
ローカル開発環境など他のサーバで動かす場合、.env
ファイルをかきかえるようにしましょう。
Azure特有の運用方法として、デプロイスクリプトで以下の様に記述して、
azureの場合は環境設定ファイルを参照するようにしてもいいかもしれません。
APP_ENV=production
DB_HOST=${MYSQL_DB_HOST}
DB_NAME=${MYSQL_DB_NAME}
...
copy /Y .env.azure .env
hacks06:Web Appsの無料枠でsslを使う
WebAppsのSSLアクセスは有料サービスプランでしか使えないはずですが、
同じサービスプランにApp Service認証がONのインスタンスがあると、
他のインスタンスでもhttpsでのアクセスが有効になっています。
なので、認証が有効なダミーWebAppsを立てれば、httpsでアクセスできるんじゃないかと。
望ましいバックエンド構成
デプロイスクリプトはできるだけシンプルに。
複数言語のハイブリッドサーバは収拾がつかなくなる(上記でやってしまいましたが…)のでやめましょう。
継続的デプロイをきちんと活用するなら、
利用するアーキテクチャごとでそれぞれインスタンスを立てるべきです。
デプロイスクリプトの作成
何の事前知識もなしにカスタムデプロイのスクリプトを記述するのは非常に困難です。
そこでデプロイスクリプトのスキャフォールドはazure-cliを使って作成します。
Node.jsのライブラリなのでエコで環境にやさしいです。
npm install -g azure-cli
リポジトリのrootに立って、以下コマンドで.deployment
と deploy.cmd
が生成されます
Node
azure config mode asm
azure site deploymentscript --node
PHP
azure config mode asm
azure site deploymentscript --php
DotNet
azure config mode asm
azure site deploymentscript --aspWAP pathToYourWebProjectFile.csproj -s pathToYourSolutionFile.sln
生成されたファイルをまとめてリポジトリにコミットすると、
Azureにデプロイされたときに自動的にデプロイスクリプトとして動きます。
Node.jsや.NETのサンプルは探せば見つかるのですが、
PHPのサンプルが見当たらなかったので作りました。
自前で書いたのは以下の部分のみです。
:: 2. Install composer
IF EXIST "%DEPLOYMENT_TARGET%\composer.json" (
pushd "%DEPLOYMENT_TARGET%"
IF NOT EXIST "%DEPLOYMENT_TARGET%\composer.phar" (
echo Download composer installer
call :ExecuteCmd php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
IF !ERRORLEVEL! NEQ 0 goto error
echo Install Composer
call :ExecuteCmd php composer-setup.php
IF !ERRORLEVEL! NEQ 0 goto error
echo Deleting installer
call :ExecuteCmd php -r "unlink('composer-setup.php');"
IF !ERRORLEVEL! NEQ 0 goto error
)
echo Composer self update
call :ExecuteCmd php composer.phar self-update
IF !ERRORLEVEL! NEQ 0 goto error
echo Install Composer plugin
call :ExecuteCmd php composer.phar global require "hirak/prestissimo"
IF !ERRORLEVEL! NEQ 0 goto error
echo Composer install
call :ExecuteCmd php composer.phar install --no-dev
IF !ERRORLEVEL! NEQ 0 goto error
popd
)
:: 3. Laravel update
IF EXIST "%DEPLOYMENT_TARGET%\.env.azure" (
pushd "%DEPLOYMENT_TARGET%"
echo Copy .env file
call :ExecuteCmd copy /Y ".env.azure" ".env"
echo Migrate database
call :ExecuteCmd php artisan migrate --force
IF !ERRORLEVEL! NEQ 0 goto error
popd
)
APIサーバとフロントエンドを別にする
フロントエンドとバックエンドでそれぞれインスタンスを分ける場合、
以下のようなアプリケーション構成が考えられます。
CASE1:APIサーバはCORSでやりとり
- 認証周りについて、フロントエンド、バックエンドのそれぞれにスキルが求められる
- トラフィックに無駄がなくネットワークにやさしい
CASE2:フロントエンドとAPIサーバはリバースプロキシでやりとり
- CORSのめんどくさい所はインフラで吸収できる
- プログラマーから見るとサイト内リンクと同じなので、2000年代スキルのロートルエンジニアでも使える
最後に
面倒なこと抜きにしてCIサーバにローカルGitをマネジメントさせるのがいいんじゃないか?
と思いつつ、最良の構成についてはいまだに迷子です。
Azureの闇についてはここにまだまだ書ききれないほどありますが、
その中でもなんとか光明を掴み取って前に進みたいと思います。
あと、 App Service on Linux も便利そうなので使ってみたいけど、
従来のApp Serviceと同居しない&無料枠は存在しないので、お財布と相談しながら使わざるを得ないですね。
さて、次回のアドベントカレンダーは…
Microsoft Azure Advent Calendar 2016
明日は @ksasaki さんです。よろしくお願いします!