Azure DevOpsでPHP開発。そしてAzure App Serviceにデプロイ

# はじめに
Git（Azure DevOps Repos）にコミットしたら自動でApp Serviceにデプロイするところまでやる！

今回使うサービス
・Azure DevOps（Repos、Pipeline）
・Azure App Service
・Azure SQL Database

SQL Databaseはサーバレスを使ってみたかったから…というのもあるのですが、
使った分だけ課金されるならAzure Database for MySQLより安くなるかなと思い…。
サーバレスのスペック的に本番環境は無理そうだけど、開発環境のコストダウンは実現できそうな？

※Azureの有料サービス使いますので「従量課金アカウント」が必要です。

# Azure Devops Repos
Azure Devopsにログイン。
[+ New project] ボタンを押してリポジトリを作る。

```
Project name：WebApp
Visibility：Private
Version control：Git
Work item process：Agile（今回Repos以外は使わないので何でもよい）
```

このリポジトリにLaravelのソースをコミットします。
別途下の方でコミットする内容を書きます。

# Azure SQL Database
Azure PortalにログインしてSQL データベースに遷移。
[+ 追加] ボタンを押して新しいDBを作る。

スペックは各自で必要なものを選択してください。
今回はサーバレスで作ります。

```
汎用目的
サーバレス
最大 vCores：4
最小 vCores：0.5
データの最大サイズ：32GB
データベース照合順序：Japanese_Bushu_Kakusu_100_CS_AS_KS_WS_SC_UTF8
Advanced Data Security：後で（デフォルトで無料試用30日が選択されているので注意）
```

注）SQL Serverのユーザーパスワードには以下の文字を入れるとダメでした。

```
@^V|}{
```

コンソールやMicrosoftのツールで直接接続する際は大丈夫なんですが、
Laravelから接続してデータ取得しようとするとエラーになります。


# Azure App Service
Azure PortalにログインしてApp Serviceに遷移。
[+ 追加] ボタンを押して新しいアプリを作る。

```
公開：コード
ランタイムスタック：PHP7.2
オペレーティングシステム：Linux
地域：Japan East
App Service プラン：Free F1（実際に使いたい構成に変更してください）
```

その他の項目はデフォルトのまま。

## アプリの環境変数設定

※Laravelを前提とした環境変数です。
基本的に.envファイルは作られませんのでApp Serviceのアプリケーション設定で環境変数を作ります。
こちらの方がパスワードをReposにコミットしなくていいし、設定変更も楽です。
1個ずつ設定するのが面倒なのでコマンド使える人はコマンドで一括設定すると楽です。
Cloud Shell使うとよいでしょう。
ローカルPCでやるならAzure SDKをインストールしてログインしておく必要があります。

```az
az webapp config appsettings set --name [App Serviceアプリ名] --resource-group [リソースグループ] --settings \
  APP_NAME="[Laravelのアプリ名]" \
  APP_ENV="development" \
  APP_KEY="[Laravelのコマンドで生成したkey]" \
  APP_DEBUG="true" \
  APP_URL="https://[App Serviceアプリ名].azurewebsites.net" \
  LOG_CHANNEL="stack" \
  DB_CONNECTION="sqlsrv" \
  DB_HOST="[Azure SQL Database ホスト]" \
  DB_DATABASE="[Azure SQL Database DB名]" \
  DB_USERNAME="[Azure SQL Database DBユーザー]" \
  DB_PASSWORD="[Azure SQL Database DBユーザーパスワード]"
```

Azure Portalでも設定できます。
作成したアプリの設定画面を開いて、左メニューから「構成」
[+ 新しいアプリケーション設定]ボタンから1個ずつ設定追加できます。
![azure_app_service_propaty.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/178991/cca5c8c6-111c-230e-ec5a-628c72a67c24.png)

# アプリの準備
先ほど作成したリポジトリをローカルPCにCloneします。
CloneしたディレクトリにLaravelのソースを配置してLaravel-adminのインストールまでやります。
※Laravel-adminの正式サポートはVer5.5ですが今回はver6の最新版を使ってみました。
（LTSなのでいずれサポートされるはず）
Reposコミットする前に動作チェックしたいのでVagrantで仮想環境を用意しました。
以下、仮想環境で実施しています。

Laravelインストール
https://readouble.com/laravel/6.x/ja/installation.html
publicディレクトリがリポジトリの直下になるように配置し直してください。

Laravel-adminインストール
実行する前に.envにAzure SQL Database（SQL Server）の設定を書いてください。
https://laravel-admin.org/docs/#/en/installation

これで必要なソースとDBにテーブルが作られたはずです。
Azure PortalのWeb UIからAzure SQL Databaseのクエリエディターを開いてadmin_xxxというテーブルが存在するのを確認します。

![azure_sql_query_editor.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/178991/7b8472a1-0848-e0a6-8bb6-b79765f6d35c.png)

デフォルトではデプロイ時にComposerを実行してくれないので自動実行されるように設定ファイルを用意します。
やり方はAzureのドキュメントに解説があります。
https://docs.microsoft.com/ja-jp/azure/app-service/containers/configure-language-php#run-composer

Composer.pharファイルを作る。
https://getcomposer.org/download/

デプロイスクリプトを作成する。

```bash
npm install kuduscript -g
kuduscript --php --scriptType bash --suppressPrompt
```

Composer.pharを使うように調整する。
SetupのDEPLOYMENT_TARGETがwwwrootになるように追加。（Pipelineで調整できそうな気もするが分からなかったので…）
Deplymentの2.の部分は削除。

