とりあえずEC2上でCGI連携のnginx+React+Laravelアプリを動かしてみよう

### 1. 概要
こんな記事のタイトルにしてみましたが、要はCGIでアプリを動かしてみましょうという記事です。
ですので、「CGIなんて知っているよ〜（笑）」というエンジニアの皆さんは、そっと「閉じる」ボタンを押してください。多分、初歩的なことしか書いていないので、、、

で、この記事を読んで、「勉強になった」と思ってもらう想定する読者層は、
* 開発はしたことあるけど、リリースまではしたことないエンジニア

かなと思っています。

### 2. 記事作成のきっかけ
開発を中心に対応していた私は「CGI」のことを知らなかったので、
「え、、、フロント側って```npm serve```とかで動いてないの！？」
「Laravelって```php artisan serve```で動いてないの！？」みたいな感じでした。

もしかしたら同じ思考のエンジニアの方々がいれば、何かの助けになるかと考え、
このテーマで記事を書いてみようと思いました。

ただ、この記事のタイトル通り「とりあえず動かしてみよう」のレベルで、実際に手を動かして、とりあえず大枠を把握しようというお話です。

ですので、機能や仕組みの詳細は書いていません、、、（強々さん、お願いします:bow_tone1:）。

色々と詳しく知りたいエンジニアの皆さんも、ここでそっと「閉じる」ボタンを押してください。

### 3. CGIとは？
CGI （Common Gateway Interface）とは、クライアントからのリクエストに応じて、Webサーバが外部のプログラムを起動し、処理結果をクライアントに返す仕組みのことです（...多分）。
まぁ、これは下記リンク先を参考にしてください:bow_tone1:
> 参考：[「分かりそう」で「分からない」でも「分かった」気になれるIT用語辞典|CGIとは](https://wa3.i-3-i.info/word112.html)

そして、その処理速度を高速化および負荷軽減したした仕組みを```FastCGI```といいます。
これもまた下記リンク先を参考にしてください:bow_tone1:
> 参考：[「分かりそう」で「分からない」でも「分かった」気になれるIT用語辞典|FastCGIとは](https://wa3.i-3-i.info/word12806.html)

今回の構成では、FastCGIのPHP版として```php-fpm```というサーバを使います。

### 4. 構成
この記事で実際に手を動かして作る構成は、AWSのEC2上でnginxサーバを起動させ、Webサーバ側との通信を想定しています。
一応、EC2の起動からアプリ作成、リリースまでをざっくり記載していますので、セキュリティや非機能要件などは考慮していないです。とりあえず動かすことが優先ですので、実際の開発時では気をつけてください。

下記が今回の技術スタックです。
* React v18.2
* Laravel v10.33
* nginx v1.24
* php-fpm v8.2

次に構成ですが、下図のような感じを想定しています。<br>
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321920/7c32ab7f-7700-8e40-e4c2-89a75c22a6a3.png)

そして、インフラはAWSで下図のような簡単な構成を想定しています。<br>
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321920/35c42038-f791-c7bb-a102-c88b5e9f75e7.png)

OSは```Amazon Linux 2023```です。

業務でよく使っていた技術スタックなので、この構成にしましたが、
他の技術スタックでも同じことはできるので、
気になる方は色々と試してみてください。

### 5. 実践
まず前提ですが、下記の通りとなります。
* AWSのアカウントは取得済み
* Reactの開発環境は構築済み
* Laravelの開発環境は構築済み
* 開発端末はApple M2 Pro
* ターミナル上の操作（Vim含む）は問題ないこと

#### 5-1. AWSにてインフラ構築
##### 5-1-1. キーペアの作成
1. 「EC2」→「ネットワーク&セキュリティ」→「キーペア」のフローで「キーペアを作成」画面に移動する
1. キーペアを作成する

##### 5-1-2. VPCの作成
1. 「VPC」→「VPCを作成」画面に移動する
1. 「作成するリソース」は「VPCなど」を選択する
1. 「名前タグの自動生成」は任意で値を入力する
1. 「IPv4 CIDR ブロック」は使用するアドレスを入力する
1. 「IPv6 CIDR ブロック」は「IPv6 CIDR ブロックなし」を選択する
1. 「アベイラビリティゾーン (AZ) の数」は「1」を選択する
1. 「パブリックサブネットの数」は「１」を選択する
1. 「プライベートサブネットの数」は「０」を選択する
1. 「NAT ゲートウェイ ($)」は「なし」を選択する
1. 「VPC エンドポイント」は「なし」を選択する
1. 「DNS オプション」のチェックは、そのままにする
1. 「VPC」を作成をクリックする

