Google App Engine フレキシブル環境でのDjangoの実行チュートリアルでハマった

## 概要

チュートリアルをやってみたものの、読むべきドキュメントが多くて、行ったり来たりで大変だったので、手順をまとめてみました。
一部どハマりしてスキップした箇所がありますが、デプロイまでできる手順となります。


チュートリアルは下記になります。

App Engine フレキシブル環境での Django の実行
https://cloud.google.com/python/django/flexible-environment?hl=ja


## 前提

上記記事の「始める前に」を読んで、以下の準備が完了している前提です。

- GCPプロジェクトがある
- プロジェクトの課金が有効
- Google Cloud SDKがインストール済み。```gcloud```コマンドが利用できる
- プロジェクトでApp EngineとCloud SQLのAPIが有効になっている

また、Python3系がインストール済みであることも前提です。
環境がない方は以下をご参考ください。

Macでanyenvをつかってpython環境構築(bash、fish対応)
https://qiita.com/kai_kou/items/f54931991a781b96bb9c


## 手順

### サンプルアプリのダウンロード

```sh
> mkdir 任意のディレクトリ
> cd 任意のディレクトリ
> git clone https://github.com/GoogleCloudPlatform/python-docs-samples.git
> cd python_sample_doc/appengine/flexible/django_cloudsql/
```

### Cloud SQLにインスタンスを作成する

下記を参考にDjangoアプリが利用するデータベースを用意します。

インスタンスを作成する
https://cloud.google.com/sql/docs/postgres/create-instance

Cloud SDK を使用してインスタンスを管理する
https://cloud.google.com/sql/docs/cloud-sdk?hl=ja


先にGCPのプロジェクトを選択しておきます。

```sh
> gcloud config list

[compute]
region = asia-northeast1
zone = asia-northeast1-a
[core]
account = xxx@xxxxx.xxx
disable_usage_reporting = True
project = プロジェクトID

> gcloud config set project プロジェクトID
```

とりあえず、お試しなので、最小スペックでインスタンスを作成します。
立ち上がるのに少々お時間がかかります。

```sh
> gcloud sql instances create インスタンス名 \
    --database-version=POSTGRES_9_6 \
    --cpu=1 \
    --region=asia-northeast1 \
    --memory=3840MiB
```

インスタンスが作成されたか確認します。

```sh
> gcloud sql instances list

NAME         DATABASE_VERSION  LOCATION           TIER              ADDRESS         STATUS
インスタンス名  POSTGRES_9_6      asia-northeast1-b  db-custom-1-3840  xx.xxx.xxx.xxx  RUNNABLE
```
はい。

インスタンス情報を取得します。

```sh
> gcloud sql instances describe インスタンス名

(略)
connectionName: プロジェクトID:asia-northeast1:インスタンス名
databaseVersion: POSTGRES_9_6
(略)
serviceAccountEmailAddress: xxxx@xxx-xxxx-xx-x.iam.gserviceaccount.com
(略)
```

### PostgreSQLユーザーのパスワード設定

下記を参考にして```postgres``` ユーザーにパスワードを設定します。

PostgreSQL ユーザーを作成、管理する
https://cloud.google.com/sql/docs/postgres/create-manage-users?hl=ja

```sh
> gcloud sql users set-password postgres no-host \
   --instance インスタンス名 \
   --password パスワード

WARNING: Positional argument deprecated_host is deprecated. Use --host instead.
Updating Cloud SQL user...done.
```

### データベースの作成

下記を参考にしてデータベースを作成します。

データベースを作成する
https://cloud.google.com/sql/docs/postgres/create-manage-databases?hl=ja#create

```sh
> gcloud sql databases create データベース名 --instance=インスタンス名

Creating Cloud SQL database...done.
Created database [データベース名].
instance: インスタンス名
name: データベース名
project: プロジェクトID
```

データベースが作成されたか確認します。

```sh
> gcloud sql databases list --instance=インスタンス名

NAME         CHARSET  COLLATION
postgres     UTF8     en_US.UTF8
django-test  UTF8     en_US.UTF8
```
はい。


### Djangoアプリの設定

Djangoアプリがデータベースへアクセスできるように設定ファイルを編集します。

```sh
vi mysite/settings.py
```
```HOST``` には先程```gcloud sql instances describe インスタンス名``` で取得した、```connectionName``` を指定します。
gcloud sql instances describe インスタンス名

