Google Cloud PlatformでMagento2 〜 RedisとCloud SQLとLoad Balancing


[Magento Advent Calender 2016](http://www.adventar.org/calendars/1372)のネタを探していたところGoogle Cloud Platformのトライアルが[60日間$300まで無料](https://cloud.google.com/free-trial/)が目に入ったのと11月から[東京リージョン](https://cloud.google.com/about/locations/japan/)が正式運用されたことが気になっていたのでこれ幸いと構築してみました

## ゴール

インターネット - Load Balancing（負荷分散） - [Magento2 - Redis] - Cloud SQL

## VM インスタンスの作成

### ベースとなるVMインスタンスを作成

[BitnamiのMagento2](https://console.cloud.google.com/launcher/details/bitnami-launchpad/magento)をCompute Engine上で起動してベースとなるインスタンスを作成します。

![スクリーンショット 2016-12-15 16.09.46.png](https://qiita-image-store.s3.amazonaws.com/0/50629/c5ecf3c9-bc8c-0608-03f5-3a55ada8aeec.png "スクリーンショット 2016-12-15 16.09.46.png")


構成（2016/12/15時点・抜粋）

* Debian (8)
* Apache (2.4.23)
* Magento (2.1.2)
* Memcached (1.4.31)
* MySQL (5.7.15)
* PHP (7.0.11)
* Varnish (4.1.0)

Zoneはasia-northeast1-a,b,cのいずれか（東京リージョン）を選んでください。Magento2はメモリを結構消費するのでn1-standard-2を選択しています。Cloud SQLを使うのでディスクI/Oは気にしないということでDisk typeはStandard Persistent Diskを選んでいます。External IPはEphemeral（静的IPじゃないやつ）を割り当てます。

起動してわかったのですが、Varnishが80番ポートで待ち受けていました。apacheは81番と443番ですね。ロードバランサでSSL Terminationして80番に渡せられれば良いですね。

![スクリーンショット 2016-12-15 16.12.28.png](https://qiita-image-store.s3.amazonaws.com/0/50629/ca84ea8d-6b8e-aabc-2ce7-b8d2235813ab.png "スクリーンショット 2016-12-15 16.12.28.png")

デプロイ直後の画面です。管理画面のパスワードも自動生成されているのでこのままでもSite Addressからアクセス可能です。Magento2に最適化された構成になっているためストレスなく動作していますが、少しづつ高速化していきます。

作成したVMインスタンスの設定を確認すると、デフォルトのアクセス権が適用されているためCloud API アクセス範囲がほとんど無効化されていました。今回の手順的にはCloud SQLとストレージを有効化する必要があります。[Cloud SQL Proxyのリファレンス](https://cloud.google.com/sql/docs/compute-engine-access?hl=ja#gce-connect-proxy)にも書いてありますがアクセス範囲を変更できるのはインスタンス作成時なので、完全に無駄な作業ですが作成したVMインスタンスをベースに再作成します。

![スクリーンショット 2016-12-16 18.09.56.png](https://qiita-image-store.s3.amazonaws.com/0/50629/95648cbd-2b6a-5846-8200-aa6208fda988.png "スクリーンショット 2016-12-16 18.09.56.png")


### VMインスタンスのコピー

VMインスタンスを停止します。
続いてメニューからスナップショットを選んで「スナップショットを作成」します。
ソースディスクに先程作成したVMインスタンスを選んでください。

![スクリーンショット 2016-12-15 16.35.18.png](https://qiita-image-store.s3.amazonaws.com/0/50629/5d86b71f-f756-dd8f-5856-81a8a9d21e5f.png "スクリーンショット 2016-12-15 16.35.18.png")

スナップショットが取得できたら、VMインスタンスを新しく作成します。

![スクリーンショット 2016-12-15 17.02.29.png](https://qiita-image-store.s3.amazonaws.com/0/50629/e51ab1ee-06d9-692c-7e41-d622ddf5b776.png "スクリーンショット 2016-12-15 17.02.29.png")

ブートディスクを先程作成したスナップショットに変更してください。IDとAPIへのアクセスについて「すべての Cloud API に完全アクセス権を許可」を選択してください。ファイアウォールにはHTTPとHTTPSのトラフィックを許可しておいてください。

これでようやくCloud APIが使えるMagento2インスタンスが手に入りました。

## Redis

### Redisサーバの作成

[BitnamiのRedis](https://console.cloud.google.com/launcher/details/bitnami-launchpad/redis)をCompute Engine上で起動してRedisサーバを作成します。

![スクリーンショット 2016-12-15 17.12.54.png](https://qiita-image-store.s3.amazonaws.com/0/50629/7febc8c3-c921-0579-e90c-9b7c1152fad8.png "スクリーンショット 2016-12-15 17.12.54.png")

RedisなのでCPUは1コアで良いと思います。あとは6379番ポートが空いていることを確認してください。

### RedisとMagento2インスタンスを連携

#### Redisサーバのファイアウォールを設定

Magento2インスタンスの内部IPを調べます。

![スクリーンショット 2016-12-15 17.22.59.png](https://qiita-image-store.s3.amazonaws.com/0/50629/3b0f694b-acd1-3324-0776-f450f7c0357c.png "スクリーンショット 2016-12-15 17.22.59.png")

上のスクリーンショットだと内部IPに10.146.0.7が割り当てられています。そのままファイアウォールの設定に移るためネットワークのdefaultをクリックします。ファイアウォールルール一覧が表示されるので、6379番ポートの設定（Redis用）を見つけて編集します。今回は`Redisインスタンス名-tcp-6379`という名前で生成されていました。

![スクリーンショット 2016-12-15 17.26.52.png](https://qiita-image-store.s3.amazonaws.com/0/50629/e511e77c-3e9d-8e68-dfee-e198b3aa2ae0.png "スクリーンショット 2016-12-15 17.26.52.png")

Magento2インスタンスの内部IPのみを指定するためソースIPの範囲を`Magento2の内部IP/32`に変更して保存してください（スクリーンショット参考）。Magento2インスタンスを追加する度に範囲指定を追加する必要があります。

#### Magento2の設定を変更

SSHでMagento2インスタンスに接続します。

![スクリーンショット 2016-12-15 17.34.44.png](https://qiita-image-store.s3.amazonaws.com/0/50629/224a0ff8-2c11-d061-6ac3-d42629acdd54.png "スクリーンショット 2016-12-15 17.34.44.png")

VMインスタンスの詳細画面からSSHボタンをクリックするとブラウザでコンソールが起動します。

`/opt/bitnami/apps/magento/htdocs/`がMagento2のドキュメントルートなので、設定ファイル`env.php`を開いて`session`の設定を変更、`cache`の設定を追記します。

```sh
$ sudo vim /opt/bitnami/apps/magento/htdocs/app/etc/env.php 

// ここから変更.
  // 'session' =>
  // array (
  //   'save' => 'files',
  // ),
  
'session' =>
   array (
   'save' => 'redis',
   'redis' =>
      array (
        'host' => '10.146.0.8',
        'port' => '6379',
        'password' => 'v1TJnCNf',
        'timeout' => '2.5',
        'persistent_identifier' => '',
        'database' => '0',
        'compression_threshold' => '2048',
        'compression_library' => 'gzip',
        'log_level' => '1',
        'max_concurrency' => '6',
        'break_after_frontend' => '5',
        'break_after_adminhtml' => '30',
        'first_lifetime' => '600',
        'bot_first_lifetime' => '60',
        'bot_lifetime' => '7200',
        'disable_locking' => '0',
        'min_lifetime' => '60',
        'max_lifetime' => '2592000'
    )
),
// ここまで変更.

// (省略)

// ここから追記.
'cache' =>
array(
   'frontend' =>
   array(
      'default' =>
      array(
         'backend' => 'Cm_Cache_Backend_Redis',
         'backend_options' =>
         array(
            'server' => '10.146.0.8',
            'password' => 'v1TJnCNf',
            'port' => '6379'
            ),
    ),
    'page_cache' =>
    array(
      'backend' => 'Cm_Cache_Backend_Redis',
      'backend_options' =>
       array(
         'server' => '10.146.0.8',
         'port' => '6379',
         'password' => 'v1TJnCNf',
         'database' => '1',
         'compress_data' => '0'
       )
    )
  )
),
// ここまで追記.

);
```

'server(host)'と'password'はそれぞれ3箇所ありますが、Redisサーバの内部IPとパスワードを指定してください。Redisサーバ構築時に自動生成されたパスワードは、カスタムメタデータのbitnami-base-passwordで確認できます。

![スクリーンショット 2016-12-15 17.53.57.png](https://qiita-image-store.s3.amazonaws.com/0/50629/6735f644-22e6-619f-dbbf-32a4dd0cf9ad.png "スクリーンショット 2016-12-15 17.53.57.png")

設定できたら動作確認のためRedisサーバにSSHで接続してモニタします。

```sh
$ redis-cli -a v1TJnCNf monitor
```

モニタしている状態でMagento2にブラウザからアクセスするとRedisが動作しているのが確認できると思います。

![スクリーンショット 2016-12-15 18.06.55.png](https://qiita-image-store.s3.amazonaws.com/0/50629/ca23fe88-7d22-7d3e-fa31-bdeee717ed7a.png "スクリーンショット 2016-12-15 18.06.55.png")

参考: [Redis for session storage](http://devdocs.magento.com/guides/v2.0/config-guide/redis/redis-session.html) [Redis for page caching](http://devdocs.magento.com/guides/v2.0/config-guide/redis/redis-pg-cache.html)

## Cloud SQL

### Google Cloud Storage

MySQLダンプをCloud SQLに転送するために[Google Cloud Storage](https://console.cloud.google.com/storage)を利用します。

Magento2インスタンスから[gsutil](https://cloud.google.com/storage/docs/gsutil)でGoogle Cloud Storageにアクセスしてバケットを作成します。

```sh
$ gsutil mb -l ASIA-NORTHEAST1 gs://magento2-sql-backup-bucket
```

バケット名はグローバルに一意なので409エラーが返却されたときは別の名前にしてください。
成功するとオブジェクトのないバケットが作成されます。

![スクリーンショット 2016-12-15 18.46.34.png](https://qiita-image-store.s3.amazonaws.com/0/50629/f6b3c55e-9bfd-f811-e593-727eb22a3176.png "スクリーンショット 2016-12-15 18.46.34.png")

### ダンプファイルの作成・転送

Magento2が利用しているMySQLデータベース・ユーザ・パスワードを設定ファイル`/opt/bitnami/apps/magento/htdocs/app/etc/env.php`を開いて確認します。

```sh
$ sudo vim /opt/bitnami/apps/magento/htdocs/app/etc/env.php
```

![スクリーンショット 2016-12-16 18.20.24.png](https://qiita-image-store.s3.amazonaws.com/0/50629/4c09a857-f880-707a-746e-1acf0a38bc68.png "スクリーンショット 2016-12-16 18.20.24.png")


[公式ドキュメント:mysqldump ファイルの作成](https://cloud.google.com/sql/docs/import-export/creating-mysqldump-csv#mysqldump)を参考にダンプファイルを作成します。


```sh
$ mysqldump --databases bitnami_magento -h localhost -u bn_magento -p --hex-blob --skip-triggers --default-character-set=utf8 > magento_backup
.sql
```

作成したダンプファイルを先程作成したGoogle Cloud Storageのバケットに転送します。

```sh
$ gsutil cp ./magento_backup.sql gs://magento2-sql-backup-bucket
```

Google Cloud Storageにアクセスして転送されたことを確認してください。

![スクリーンショット 2016-12-15 18.59.20.png](https://qiita-image-store.s3.amazonaws.com/0/50629/ee0ffeaf-122d-252c-8fcd-1ebda8c6d253.png "スクリーンショット 2016-12-15 18.59.20.png")

### Cloud SQLの起動

[Google Cloud SQL](https://console.cloud.google.com/sql)にアクセスします。第2世代はMySQLデータベースです。サポートバージョンは5.6,5.7、エンジンはInnoDBのみなので、Magento2には必要十分だと思います。

![スクリーンショット 2016-12-16 10.28.48.png](https://qiita-image-store.s3.amazonaws.com/0/50629/31777540-43be-9198-3a48-5b0e2a33782a.png "スクリーンショット 2016-12-16 10.28.48.png")

ロケーションはMagento2インスタンスと同じにしておくと良いでしょう。通常時や[HAな構成](https://cloud.google.com/sql/docs/configure-ha)にした時の性能など未検証ですが、定期バックアップが取れるだけでも幸せですね。

![スクリーンショット 2016-12-16 11.09.07.png](https://qiita-image-store.s3.amazonaws.com/0/50629/dda0a9aa-228f-88ce-b662-e164d7d9ac1e.png "スクリーンショット 2016-12-16 11.09.07.png")


作成されたインスタンスにはIPv4アドレスが割り当てられています。内部IPでアクセスできないの？ってなったのですが、ネットの声を拾ってみるとみんな思うところは同じみたいですね。
インスタンス接続名はMagento2の設定に使用するので覚えておいてください。

### MySQLダンプのインポート

インスタンスの詳細から「インポート」をクリックして、Cloud Storageファイルを選択します。

![スクリーンショット 2016-12-16 11.17.33.png](https://qiita-image-store.s3.amazonaws.com/0/50629/8391bfc7-4f8d-9c35-ff8d-1f6f23d5da65.png "スクリーンショット 2016-12-16 11.17.33.png")

初期データのみなので、インポートは1分もかからずに終わります。

### MySQLユーザの作成

Magento2用のユーザを作成します。アクセス制御/ユーザー/ユーザーアカウントを作成をクリックするとダイアログが開くのでユーザー名・パスワードを入力して作成してください。既存の設定から変えない場合は、ダンプファイル作成時に確認したMagento2の設定ファイル`env.php`のユーザー名とパスワードを指定します。ホスト名は「すべてのホストを許可する」を選択してください。ついでにrootのパスワードも変更しておくと良いと思います。

![スクリーンショット 2016-12-16 11.26.25.png](https://qiita-image-store.s3.amazonaws.com/0/50629/82bf8fa3-5c56-8031-ae9a-ccdbeb983d03.png "スクリーンショット 2016-12-16 11.26.25.png")

### Cloud SQL接続用のソケットを作成

[Cloud SQL Proxyを使用してMySQLクライアントに接続する](https://cloud.google.com/sql/docs/mysql-connect-proxy)を参考にCloud SQL接続用のソケットを作成していきます。

APIを利用するため[Cloud SQL Administration API を有効](https://console.cloud.google.com/flows/enableapi?apiid=sqladmin)にします。認証情報の設定は不要です。

続いてMagento2インスタンスにSSHで接続します。ほぼチュートリアルからのコピペですが、下記コマンドを順に実行してCloud SQL Proxyを開始します。

```sh
# 1.プロキシをダウンロードします。
$ wget https://dl.google.com/cloudsql/cloud_sql_proxy.linux.amd64

# 2.プロキシの名前を変更して標準のファイル名を使用します。
$ mv cloud_sql_proxy.linux.amd64 cloud_sql_proxy

# 3.プロキシを実行できるようにします。
$ chmod +x cloud_sql_proxy

# 4.プロキシ ソケットを格納するディレクトリを作成します。
$ sudo mkdir /cloudsql; sudo chmod 777 /cloudsql

# 5.Cloud SDK認証と自動インスタンス検出を使用してプロキシを開始します。
$ sudo nohup ./cloud_sql_proxy -dir=/cloudsql &
```

### Magento2の設定を変更

Cloud SQLにプロキシを利用して接続するには下記のようなコマンドを実行します。

```sh
$ mysql -u root -p -S /cloudsql/インスタンス接続名
```

そのためMagento2の設定ファイル`env.php`も'db'の'host'をプロキシを利用するように変更します。

```sh
$ sudo vim ./apps/magento/htdocs/app/etc/env.php

  'db' =>
  array (
    'table_prefix' => '',
    'connection' =>
    array (
      'default' =>
      array (
        // (変更前) 'host' => '/opt/bitnami/mysql/tmp/mysql.sock',
        'host' => '/cloudsql/xxxxxx:asia-northeast1:magento2-cloud-sql',
        'dbname' => 'bitnami_magento',
        'username' => 'bn_magento',
        'password' => '62...（省略）
```

以上でMagento2インスタンス上のMySQLサーバからCloud SQLに接続先が変更されました。

## ロードバランサ（負荷分散）

### 事前準備

#### URLの変更

Magento2のURLを変更します。CertbotなどでSSL化済みの場合はこの作業は不要です。管理画面を開いて変更することもできますが、今回はこのままコンソールで作業を続けます。

```sh
# ドキュメントルートに移動
$ cd /opt/bitnami/apps/magento/htdocs

# ベースURLを変更
$ sudo php bin/magento setup:store-config:set --base-url="http://www.example.co.jp/"

# SSL接続URLを変更
$ sudo php bin/magento setup:store-config:set --base-url-secure="https://www.example.co.jp/"
```

確認のためCloud SQLにMySQLクライアントで接続して、設定テーブル`core_config_data`が書き換えられたことを確認します。

```sh
$ mysql -u bn_magento -p -D bitnami_magento -S /cloudsql/xxxxxx:asia-northeast1:magento2-cloud-sql

mysql> select * from core_config_data;
```

![スクリーンショット 2016-12-16 13.23.10.png](https://qiita-image-store.s3.amazonaws.com/0/50629/8aadebde-8623-c645-218c-809b60092f93.png "スクリーンショット 2016-12-16 13.23.10.png")

#### 証明書の取得

SSL化するためのドメインとSSLサーバ証明書、中間CA証明書、秘密鍵の3点セットを手に入れておいてください。作成したMagento2インスタンス上でCertbotを利用して証明書を取得する場合は、ApacheやVarnishなどを停止してから`--standalone`でCertbotを起動してください。

```sh
$ sudo /opt/bitnami/ctlscript.sh stop
$ sudo certbot certonly --standalone
$ sudo /opt/bitnami/ctlscript.sh start
```

#### ヘルスチェック対策

Magento2のURL設定を変更してドメイン名でHTTPアクセスできるようにすると、IPアドレスでアクセスするとドメイン名にリダイレクトされてしまうので、ヘルスチェックの結果がエラーになってしまいます。そこでヘルスチェック用のファイルを用意して該当ファイルにアクセスしたときだけは200番が返却されるようにしていきます。

* http://104.198.124.255 -> http://www.example.co.jp/ (302 Found)
* http://104.198.124.255/healthcheck.html -> http://104.198.124.255/healthcheck.html (200 OK)

Magento2インスタンスにSSHで接続します。`/opt/bitnami/apps/magento/htdocs/`がMagento2のドキュメントルートなので直下に`healthcheck.html`を作成します。

```sh
$ sudo vim /opt/bitnami/apps/magento/htdocs/healthcheck.html
```

中身はなんでも良いので適当に編集して保存してください。

```html
<!doctype html><html><body><h1>instance-1</h1></body></html>
```

参考: [Redirect HTTP to HTTPS in Wordpress running behind a Google HTTP Load Balancer](http://code.haleby.se/2015/12/11/redirect-http-to-https-in-wordpress-running-behind-a-google-http-load-balancer/)

#### HTTPS Termination時のリダイレクトループ対策

ロードバランサのバックエンドにMagento2インスタンスを配置するとSSLアクセスしたときにリダイレクトループが発生します。そのため`X-Forwarded-Proto`が`https`だったときに`HTTPS=on`にします。通常`.htaccess`を書き換えるわけですがファイルを開くと変更しても反映されない的なことが書いてあったので`httpd.conf`を変えてしまいます。

```sh
$ sudo vim /opt/bitnami/apache2/conf/httpd.conf

...
# 追記
SetEnvIf X-Forwarded-Proto https HTTPS=on

Include "/opt/bitnami/apache2/conf/ssi.conf"
...
```

Includeで他のコンフィグファイルを読み込む前に追記しています。

参考: [Magento SSL Offloading with Amazon ELB](http://www.aschroder.com/2012/07/magento-ssl-offloading-with-amazon-elb/)

### バックエンドの準備

準備が終わったので、ロードバランサのバックエンドを構築していきます。
Compute Engineのインスタンス グループを選択して、インスタンス グループを作成しましょう。

![スクリーンショット 2016-12-16 14.36.09.png](https://qiita-image-store.s3.amazonaws.com/0/50629/bcc1d169-d914-22c2-a5e9-29e059f9c601.png "スクリーンショット 2016-12-16 14.36.09.png")

ロケーションにシングルゾーンを選択して、既存のインスタンスとしてMagento2インスタンスを選びます。

続いてヘルスチェックの作成です。さくさく行きます。

![スクリーンショット 2016-12-16 14.39.09.png](https://qiita-image-store.s3.amazonaws.com/0/50629/9994b996-bfde-acef-41f5-b48932e1e2f0.png "スクリーンショット 2016-12-16 14.39.09.png")

リクエストパスに先程作成した`healthcheck.html`を指定します。

### ロードバランサ（負荷分散）

ネットワーキングから負荷分散を選択して「ロードバランサを作成」します。
HTTP（S）負荷分散の「設定を開始」ボタンをクリックするとチュートリアル形式で設定が開始されます。

![スクリーンショット 2016-12-16 14.43.42.png](https://qiita-image-store.s3.amazonaws.com/0/50629/6ee4a525-a04d-26a8-c04f-a920a5a5d724.png "スクリーンショット 2016-12-16 14.43.42.png")

バックエンドサービスを作成します。

![スクリーンショット 2016-12-16 14.46.33.png](https://qiita-image-store.s3.amazonaws.com/0/50629/06e73b27-7bcf-3a53-eeda-363eed10241f.png "スクリーンショット 2016-12-16 14.46.33.png")

インスタンスグループとヘルスチェックに先程作成したものを指定します。

![スクリーンショット 2016-12-16 14.48.12.png](https://qiita-image-store.s3.amazonaws.com/0/50629/9c76457b-d4ac-e8dd-0721-a7f84c9005c6.png "スクリーンショット 2016-12-16 14.48.12.png")

ついでに[Cloud CDN](https://cloud.google.com/cdn/docs/overview)にもチェックを入れておくと（料金は発生しますが）さらなる高速化が望めて嬉しいかもしれません。

ホストとパスのルールはバックエンドが1つしかないのでそのままで良くて、フロントエンドを設定していきます。IPアドレスの作成を選んで静的IPを申請します。トライアルの枠内だと貴重な1個をここで使います。Magento2は管理画面からHSTSの設定などができるのですが、いったんHTTPとHTTPSの両方のリクエストを許可しています。

![スクリーンショット 2016-12-16 15.14.44.png](https://qiita-image-store.s3.amazonaws.com/0/50629/181eeccb-8095-906b-2dcc-8189f6f66cb5.png "スクリーンショット 2016-12-16 15.14.44.png")

新しい証明書の作成では、事前に準備しておいたSSL証明書と中間証明書と秘密鍵を順に貼り付けてorアップロードしていきます。

![スクリーンショット 2016-12-16 15.02.48.png](https://qiita-image-store.s3.amazonaws.com/0/50629/befe5249-8fed-1c5d-ee57-c20e7de2afa1.png "スクリーンショット 2016-12-16 15.02.48.png")

全ての設定が終わって「作成」をクリックするとロードバランサが作成されます。

### DNSの設定を変更

最後にロードバランサで設定したIPアドレスをDNSのAレコードに設定してあげればOKです。

![スクリーンショット 2016-12-16 17.56.40.png](https://qiita-image-store.s3.amazonaws.com/0/50629/87a5d17a-acbf-f758-1b10-2af74ad27dad.png "スクリーンショット 2016-12-16 17.56.40.png")

HTTP2でアクセスできることが確認できました！

## あとがき

Google Cloud Platformに初めて触れたので結構大変でした。特にCloud APIのアクセス範囲を有効化しておかないとAPIが利用できなかったり、ヘルスチェック用にIPアドレスでアクセスしたときに200番を返却するURLを準備する必要があったりと、Magento2と関係ないところで苦労した感じです。

一方で設定だけでRedisを利用できるようになったことには正直驚いてしまいました。しかもRedisの設定をした時が一番高速化の恩恵を感じたので、運用するときは軒並み設定したいところですね。いたるところでキャッシュを利用しているので不具合発生時の切り分けが大変そうなのはトレードオフということで。