VPCとパブリックサブネットに関連するリソースが一括で作成されます。

##### 5-1-3. EC2インスタンスの作成
1. 「EC2」→「インスタントを起動」画面に移動する
1. 「名前」に任意で値を入力する
1. 「Amazon マシンイメージ (AMI)」は「Amazon Linux 2023 AMI」を選択する
1. 「インスタンスタイプ」は「t2.micro」を選択する
1. 「キーペア」は既存もキーペアを選択する
1. 「VPC」は上記で作成したVPCを選択する
1. 「サブネット」は上記で作成されたサブネットを選択する
1. 「パブリック IP の自動割り当て」は「有効化」を選択する
1. 「セキュリティグループを作成」を選択します
1. 「セキュリティグループ名」を記入します
1. 「説明」を記入します
1. 「インバウンドセキュリティグループのルール」は「ssh」と「HTTP」の２つを作成し、「ソース」は両方とも「0.0.0.0/0」を入力しておく
1. 「インスタンスを起動する」をクリックする

EC２インスタンスが起動されます。

#### 5-２. Reactにてフロントエンド側の作成（ローカル環境）
1. ```npx create-react-app アプリ名```コマンドを叩く
1. ```cd アプリ名```コマンドを叩く
1. ```npm install axios```コマンドを叩く
1. ```App.js```ファイルの内容を下記に変更する
   ```javascript
   import axios from "axios"
   import React, { useEffect, useState } from "react"

   function App() {
     const [greeting, setGreeting] = useState("")

     useEffect(() => {
       (async() => {
         try {
           const response = await axios.get("http://xx.xx.xx.xx/api/greeting")
           setGreeting(response.data.greeting)
         } catch (error) {
           console.error(error)
         }
       })()
     }, [])

     return (
       <div className="App">
         <p>{greeting}</p>
       </div>
     )
   }

   export default App;

   ```
1. 10行目の```xx.xx.xx.xx```は、EC2インスタンス作成時に割り当てられたグローバルIPアドレスを記載する
1. ```npm run build```コマンドを叩く
1. ```/アプリ名/build/```配下にビルドされたhtmlやcssファイルが配置されていることを確認する

```/api/greeting```にリクエストを送り、レスポンスを展開する簡単な実装です。

#### 5-3. Laravelにてバックエンド側の作成（ローカル環境）
1. ```laravel new アプリ名```コマンドを叩く
1. ```cd アプリ名```コマンドを叩く
1. ```sudo chmod 777 storage/```コマンドを叩き、storage配下のファイルの権限を変更する
1. ```/アプリ名/routes/api.php```ファイルを下記に変更する
   ```php5
   <?php

   use Illuminate\Http\Request;
   use Illuminate\Support\Facades\Route;
   use App\Http\Controllers\GreetingController;

   Route::get('/greeting', 
   [GreetingController::class, 'index']);

   ```
1. ```php artisan make:controller GreetingController```コマンドを叩き、```/app/Http/Controllers/GreetingController```ファイルを作成する
1. ```/アプリ名/app/Http/Controllers/GreetingController```ファイルを下記に変更する
   ```php5
   <?php

   namespace App\Http\Controllers;

   class GreetingController extends Controller
   {
       public function index()
       {
       return response()->json(['greeting' => 'Hello SystemI']);
       }
   }
   ```