```python:mysite/settings.py
-       'NAME': 'polls',
-       'USER': '<your-database-user>',
-       'PASSWORD': '<your-database-password>',
+       'NAME': 'データベース名',
+       'USER': 'postgres',
+       'PASSWORD': 'パスワード',

-DATABASES['default']['HOST'] = '/cloudsql/<your-cloudsql-connection-string>'
+DATABASES['default']['HOST'] = '/cloudsql/プロジェクトID:asia-northeast1:インスタンス名'
```

### Djangoアプリの準備

Pythonの仮想環境を作成して、必要なパッケージをインストールします。
が、その前にrequirements.txtを編集しておきます。
Postgresqlを利用するチュートリアルなのに、不要でかつ、インストールできないライブラリが指定されていました。
あと、マイグレーションするとライブラリが足りないと怒られるので、先に追加しておきます。
(2018/08/10現在)


```sh
> vi requirements.txt
```

```txt:requirements.txt
Django==2.0.3
-mysqlclient==1.3.12
wheel==0.31.0
gunicorn==19.7.1
psycopg2==2.7.4
+psycopg2-binary==2.7.5
```

``` sh
> python -m venv venv
> . venv/bin/activate
# fishはこちら
> . venv/bin/activate.fish

> pip install -r requirements.txt
(略)
```


### Djangoアプリのマイグレーション

アプリの設定とライブラリがインストールできたらマイグレーションを行います。
ローカル環境でCloud SQLへアクセスできるプロキシアプリが提供されているので、**新しいターミナルを開いて作業**します。

```sh:新しいターミナル
> cd 任意のディレクトリ/python_sample_doc/appengine/flexible/django_cloudsql/
> curl -o cloud_sql_proxy https://dl.google.com/cloudsql/cloud_sql_proxy.darwin.amd64
> chmod +x cloud_sql_proxy
> ./cloud_sql_proxy -instances="プロジェクトID:asia-northeast1:インスタンス名"=tcp:5432

2018/08/10 15:55:10 Listening on 127.0.0.1:5432 for プロジェクトID:asia-northeast1:インスタンス名
2018/08/10 15:55:10 Ready for new connections
```

もとのターミナルに戻ってマイグレーションを実行します。

```sh
> python manage.py makemigrations


Error during createEphemeral for プロジェクトID:asia-northeast1:インスタンス名: googleapi: Error 403: The client is not authorized to make this request., notAuthorized
```
はい。
権限がないと怒られました。

調べたところ、Cloud SQLのインスタンスに関連するサービスアカウントに権限付与しないといけなさそう？
先程実行した```gcloud sql instances describe インスタンス名``` で取得できた```serviceAccountEmailAddress``` をGCP管理コンソールのIAMに編集者として追加してあげたら良いのかな？

Google Compute Engine(GCE)からCloud SQL接続でハマった
https://qiita.com/NagaokaKenichi/items/b54952e977c13f098bf5


申し訳ありませんが、諸般の都合で、IAM権限が低いため、ここではローカルでの実行をスキップして、Google App Engineへのデプロイへ進みます。
m(_ _)m

ちなみにCloud SQLへアクセスできないので、ローカルでアプリは起動できませんでした。

```sh
> python manage.py runserver

だめだった。
```


### App Engineへのデプロイ

さて、ローカルでの実行もできたので(大嘘)
いよいよ、App Engineへのデプロイとなります。

まずは、静的コンテンツをCloud Storageへアップします。
バケットを一般読み取り可能にするので、専用のバケットにしたほうが安全です。

```sh
# バケットの作成
> gsutil mb gs://静的コンテンツ用のバケット名

Creating gs://静的コンテンツ用のバケット名/...

# バケットを一貫読み取り可能にする
> gsutil defacl set public-read gs://静的コンテンツ用のバケット名

Setting default object ACL on gs://静的コンテンツ用のバケット名/...

# 静的コンテンツを1フォルダにまとめる
> python manage.py collectstatic

# バケットにコピーする
> gsutil rsync -R static/ gs://静的コンテンツ用のバケット名/static
```

Djangoアプリ設定にアップロードしたフォルダのURLを設定します。

```sh
> vi mysite/settings.py
```

```python:mysite/settings.py
-STATIC_URL = '/static/'
+STATIC_URL = 'https://storage.googleapis.com/静的コンテンツ用のバケット名/static/'
```

App Engineの設定を変更します。

```sh
> vi app.yaml
```

**既存のプロジェクトを利用する場合、```service```を指定しないと、```default``` にデプロイされてしまうので、ご注意ください。**

```yaml:app.yaml
# 新しいプロジェクトなら追加は不要
+service: サービス名

-   cloud_sql_instances: <your-cloudsql-connection-string>
+   cloud_sql_instances: プロジェクトID:asia-northeast1:インスタンス名
```

準備ができたので、デプロイします。

```sh
> gcloud app deploy

Services to deploy:

descriptor:      [/任意のディレクトリ/gcp/python-docs-samples/appengine/flexible/django_cloudsql/app.yaml]
source:          [/任意のディレクトリ/gcp/python-docs-samples/appengine/flexible/django_cloudsql]
target project:  [プロジェクトID]
target service:  [サービス名]
target version:  [バーション]
target url:      [https://サービス名-dot-プロジェクトID.appspot.com]

Do you want to continue (Y/n)?  Y
(略)

DONE
(略)  
```

デプロイが完了したら上記の```target url``` にアクセスしてみましょう。

<img width="666" alt="hello_-django.png" src="https://qiita-image-store.s3.amazonaws.com/0/48549/578312af-db7b-b393-95ac-d1ecc903b2b2.png">


やったぜ。


これでチュートリアルは終了ですが、もうちょっとだけ続くんじゃ。

---

### デプロイ先でマイグレーションする

先程、ローカル環境からCloud SQLにアクセスできないままで、マイグレーションができていないので、ここではデプロイ先のインスタンスに入ってマイグレーションしてみます。

下記を参考にして進めます。

インスタンスをデバッグする
https://cloud.google.com/appengine/docs/flexible/python/debugging-an-instance

gcloud app instances enable-debug
https://cloud.google.com/sdk/gcloud/reference/app/instances/enable-debug


インスタンスのデバッグができるように有効化します。
インスタンスが複数ある場合、どのインスタンスで有効化するか聞かれます。
スケールしているだけなので、どれを選んでも問題ありません。

```sh
> gcloud app --project プロジェクトID \
    instances enable-debug \
    --service=サービス名 \
    --version バーション

Which instance?
 [1] サービス名/バージョン/aef-xxxxx-バージョン-bqrz
 [2] サービス名/バージョン/aef-xxxxx-バージョン-zlqb

Please enter your numeric choice:　1
Waiting for operation 
(略)
...done.
```

デバッグが有効化されたらインスタンスにログインします。
初回はインスタンスへの接続に利用するrsa keyが生成されます。パスフレーズを忘れないようにしましょう。

```sh
> gcloud beta app instances \
    --project プロジェクトID \
    ssh aef-xxxxx-バーション-zlqb \
    --service サービス名 \
    --version バーション

Generating public/private rsa key pair.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
(略)
Are you sure you want to continue connecting (yes/no)? 
(略)
Enter passphrase for key '/Users/xxx/.ssh/google_compute_engine':
```

インスタンスにログインできたらさらにDockerコンテナに入ります。
コンテナプロセスを確認するとどうやら```gaeapp``` がDjangoアプリのコンテナのようです。

```sh:インスタンス内
> sudo docker ps

CONTAINER ID        IMAGE                                                         COMMAND                  CREATED             STATUS       PORTS                                        NAMES
212d1c1528be        us.gcr.io/プロジェクトID/appengine/サービス名.バーション@sha256:xxxx   "/bin/sh -c 'exec ..."   18 minutes ago      Up 18 minutes       172.17.0.1:8080->8080/tcp                    gaeapp
269598f6f28c        gcr.io/google-appengine/cloud-sql-proxy                                                         "/cloud_sql_proxy ..."   19 minutes ago      Up 18 minutes                                                    cloudsql
9629a8176717        gcr.io/google-appengine/api-proxy                                                         "/proxy"                 19 minutes ago      Up 19 minutes                                                    api
dae95a724b34        gcr.io/google-appengine/nginx-proxy                                                         "/var/lib/nginx/bi..."   19 minutes ago      Up 19 minutes       8080/tcp, 8090/tcp, 0.0.0.0:8443->8443/tcp   nginx_proxy
b3a1a85a156b        gcr.io/google-appengine/iap-watcher                                                         "./iap_watcher.py ..."   19 minutes ago      Up 19 minutes                                                    iap_watcher
3d45ca9828db        gcr.io/google-appengine/fluentd-logger                                                         "/opt/google-fluen..."   20 minutes ago      Up 20 minutes                                                    fluentd_logger
```