```bash:.deployment
# Setup
# -----
…
…
if [[ ! -n "$DEPLOYMENT_TARGET" ]]; then
  DEPLOYMENT_TARGET=$ARTIFACTS/wwwroot
else
  KUDU_SERVICE=true
  DEPLOYMENT_TARGET=$DEPLOYMENT_TARGET/wwwroot
fi
…
…

# Deployment
# ----------
echo PHP deployment

# 1. KuduSync
if [[ "$IN_PLACE_DEPLOYMENT" -ne "1" ]]; then
  "$KUDU_SYNC_CMD" -v 50 -f "$DEPLOYMENT_SOURCE" -t "$DEPLOYMENT_TARGET" -n "$NEXT_MANIFEST_PATH" -p "$PREVIOUS_MANIFEST_PATH" -i ".git;.hg;.deployment;deploy.sh"
  exitWithMessageOnError "Kudu Sync failed"
fi

# 3. Initialize Composer Config
initializeDeploymentConfig

# 4. Use composer
echo "$DEPLOYMENT_TARGET"
if [ -e "$DEPLOYMENT_TARGET/composer.json" ]; then
  echo "Found composer.json"
  pushd "$DEPLOYMENT_TARGET"
  php composer.phar install $COMPOSER_ARGS
  exitWithMessageOnError "Composer install failed"
  popd
fi
######
```

Vagrantの仮想環境でページが表示されるのを確認します。
・Laravelの初期ページが見れること
・Laravel-adminのログインページとadminアカウントでログインできること
　※DBライブラリを1行修正しないとSQLの実行でエラー出ます

`getDateFormat()` の `.x` を `.u` に変更します。
Composerでライブラリ更新すると戻ってしまうので最終的にはパッチとか作る。
とりあえずApp Serviceで動くの確認するため手動でやる。

```php:vendor/laravel/framework/src/Illuminate/Database/Query/Grammars/SqlServerGrammar.php
    /**
     * Get the format for database stored dates.
     *
     * @return string
     */
    public function getDateFormat()
    {
        return 'Y-m-d H:i:s.u';
    }
```

App ServiceのルートディレクトリはwwwrootなのでRewriteします。
ルートディレクトリは変更できるらしい…
安いプランのためなのか出来なかった。
（リソースエクスプローラーで設定編集してみたけど変わらんかった）

```cmd:.htaccess
<IfModule mod_rewrite.c>
    RewriteEngine on
    RewriteRule ^.*$ /public/$1 [NC,L,QSA]
</IfModule>
```

確認できたらソース一式をReposにコミットします。

# デプロイ
Azure PortalにログインしてApp Serviceに遷移。
デプロイセンターを開く。

### [Azure Repos]を選択

![azure_app_service_ci1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/178991/fb4bd5c3-1651-67b1-7b74-5e8bfe3f0d49.png)

### [Azure Pipeline]を選択

![azure_app_service_ci2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/178991/e281e4d7-1647-1c24-22e9-cdf57155a097.png)

### Azure Devops Reposの設定
・コード
　上で作成したリポジトリを選択します。
　ブランチはmasterを選択

・ビルド
　フレームワーク：PHP
　バージョン：7.2

![azure_app_service_ci3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/178991/d16ad25f-200f-e7a9-ea61-48e8cfc95ab7.png)

最後に確認画面が表示されるのでそのまま進む。
これでAzure PipelineにApp Serviceにデプロイするための設定が作られます。
App Service経由で設定を作るとエージェントのOSがWindowsだったのでLinuxに変更してください。
CIとCDの設定2か所を変更します。

![azure_app_service_ci4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/178991/335e6eca-6178-2ba6-d723-705e4fbe17f9.png)

![azure_app_service_ci6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/178991/7c3aee0f-dfd5-0bf3-4d4a-d3626f59ef85.png)

Linuxに設定を直したらデプロイセンターに戻ります。
設定がうまくいっていればAzure Pipelineと連携した状態が表示されます。
ここで[同期]ボタンを押すと現在Reposにコミットした最新版でデプロイされます。
Azure Pipelineでデプロイの状況が見れます。

![azure_app_service_ci5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/178991/be4d7a4c-f7d8-36f4-cec3-2c9b894b2f31.png)

デプロイするとデプロイセンターに履歴が追加されていきます。
履歴のリンクからソース・CI・CDそれぞれ結果ページを見れるので便利です。
以降はReposにコミットするたびに自動デプロイされます。
自動デプロイのトリガーはPipeline側で設定変更できます。

![azure_app_service_ci7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/178991/a4733a55-12bf-87bc-5ce3-e89efd50fb0c.png)

# アプリにアクセスしてみる
Laravelの初期ページを見る
https://[アプリ名].azurewebsites.net/

Laravel-adminのログインページを見る
https://[アプリ名].azurewebsites.net/admin

若干SQL Databaseのくだりが強引でしたけど、他のマネージドDBも選べるので実用的なのでは？
上位のプランではステージングスロットなるものが付属していてなかなか便利そうである。
https://docs.microsoft.com/ja-jp/azure/app-service/deploy-staging-slots

App ServiceもSQL Database（サーバレス）もアクセスせずに放っておくと勝手に落ちるみたいです。
そして接続しようとすると起動されます。（ちょっと待たされたり、エラーになったり）
WebUIに起動ボタンはない。
本番稼働でアクセス0になるのはあまりないから大丈夫なのかな？
これで開発環境構築すると開発メンバーが使ってない時は勝手に落ちてお財布にやさしそう。
Azure Devopsは優秀なのでぜひ使ってみてほしい。