```/api/greeting```のリクエストに対する受け口を作り、```{ 'greeting': 'SystemI' }```というレスポンスを返す簡単な実装です。
#### 5-4. サーバ構築作業
##### 5-4-1. nginxの導入
1. 上記で作成したEC２インスタンスにSSH接続でログインする
1. ```sudo yum update```コマンドを叩き、アップデートを実行する
1. ```sudo yum install nginx```コマンドを叩き、nginxをインストールする
1. ```sudo vim /etc/nginx/nginx.conf```コマンドを叩き、```server```ディレクティブ内に```location /api```のディレクティブを追記して保存する
   ```
    server {
        listen       80;
        listen       [::]:80;
        server_name  _;
        root         /usr/share/nginx/html;

        # Load configuration files for the default server block.
        include /etc/nginx/default.d/*.conf;

        location /api {
            alias /var/www/laravel/public;　　　　　　　# この後、/var/www配下にlaravelディレクトリを作成する
            fastcgi_pass unix:/var/run/php-fpm/www.sock;
            fastcgi_param SCRIPT_FILENAME $document_root/index.php;
            include fastcgi_params;
        }

        error_page 404 /404.html;
        location = /404.html {
        }

        error_page 500 502 503 504 /50x.html;
        location = /50x.html {
        }
    }
   ```
1. ```sudo systemctl status nginx```コマンドでnginxの起動が確認できれば、```sudo systemctl restart nginx```コマンドでnginxを再起動、nginxが起動していなければ```sudo systemctl start nginx```コマンドでnginxを起動する
1. ```/usr/share/nginx/html```配下のファイルを全て削除し、Reactでbuildしたファイル群を```/usr/share/nginx/html```配下に配置する
   ```bash
   html   # こんな感じに配置する想定です
   　　├── asset-manifest.json
   　　├── favicon.ico
   　　├── index.html
   　　├── logo192.png
   　　├── logo512.png
   　　├── manifest.json
   　　├── robots.txt
   　　└── static
   ```
##### 5-4-2. php-fpmの導入
1. ```sudo yum install php```コマンドを叩き、php-fpmを含むPHP関連のパッケージを導入する
1. ```sudo vim /etc/php-fpm.d/www.conf```コマンドを叩き、php-fpmの設定を下記のように編集する
    a. ```user = apache```を```user = nginx```に変更する
    b. ```group = apache```を```group = nginx```に変更する
    c. ```listen = /run/php-fpm/www.sock```がコメントアウトであれば、コメントアウトを外す
    d. ```listen.owner = nginx```がコメントアウトであれば、コメントアウトを外す
    e. ```listen.group = nginx```がコメントアウトであれば、コメントアウトを外す
    f. ```listen.mode = 0660```がコメントアウトであれば、コメントアウトを外す
1. ```sudo systemctl status php-fpm```コマンドでphp-fpmの起動が確認できれば、```sudo systemctl restart php-fpm```コマンドでphp-fpmを再起動、php-fpmが起動していなければ```sudo systemctl start php-fpm```コマンドでphp-fpmを起動する

##### 5-4-3. Laravelの構成ファイルの配置
1. ```sudo mkdir /var/www/laravel```コマンドを叩き、Laravekの構成ファイルの配置場所を作成する
1. ローカル開発環境のLaravel構成ファイルを```/var/www/laravel```配下に配置する
   ```bash
   laravel  # こんな感じに配置する想定です
     ├── README.md
     ├── app
     ├── artisan
     ├── bootstrap
     ├── composer.json
     ├── composer.lock
     ├── config
     ├── database
     ├── package.json
     ├── phpunit.xml
     ├── public
     ├── resources
     ├── routes
     ├── storage
     ├── tests
     ├── vendor
     └── vite.config.js
   ```
1. ```sudo chown ec2-user:nginx /var/www```コマンドを叩き、所有者とグループを変更する
1. ブラウザで```http://xx.xx.xx.xx```と割り振られたIPアドレスを入力し、下イメージの画面が表示されれば成功です<br>

![スクリーンショット 2023-11-26 13.54.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321920/3bce7ad9-e403-301d-4d3d-ac47a724653c.png)


### ６. 最後に
お疲れ様です。
最後まで読んでいただき、ありがとうございます。

この記事を読んで、何となくCGI連携の仕組みというか、構築方法が分かってもらえたかなと思います。
ちなみに通信方法には、UNIXソケットを使う方法とTCP/IPソケットを使う方法がありますが、今回は高速で効率的と言われているUNIXソケット通信で構築しています。

まぁ、記事のレベルは高くないので、ぜひぜひ実践を。
何かご指摘などありましたら、コメントでお知らせください。

よろしくお願いいたします:bow_tone1: