備忘録: Django×React×AWS×firebase[デプロイ 手順編]

# 本記事でのデプロイ環境の完成図
![portfolder drawio](https://user-images.githubusercontent.com/46844364/138878414-5ab97a30-91e5-4b66-8dea-46752c088545.png)
間違っている部分があれば是非指摘してください!!!!

# EC2を使用する際の注意点
- 2022/5/2現在 1〜4までの工程が簡略化できるようになっている

| 作成するリソース |名前タグの自動生成| 
|:-:|:-:|
| VPCなど | 任意(ここではfp) |

他の設定は任意。
本記事では上記の2点のみ変更して作成

![スクリーンショット 2022-05-02 14.38.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/596659/2849f327-5a69-c8b0-e1bf-8697f31e8ca9.png)

- 2021/9/30現在 EC2インスタンス(8GB)を４つ作成した時点で無料枠(30GB)を超えるので課金対象になる
- 2つのインスタンスが1ヶ月フル稼働をした時点で課金対象になる
- また稼働していないインスタンスに対してElasticIPが有効になっている場合も課金対象になる
- 2021年10月26日時点ではRDSの無料枠にPostgresQLがなかったのでMySQLを使用しています。(DjangoはPostgresQLの使用を推奨しています。)

firebaseでプロジェクトを作成し、Firebase Hosting の設定通りに進める
# フロントエンド側
## buildする
```terminal
$ npm run build
```

## firebase CLIのインストール

```terminal
$ npm install -g firebase-tools
$ firebase login
$ firebase projects:list
```

上記のコマンドでログインとプロジェクト一覧を確認できればOK
[firebase公式ドキュメント](https://firebase.google.com/docs/cli?hl=ja#install_the_firebase_cli)

## firebaseプロジェクトの初期化
```terminal
$ firebase init hosting
以降は下記の内容で選択
Use an existing project

firebaseで作成したプロジェクトを選択

? What do you want to use as your public directory? (public)build

? Configure as a single-page app (rewrite all urls to /index.html)? (y/N) y

? Set up automatic builds and deploys with GitHub? Y

? File build/index.html already exists. Overwrite? (y/N) N

? For which GitHub repository would you like to set up a GitHub workflow? (fo
rmat: user/repository) enuii3/portfolder_api

? Set up the workflow to run a build script before every deploy? (y/N) y

? What script should be run before every deploy? npm ci && npm run build

? Set up automatic deployment to your site's live channel when a PR is merged
? (Y/n) Y
? What is the name of the GitHub branch associated with your site's live chan
nel? master
```

## 環境変数を再設定する

後日作成内容
[create-react-appで独自の環境変数を読み込む](https://qiita.com/zgmf_mbfp03/items/008436c5749d65f96e55)

## deployする
```terminal
$ firebase deploy
```




# バックエンド側

## デプロイ用ライブラリの導入
```terminal
$ pip install django-environ
$ pip instlal dj-database-url
```
- 環境変数自体は.env（jira_api直下に新規作成する必要がある）に記述し、django-environは環境変数を簡単に扱える様にする
- dj-dabase-urlはデーターベースを簡単に扱える様にする

```settings.py
# 下記の2行を追加
import os
import environ

# BASE_DIRの定義箇所以下に以下の2行を追加
env = environ.Env()
env.read_env(os.path.join(BASE_DIR, '.env'))
# 以下省略

# 以下の3項目をそれぞれ編集
SECRET_KEY = env('SECRET_KEY')

DEBUG = env('DEBUG')

DATABASES = {
    'default': env.db(),
}
CORS_ORIGIN_WHITELIST = env.list('CORS_ORIGIN_WHITELIST')

STATIC_URL = '/static/'
# 下記の1行を追加
STATIC_ROOT = os.path.join(BASE_DIR, 'static')
```
- .envファイルの読み込ませるためにsettings.pyに編集
- djangoには複数のstaticファイル(adminダッシュボードなど)があり、　djangoアプリのデプロイ時にはcollect staticコマンドを実行して、散らばったstaticファイルを一つのstaticフォルダ(新規作成する必要がある)に統合する必要があるので<font color="Red">プロジェクトディレクトリ直下に空のstaticディレクトリを作成する</font>



```.env
SECRET_KEY=settings.pyで設定されていたシングルクォートの中身
DEBUG=False
DATABASE_URL=sqlite:///db.sqlite3
CORS_ORIGIN_WHITELIST=http://localhost:3000
```
```terminal
$ pip freeze > requirements-dev.txt
```
- 開発時にimportしたライブラリを`pip freeze > requirements-dev.txt`コマンドで読み取って依存関係を吐き出す

```requirements.txt
-r requirements-dev.txt
gunicorn
psycopg2
```
- 本番環境用にrequirements.txtを作成し、`requirements-dev.txt`を読み込むのと、本番環境でgunicornとPostgreSQLを使用するので上記の2行を追加

# 以下AWS操作
## 1. VPCの作成

| 名前タグ | CIDRブロック |
|:-:|:-:|
| portfolder-vpc | 10.0.0.0/16 |

## 2. サブネットの作成

| VPC ID | サブネット名 | アベイラビリティーゾーン | CIDRブロック |
|:-:|:-:|:-:|:-:|
| portfolder-vpc | pf-public-sn1 | ap-notheast-1a | 10.0.1.0/24 |
| portfolder-vpc | pf-public-sn2 | ap-notheast-1d | 10.0.2.0/24 |
| portfolder-vpc | pf-private-sn1 | ap-notheast-1a | 10.0.3.0/24 |
| portfolder-vpc | pf-private-sn1 | ap-notheast-1d | 10.0.4.0/24 |

## 3. インターネットゲートウェイを作成し、アタッチする

| 名前タグ |
|:-:|
| pf-igw |

## 4 ルートテーブルの作成

### 4.1 ルートテーブルに下記のルートの追加

| 送信先 | ターゲット |
|:-:|:-:|
| 0.0.0.0/0 | pf-igw |

### 4.2 **サブネットの関連付け**タブから２つのパブリックサブネットの関連付け

## 5. EC2インスタンスの作成
異なるアベイラビリティーゾーンのEC2インスタンスを２つ作成

|名前|アプリケーションおよびOSイメージ|キーペア|VPC|サブネット|
|:-:|:-:|:-:|:-:|:-:|
|任意(本記事ではfp-ec2-1a)|Ubuntu|任意(推奨)|fp-vpc|アベイラビリティーゾーン1a（もしくは1c）のpublicサブネット|

パブリックIPの自動割り当て|セキュリティーグループ|セキュリティーグループ名|インバウンドセキュリティールール|
:-:|:-:|:-:|:-:|
有効化|作成|任意(本記事ではfp-public-sg)|ルールは以下のスクショを参照|

![スクリーンショット 2023-01-29 13.14.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/596659/87937fc3-c5e1-24c2-17cd-1f51be7270b6.png)

## 6. ElasticIPアドレスを紐付け
ネットワークボーダーグループ|
:-:|
任意(本記事ではデフォルトのap-northeast-1)|

作成完了後は一覧画面に戻り、アクション内のElastic　IPアドレスの関連付けを選択し、先ほど作成したEC2インスタンスに紐付け(紐付け完了後は)

デフォルトでは起動する度にインスタンスのIPアドレスは変更されるが、こうする事によって、IPアドレスを固定化できる

## 6. RDSの作成

### 6.1 データベース(MySQL)の作成
データベースの作成時の選択項目

|DB作成方法|エンジンオプション|テンプレート|マスターユーザー|マスターパスワード| 
|:-:|:-:|:-:|:-:|:-:|
|標準作成|MySQL|無料利用枠|admin|任意| 

コンピューティングリソース|EC2インスタンス|ネットワークタイプ|VPC|サブネットグループ|
:-:|:-:|:-:|:-:|:-:|
EC2 コンピューティングリソースに接続|作成済のもの|IPv4|自動選択|自動選択|

VPC SG|新しいSG名|AZ|
:-:|:-:|:-:|
一旦defaultを選択し、後に自動生成されたec2-rds-1に変更|pf-db-sg|自動選択|

作成が完了すると、データベースとサブネットグループ(1a, 1c, 1dのそれぞれ異なるアベイラビリティーゾーン毎のサブネットと付随するルートテーブルなど)が作成される

~~6.2 **サブネットグループ**の作成~~
(RDS作成時に自動生成されるようになってました。 一応下記は残しておきます。)

| 名前 | 説明 |VPC | アベイラビリティーゾーン | マスターユーザー | サブネット | 
|:-:|:-:|:-:|:-:|:-:|:-:|
| fp-db-sngroup（任意） | 何かしらの入力が必須 | friends-phrase-vpc | ap-notheast-1aと1c | admin | 1aと1c一つずつ |


## 7. Route53にてホストゾーンの作成
## 8. お名前.comにてネームサーバーの設定

設定ができたかの確認

```
#Linux or Mac
$ dig NS ドメイン名

# windows
$ nslookup
> set type=ns
> ドメイン名
```

## 9. ACMにてSSL証明書を発行

| ドメイン名 |
|:-:|
| 任意のドメイン名 |

### 9.1 証明書を選択し、Route53でレコードを作成ボタンを押下し、レコード作成
### 9.2 Route53にて作成したホストゾーンにCNAMEレコードの登録


## 10. ターゲットグループを作成
インスタンスを選択し、Include as pending belowボタンを押下

| ターゲットタイプ | ターゲットグループ名 | VPC |
|:-:|:-:|:-:|
| Instance | pf-target-group | porfolder-vpc |

## 11. Application LoadBalancerの作成

| ロードバランサー名 | VPC | マッピング | セキュリティーグループ　| HTTPリスナーのデフォルトアクション
|:-:|:-:|:-:|:-:|:-:|
| pf-loadbalancer | portfolder-vpc | pf-public-sn1, pf-public-sn2  | pf-db-sg | pf-targetgroup


## 11.1 既存のHTTPリスナーのルールを編集
![スクリーンショット 2021-11-11 21.28.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/596659/3883fb7d-a613-7a59-bb91-9e32323a3561.png)

## 11.2 HTTPSリスナーの追加

| プロトコル | ポート | デフォルトアクション | デフォルトSSL |
|:-:|:-:|:-:|:-:|
| HTTPS | 443 | Foward to(pf-target-group) | 作成したACM |


## 12. route53にて、トラフィック先をALBにしたAレコードの作成

![スクリーンショット 2021-11-11 22.24.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/596659/347334d1-c6a7-7b06-c195-7df8af638213.png)

## 13. 本番環境を構築

### 14.1 sshでEC2にアクセス
コマンドは作成したEC2インスタンスの**接続**ボタンから確認できる

#### ちなみに

```..ssh/config
Host *
  ServerAliveInterval 60 
  TCPKeepAlive yes
  IPQoS=throughput  
```
上記の設定はubuntuに接続時に強制ログアウトになるのを防ぐ


### 13.2 各ライブラリ(データベースクライアント、gunicorn, nginxなど)をインストール

```terminal
//EC2インスタンスに各ライブラリを導入
ubuntu@ip-00-0-0-000:~$ sudo apt-get update
ubuntu@ip-00-0-0-000:~$ sudo apt-get install python3-pip python3-dev mysql-server libmysqlclient-dev nginx
```

### 13.3 仮想環境の構築

```terminal 
ubuntu@ip-00-0-0-000:~$ sudo -H pip3 install --upgrade pip
ubuntu@ip-00-0-0-000:~$ sudo -H pip3 install virtualenv
ubuntu@ip-00-0-0-000:~$ virtualenv 仮想環境名
ubuntu@ip-00-0-0-000:~$ source 仮想環境名/bin/activate
(仮想環境名) ubuntu@ip-00-0-0-000:~$ git clone レポジトリ
(仮想環境名) ubuntu@ip-00-0-0-000:~$ cd リポジトリ名
(仮想環境名) ubuntu@ip-00-0-0-000:~/リポジトリ名$ pip install -r requirements.txt
```

#### ちなみに私用
```requirements.txt
-r requirements-dev.txt
gunicorn
mysqlclient
```

### 13.4 本番環境用の環境変数を作成

```EC2内の.env
SECRET_KEY=ローカル環境で初めに設定されていたシークレットキー
DEBUG=False
DATABASE_URL=mysql://ユーザー名:パスワード@RDSのエンドポイント:/データベース名
ALLOWED_HOSTS=EC2インスタンスの独自ドメイン
CORS_ORIGIN_WHITELIST=firebase側のURLを設定する
AWS_ACCESS_KEY_ID=各自設定が必要
AWS_SECRET_ACCESS_KEY=各自設定が必要
AWS_STORAGE_BUCKET_NAME=各自設定が必要
```

```アプリ名/settings.py
ALLOWED_HOSTS = env.list('ALLOWED_HOSTS')
```


### 13.5 RDSにアクセスし、データベースを作成

```terminal
//データベースの作成
$ mysql -h RDSのエンドポイント -u admin -p
mysql> CREATE DATABASE データベース名;
```


```terminal
(仮想環境名) ubuntu@ip-00-0-0-000:~/リポジトリ名$ python3 manage.py migrate
(仮想環境名) ubuntu@ip-00-0-0-000:~/リポジトリ名$ python3 manage.py collectstatic
(仮想環境名) ubuntu@ip-00-0-0-000:~/リポジトリ名$ python3 manage.py createsuperuser
```
- 本番環境用にpostgresqlのデーターベースを作成したので、models.pyのデータベース構造を再度展開するためにmigrateする必要がある
- プロジェクト内に散らばったstaticファイルをstaticフォルダに統合する

## 14.6 gunicornの設定
```terminal
$ sudo vi /etc/systemd/system/gunicorn.service
```

```gunicorn.service
[Unit]
Description=gunicorn daemon
After=network.target

[Service]
User=ubuntu
Group=www-data
WorkingDirectory=/home/ubuntu/プロジェクト名
ExecStart=/home/ubuntu/仮想環境名/bin/gunicorn --access-logfile - --workers 3 --bind unix:/home/ubuntu/プロジェクト名/アプリ名.sock アプリ名.wsgi:application

[Install]
WantedBy=multi-user.target
```
- WorkingDirectoryは`[/ から Django Project Root までパス]/[Django Project Root(git cloneならリポジトリ名)]`
- ExecStartは`[/ から gunicorn コマンドまでのパス]/gunicorn --access-logfile - --workers 3 --bind unix:　(wsgiが入ってるディレクトリ).wsgi:application`
- gunicornの設定ファイルは[公式ドキュメント](https://ai-can-fly.hateblo.jp/entry/gunicorn-install-and-settings)を参考にしております。

#### gunicornの起動
```terminal
$ sudo systemctl restart gunicorn
$ sudo systemctl enable gunicorn
```


### 13.7 nginxの設定
```terminal
$ sudo vi /etc/nginx/sites-available/プロジェクト名
```

```/etc/nginx/sites-available/プロジェクト名
server {
        listen 80;
        server_name EC2のパブリックIPアドレス;

        location = /favicon.ico {access_log off; log_not_found off;}
        location /static/ {
                root /home/ubuntu/プロジェクト名;
        }
        location / {
                include proxy_params;
                proxy_pass http://unix:/home/ubuntu/プロジェクト名/アプリ名.sock;
        }
}
```
-  プロジェクト名(git clone)はリポジトリ名

#### nginxの起動

```terminal
//シンボリックリンクを作成
$ sudo ln -s /etc/nginx/sites-available/プロジェクト名 /etc/nginx/sites-enabled/

//nginxの起動
$ sudo systemctl restart nginx

//80ポートを解放
$ sudo ufw allow 'Nginx Full'
```
- 上記で作成した設定ファイルに対してシンボリックリンクを作成する（こうすることによって実態のファイルを削除しなくてもリンクを切ることで、新しくリンクさせることができる）
- ec2コンソールに戻り、先ほど作成したセキュリティーグループのインバウンドルールに`httpタイプ`で`0.0.0.0/0ソース`を追加した後にgunicornを起動

#### ちなみにnginx status確認コマンド
```terminal
$ sudo systemctl status nginx.service
```

## 14. 本番環境用の設定
```(フロントエンド).env.production
REACT_APP_API_URL=EC２のパブリックIPアドレスもしくは独自ドメイン
```

```(バックエンド)settings.py
CORS_ORIGIN_WHITELIST = [
    "firebase側のURLを設定する"
]
```

Route53とACMについての[参考記事](https://qiita.com/shu1124/items/3e4442fe95a91649de01)

## おまけ
### ssh: connect to host ec2-00-00-000-00.ap-northeast-1.compute.amazonaws.com port 22: Connection refusedのチェックポイント
- EC2インスタンスに付随しているセキュリティーグループのインバウンドルールにsshが追加されているか
- EC2インスタンスに付随しているサブネットグループは適正かどうか(publicのサブネットグループになっているかなど)
- publicルートテーブルの関連付けに上記のサブネットグループが追加されているかどうか

### 削除したくなった場合のRDSの削除手順
1. RDSインスタンスを削除
2. RDS用のサブネットグループの削除
3. RDS用セキュリティーグループのインバウンドとアウトバウンドを空にしてから削除(空にしないと削除出来ない)もしくはセキュリティーグループ関連づけを削除


### ERR_CONNECTION_TIMED_OUTのエラー
### ALBのアクセスログに関しては[こちら](https://qiita.com/Takao_/items/da74897ca51d60f263fc)
ちなみに私用
```
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "PublicReadGetObject",
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::バケット名/*"
        },
        {
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::東京リージョンの場合h582318560864:root"
            },
            "Action": "s3:PutObject",
            "Resource": "arn:aws:s3:::バケット名/*"
        },
        {
            "Effect": "Allow",
            "Principal": {
                "Service": "delivery.logs.amazonaws.com"
            },
            "Action": "s3:PutObject",
            "Resource": "arn:aws:s3:::バケット名/*",
            "Condition": {
                "StringEquals": {
                    "s3:x-amz-acl": "bucket-owner-full-control"
                }
            }
        },
        {
            "Effect": "Allow",
            "Principal": {
                "Service": "delivery.logs.amazonaws.com"
            },
            "Action": "s3:GetBucketAcl",
            "Resource": "arn:aws:s3:::バケット名"
        }
    ]
}
```
###

# 参考文献
![スクリーンショット 2021-10-03 21 11 10](https://user-images.githubusercontent.com/46844364/135753151-6aa975a0-397c-469f-98b7-a7cb4f9fa6e5.png)

- [こちら](https://view-s.co.jp/product/webapp/concept/)の記事がとても見やすかったので図解を引用させていただきました。
- [【未経験開発 Rails/React/AWS/Docker/CircleCI】独学＋メンターでここまで出来た！Web知識ゼロからモダンな技術アプリ開発までに利用した5つのサービス](https://qiita.com/ddpmntcpbr/items/739dbb992b5ffac3fc2f)

- [【React x Django】日本の電力を見える化するWebアプリケーションを作って公開してみた。](https://qiita.com/TsJazz27Sumin/items/0094a4f0924c9316d32d)

- [reactで環境変数を切り替える](https://sumii.io/posts/create-react-app-env-setup/)