Dockerコンテナに入ります。

```sh:インスタンス内
> docker exec -it gaeapp /bin/bash

root@25f63590347e:/home/vmagent/app# ll
total 7320
drwxr-xr-x 1 root root    4096 Aug 10 07:37 ./
drwxr-xr-x 1 root root    4096 Jul  9 20:36 ../
-rw-r--r-- 1 root root     988 Aug  9 07:51 README.md
-rw-r--r-- 1 root root     271 Aug 10 07:36 app.yaml
-rwxr-xr-x 1 root root 7448368 Aug 10 06:39 cloud_sql_proxy*
-rwxr-xr-x 1 root root     825 Aug  9 07:51 manage.py*
drwxr-xr-x 1 root root    4096 Aug 10 07:41 mysite/
drwxr-xr-x 1 root root    4096 Aug 10 07:41 polls/
-rw-r--r-- 1 root root      83 Aug 10 07:11 requirements.txt
-rw-r--r-- 1 root root     136 Jan  1  1970 source-context.json
drwxr-xr-x 3 root root    4096 Aug 10 07:37 static
```
あってました^^

ではコンテナ内でマイグレーションします。

```sh:コンテナ内
> python manage.py makemigrations

No changes detected

> python manage.py makemigrations polls

Migrations for 'polls':
  polls/migrations/0001_initial.py
    - Create model Choice
    - Create model Question
    - Add field question to choice

> python manage.py migrate

Operations to perform:
  Apply all migrations: admin, auth, contenttypes, polls, sessions
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying auth.0009_alter_user_last_name_max_length... OK
  Applying polls.0001_initial... OK
  Applying sessions.0001_initial... OK
```
やったぜ。

無事に終わったら、コンテナとインスタンスから抜け出しましょう。

```sh
> exit
> exit
```

インスタンスでの作業が終わったら、デバッグモードを無効化します。

**多分これを忘れると、インスタンスが起動しっぱなしで課金される？(未調査)**


```sh
> gcloud app --project プロジェクトID \
    instances disable-debug \
    --service=サービス名 \
    --version バーション
```

どハマりもありましたが、なんとかApp Engine上でDjangoが実行できることが確認できました。
最後に、チュートリアルで作成した、Cloud SQLのインスタンスやApp Engineのサービスは不要であれば削除しておきましょう。

**間違って削除してしまわないように、
ほんとうに、
ほんとうに、
ご注意ください。**

```sh
# App Engineのサービス削除
> gcloud app services delete サービス名

# Cloud SQLインスタンスのデータベース削除
> gcloud sql databases delete データベース名	\
    --instance=インスタンス名

# Cloud SQLインスタンス削除
> gcloud sql instances delete インスタンス名

# Cloud Storageのバケット削除
> gsutil rm -r gs://静的コンテンツ用のバケット名
```

それでは、良きApp Engine上でのDjangoライフを^^


## 参考

App Engine フレキシブル環境での Django の実行
https://cloud.google.com/python/django/flexible-environment?hl=ja

インスタンスを作成する
https://cloud.google.com/sql/docs/postgres/create-instance

Cloud SDK を使用してインスタンスを管理する
https://cloud.google.com/sql/docs/cloud-sdk?hl=ja

PostgreSQL ユーザーを作成、管理する
https://cloud.google.com/sql/docs/postgres/create-manage-users?hl=ja

データベースを作成する
https://cloud.google.com/sql/docs/postgres/create-manage-databases?hl=ja#create

Google Compute Engine(GCE)からCloud SQL接続でハマった
https://qiita.com/NagaokaKenichi/items/b54952e977c13f098bf5

インスタンスをデバッグする
https://cloud.google.com/appengine/docs/flexible/python/debugging-an-instance

gcloud app instances enable-debug
https://cloud.google.com/sdk/gcloud/reference/app/instances/enable-debug