概要
Laravel プロジェクトを GitLabにプッシュと同時に、Laravelのログイン機構(Laravel Breeze)を AWS CodeBuildでユニットテストします。
ユニットテスト成功後、AWS ECSに自動でデプロイします。
なお、記事全体を通して、プロジェクトやアプリ名、その他AWSリソースのプレフィックスを「myapp」に統一していますので、本記事を参考にされるかたは myapp を読み替えて参考にしてみてください。
開発環境
OS
- Windows11上のWSL2で動作するUbuntu
以下がインストールされている事
- PHP(v8.2以上)
前提
- ドメインの取得と、Route53でホストゾーンを作っておくこと
- ACMで取得したドメインのパブリック証明書をリクエストしておくこと
目次
1章. Laravelのインストール
2章. Unitテスト(ローカル実行編)
3章. AWSリソースの用意-前編(DEV環境のみ)
4章. GitLabの設定
5章. Docker関連ファイルの設定
6章. AWSリソースの用意-後編(DEV環境のみ)
1章. Laravelのインストール
Laravelプロジェクト「myapp」の作成
任意のディレクトリで以下コマンドを実行する。
$ composer create-project --prefer-dist laravel/laravel myapp
開発/STG/本番環境でのHTTPS強制
後ほどECSにデプロイして画面確認するとき用に、開発/STG/本番環境の場合にはHTTPSを強制するようにします。
:
public function boot(): void
{
if (app()->environment('develop') || app()->environment('staging') || app()->environment('production')) {
URL::forceScheme('https');
}
}
:
Laravel Sailのインストール
$ cd myapp
$ composer require laravel/sail --dev
$ php artisan sail:install # 以下画面が表示されるので、必要なサービスをSpaceキーで選択してEnter
┌ Which services would you like to install? ───────────────────┐
│ › ◻ mysql ┃ │
│ ◻ pgsql │ │
│ ◻ mariadb │ │
│ ◻ mongodb │ │
│ ◻ redis │ │
└────────────────────────────────────────────────── 0 selected ┘
※mysql, redis を選択しておく
Laravel Sailの起動
Sail は docker-compose を利用して、必要なコンテナを起動します。
$ ./vendor/bin/sail up -d
Dockerコンテナの起動確認
以下3つのプロセスが起動していること
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
f79af7ce5a24 sail-8.4/app "start-container" About a minute ago Up 5 seconds 0.0.0.0:80->80/tcp, :::80->80/tcp, 0.0.0.0:5173->5173/tcp, :::5173->5173/tcp myapp-laravel.test-1
91591fd6fb81 redis:alpine "docker-entrypoint.s…" About a minute ago Up About a minute (healthy) 0.0.0.0:6379->6379/tcp, :::6379->6379/tcp myapp-redis-1
7c0d080a8fe0 mysql/mysql-server:8.0 "/entrypoint.sh mysq…" About a minute ago Up About a minute (healthy) 0.0.0.0:3306->3306/tcp, :::3306->3306/tcp, 33060-33061/tcp myapp-mysql-1
DBにテーブル作成
$ ./vendor/bin/sail artisan migrate
ブラウザで確認してみる
http://localhost/ (Laravelトップ)
DBに接続してみる
$ ./vendor/bin/sail mysql
Tinker(対話型シェル)を試してみる
$ ./vendor/bin/sail artisan tinker
ログイン機能実装の為に、Breeze のインストール
Breeze は laravel/ui より新しく、Jetstream よりは設定が簡単です
$ composer require laravel/breeze --dev
Breeze をセットアップ
$ php artisan breeze:install
┌ Which Breeze stack would you like to install? ───────────────┐
│ › ● Blade with Alpine │
│ ○ Livewire (Volt Class API) with Alpine │
│ ○ Livewire (Volt Functional API) with Alpine │
│ ○ React with Inertia │
│ ○ Vue with Inertia │
│ ○ API only │
└──────────────────────────────────────────────────────────────┘
※Blade with Alpineを選択してEnter
┌ Would you like dark mode support? ───────────────────────────┐
│ ○ Yes / ● No │
└──────────────────────────────────────────────────────────────┘
※Noを選択してEnter
┌ Which testing framework do you prefer? ──────────────────────┐
│ ○ Pest │
│ › ● PHPUnit │
└──────────────────────────────────────────────────────────────┘
※PHPUnitを選択してEnter
Breeze には Tailwind CSS などのフロントエンドパッケージが必要なのでインストール
$ npm install && npm run build
ログインボタン、登録ボタンが表示されることを、ブラウザで確認
http://localhost/ (Laravelトップ)
参考コマンド
Laravel Sailの停止
$ ./vendor/bin/sail down
すべてのコンテナ・ネットワーク・ボリュームを削除する場合
$ ./vendor/bin/sail down --volumes
2章. Unitテスト(ローカル実行編)
.env.testing を作成
高速化の為、DBはインメモリを利用します
$ cp .env .env.testing
:
+ DB_CONNECTION=sqlite # SQLiteに変更
DB_HOST=mysql
DB_PORT=3306
+ DB_DATABASE=:memory: # メモリに変更
DB_USERNAME=sail
DB_PASSWORD=password
:
phpunit.xmlを修正
:
<env name="CACHE_STORE" value="array"/>
+ <env name="DB_CONNECTION" value="sqlite"/> この行を追加
+ <env name="DB_DATABASE" value=":memory:"/> この行を変更
<env name="MAIL_MAILER" value="array"/>
:
サンプルで用意されているログイン機構(Breeze)のUnitテストを実行
$ ./vendor/bin/phpunit
PHPUnit 11.5.15 by Sebastian Bergmann and contributors.
Runtime: PHP 8.2.27
Configuration: /xxxxx/myapp/phpunit.xml
......................... 25 / 25 (100%)
Time: 00:00.772, Memory: 44.50 MB
OK (25 tests, 61 assertions)
※E: Unable to locate package php82-php-sqlite3 エラーが出た場合は、以下コマンドを実行してください
$ add-apt-repository ppa:ondrej/php # ondrej/phpリポジトリを追加(このリポジトリは、PHPの最新バージョンや拡張モジュールを提供)
$ apt update
$ apt install php8.2-sqlite3 # SQLiteをインストール
GitLabにプッシュしておく
Unitテストが正常に終了できたら、一旦GitLabにプッシュしておく。
また、develop ブランチを作成して、同様にプッシュしておく。
以降は develop ブランチを使用していきます。
3章. AWSリソースの用意-前編(DEV環境のみ)
システム構成図
以下のようなリソースを用意します。
また、説明が複雑にならないよう、今回はDEV(開発用)環境のみ用意します。
デプロイの流れ
これから説明する設定をすることで、下図のような流れで自動デプロイをすることができます。
下図はDEV環境用のフローですが、一部Lambda関数などは本番/STG/HFX(ホットフィックス)用にも利用できるよう汎用的な作りにしています。
VPCの作成
AWSコンソールからVPCを作成します。
※DNS解決を有効化、DNSホスト名を有効化の両方にチェックをいれてください。
サブネットの作成
上記サブネット「myapp-subnet-private-1a」を作成したら、同様に以下のサブネットも追加で作成します。
サブネット名 | アベイラビリティ―ゾーン | IPv4 VPC CIDR ブロック | IPv4 サブネット CIDR ブロック |
---|---|---|---|
myapp-subnet-private-1c | ap-northeast-1c | 10.0.0.0/16 | 10.0.2.0/24 |
myapp-subnet-public-1a | ap-northeast-1a | 10.0.0.0/16 | 10.0.3.0/24 |
myapp-subnet-public-1c | ap-northeast-1c | 10.0.0.0/16 | 10.0.4.0/24 |
インターネットゲートウェイの作成
AWSコンソールからインターネットゲートウェイを作成します。
作成したインターネットゲートウェイの右上にある「アクション」メニューから「VPCにアタッチ」を選び、「myapp-vpc」にアタッチします。
パブリック用のルートテーブルの作成
AWSコンソールからパブリック用のルートテーブルを作成します。
パブリック用のルートテーブル作成後、パブリックルートテーブル画面右上の「アクション」メニューの中から「サブネットの関連付けを編集」をクリック。
「サブネットの関連付けを編集」画面で、publicサブネットをチェックして「関連付けを保存」をクリック。
再びパブリックルートテーブル画面に遷移し、画面右下の「ルートを編集」をクリック。
「ルート編集」画面から、上記で作成したインターネットゲートウェイを設定します。
プライベート用のルートテーブルの作成
AWSコンソールからプライベート用のルートテーブルを作成します。
パブリック用ルートテーブル同様に、画面右上の「アクション」メニューから「サブネットの関連付けを編集」をクリック。
「サブネットの関連付けを編集」画面で、privateサブネットをチェックして「関連付けを保存」をクリック。
セキュリティグループの作成
ターゲットグループの作成
AWSコンソールからALBのターゲットグループを作成します。
ALBの作成
証明書(ACMから)には事前にリクエストしておいた証明書を選択してください。
Route53でレコードを作成
AWSコンソールからレコードを作成します。
ドメインは既に取得済のものを指定してください。
VPCエンドポイントの作成
AWSコンソールからVPCエンドポイントを作成します。
これにより、S3、ECR へのアクセスがインターネットを通らずにAWS内のみになります。
S3へのVPCエンドポイント設定
ルートテーブルには private を選択してください。
ECRへのVPCエンドポイント設定1
サブネットには private を選択してください。
ECRへのVPCエンドポイント設定2
サブネットには private を選択してください。
ECRへのVPCエンドポイント設定3
サブネットには private を選択してください。
S3バケットの作成
zipファイルアップロード用のS3バケットを作成
S3バケット「myapp-<ユニークな値>-git」を作成します。
その際、バージョニングは「有効にする」を選択してください。
環境設定ファイル(.env)格納用のS3バケットを作成
S3バケット「myapp-<ユニークな値>-config」を作成します。
その他はデフォルト設定のままとする。
CodePipelineのアーティファクト格納用のS3を作成
S3バケット「myapp-<ユニークな値>-artifact」を作成します。
その他はデフォルト設定のままとする。
S3バケットのライフサイクルルールについて
特にアーティファクト等は、デプロイする度にデータが増えていきます。
不要なデータを定期的に削除したい場合には、以下の手順で「ライフサイクルルール」を設定して、古いファイルは削除してください。
以下の設定をする(7日でバケットのオブジェクトが削除される例)
Lambdaに必要なIAMロールの作成
AWSコンソールから、Lambdaに必要なIAMロールを作成します。
許可ポリシーから「AmazonS3FullAccess」検索して選択。
同じく許可ポリシーから「AWSLambdaBasicExecutionRole」を検索して選択して次へ。
ロール名に「myapp_s3_lambda_basic_role」を入力して、ロールを作成をクリック。
Lambda関数の作成
AWSコンソールから、Lambda関数を作成します。
実行ロールには上記で作成した「myapp_s3_lambda_basic_role」を指定してください。
Lambda関数の設定(一般設定)
Lambda関数が作成できたら、「設定」>「一般設定」>「編集」クリックして、一般設定の編集画面に遷移します。
その後、メモリを10240MBに、タイムアウトを15分0秒に変更してください。
Lambda関数の設定(環境変数)
次に「設定」>「環境変数」>「編集」をクリックして、環境変数の編集画面に遷移します。
その後、以下の環境変数を登録します。
- BUCKET_NAME: 前述で作成した、S3バケット(myapp-<ユニークな値>-git)
- GIT_USER: GitLabのログインユーザー名
- GIT_PASS: GitLabのログインパスワード
- GIT_REPOSITORY: GitLabのリポジトリ名
- HTTPS_CLONE_URL: GitLabのHTTPSのclone URL
- MAIN_BRANCH_NAME: main(将来用にmainブランチを指定)
- DEVELOP_BRANCH_NAME: develop(developブランチを指定)
- HOTFIX_BRANCH_NAME: hotfix(将来用にhotfixブランチを指定)
Lambdaレイヤーの作成
ローカルLinux環境(WSL2環境)の任意のディレクトリで以下のコマンドを実行し、node_modules.zip を作成する。
$ mkdir npm_library
$ cd npm_library
$ npm install simple-git #node_moduleが出来上がる
$ npm install aws-sdk
$ npm install adm-zip
$ zip -r node_modules.zip node_modules
Lambdaのトップ画面に遷移し、左メニューの「レイヤー」クリックし、レイヤーの作成画面に遷移する。
次に、上記で作成した node_modules.zip を アップロードします。
その際の名称は node_modules とします。
Lambdaレイヤーの追加
再び、作成したLambda関数「myapp_s3_lambda_basic_role」に戻り、ページ下部にある「レイヤーの追加」をクリックし、レイヤーの追加画面に遷移します。
レイヤーの追加画面では、「カスタムレイヤー」を選択したのち、上記で作成したLambdaレイヤー「node_modules」が選べる状態になっているので、選択し、バージョンを最新のものに選択して、「追加」をクリックします。
再びレイヤー追加画面に戻り、「ARNを指定」を選択したのち、「ARNを指定」フォームに
arn:aws:lambda:ap-northeast-1:553035198032:layer:git-lambda2:8 を入力します。
参考
Lambda関数のコードを作成
作成したLambda関数「myapp-gitclone-upload-to-s3」に戻り、「コード」タブをクリックして、以下のコードを貼り付けます。
このコードは develop ブランチだけではなく、将来 main / hotfix ブランチを作ったときにも使えるような仕組みになっています。
その後、「Deploy」をクリックしてデプロイします。
import url from 'url';
import fs from 'fs';
import simpleGit from 'simple-git';
import path from 'path';
import admZip from 'adm-zip';
import aws from 'aws-sdk';
import { execSync } from 'child_process';
// 環境変数の呼び出し
const HTTPS_CLONE_URL = process.env.HTTPS_CLONE_URL;
const BUCKET_NAME = process.env.BUCKET_NAME;
const GIT_USER = process.env.GIT_USER;
const GIT_PASS = process.env.GIT_PASS;
const GIT_REPOSITORY = process.env.GIT_REPOSITORY;
const MAIN_BRANCH_NAME = process.env.MAIN_BRANCH_NAME;
const DEVELOP_BRANCH_NAME = process.env.DEVELOP_BRANCH_NAME;
const HOTFIX_BRANCH_NAME = process.env.HOTFIX_BRANCH_NAME;
export const handler = async(event) => {
//JSONデータをオブジェクトとして取得する
const jsonObj = event;
const rcv_ref = jsonObj['ref']; //ブランチ名が格納
const rcv_repository = jsonObj['repository']['name']; //リポジトリ名
console.log(rcv_repository + " - " + rcv_ref);
let rcv_refs, rcv_branch, target_branch, clone_url;
if(rcv_repository == GIT_REPOSITORY){
rcv_refs = rcv_ref.split("/");
rcv_branch = rcv_refs[rcv_refs.length-1];
if(rcv_branch == MAIN_BRANCH_NAME || rcv_branch == DEVELOP_BRANCH_NAME || rcv_branch == HOTFIX_BRANCH_NAME){
if(rcv_branch == MAIN_BRANCH_NAME){
target_branch = "main";
}
else if(rcv_branch == DEVELOP_BRANCH_NAME){
target_branch = "develop";
}
else if(rcv_branch == HOTFIX_BRANCH_NAME){
target_branch = "hotfix";
}
else{
console.log("対象外ブランチの為、処理なし");
return;
}
clone_url = HTTPS_CLONE_URL;
console.log(clone_url + " の " + rcv_branch + " ブランチからcloneします!");
}else{
console.log("対象外ブランチの為、処理なし");
return;
}
}else{
console.log("対象外リポジトリの為、処理なし");
return;
}
// GitのURLをパースして、cloneのURLを組み立てる
const site = url.parse(clone_url, true);
const userStr = encodeURIComponent(GIT_USER);
const passStr = encodeURIComponent(GIT_PASS);
const uri = site['protocol'] +"//" + userStr + ":" + passStr +"@" + site['host'] + site['pathname'];
console.log("clone実行URL : " + uri);
try {
// 一時ディレクトリ作成
const tmpDir = fs.mkdtempSync('/tmp/');
// clone実行(tmpDir直下にclone)
// await simpleGit().clone(uri, tmpDir);
execSync(`git -c http.sslVerify=false clone ${uri} ${tmpDir}`); // SSL認証をfalseにしてgit clone
execSync(`cd ${tmpDir}; git config --local http.sslVerify false`); // ローカルリポジトリをSSL認証falseにする
// 該当ブランチをcheckout
const git = simpleGit(tmpDir);
await git.checkout(rcv_branch);
await git.pull();
// zip圧縮(git情報は含めない)
rmDir(tmpDir + "/.git");
await zipArchive(tmpDir, target_branch);
let stat = fs.statSync(path.join(tmpDir, target_branch + ".zip"));
console.log(path.join(tmpDir, target_branch + ".zip") + "ファイルのサイズ:" + stat.size);
const filenames = fs.readdirSync(tmpDir);
console.log(tmpDir +"ディレクトリ内のファイル一覧", filenames);
// S3への設置
const s3 = new aws.S3();
let params = {
Bucket: BUCKET_NAME,
Key: GIT_REPOSITORY + "/" + target_branch + ".zip"
};
console.log(params);
await s3.deleteObject(params).promise();
console.log("S3削除完了");
const v= fs.readFileSync(path.join(tmpDir, target_branch + ".zip"));
params.Body=v;
await s3.putObject(params).promise();
console.log("S3アップロード完了");
// 一時ディレクトリ削除
rmDir(tmpDir);
} catch (err) {
console.log(err.name, err);
}
return;
};
// フォルダの圧縮
const zipArchive = async (targetDir, zipFileName) => {
const zip = new admZip();
// zipにフォルダを追加
zip.addLocalFolder(targetDir);
// zipファイル書き出し
zip.writeZip(path.join(targetDir, zipFileName + ".zip"));
return;
};
// ディレクトリを再帰的に削除
const rmDir = (dirPath) => {
if (!fs.existsSync(dirPath)) { return }
const items = fs.readdirSync(dirPath);
for (const item of items) {
const deleteTarget = path.join(dirPath, item);
if (fs.lstatSync(deleteTarget).isDirectory()) {
rmDir(deleteTarget);
} else {
fs.unlinkSync(deleteTarget);
}
}
fs.rmdirSync(dirPath);
};
API Gateway の作成
AWSコンソールから、API Gatewayを作成します。
画面右上の「APIを作成」をクリックして「APIタイプを選択」画面に遷移します。
そこで、「REST API」欄の「構築」ボタンをクリックします。
「新しいAPI」にを選択したまま、API名に「myapp-apigw」と入力、APIエンドポイントタイプに「リージョン」を選択して、画面右下の「APIを作成」をクリックして、リソース画面に遷移します。
リソース画面に遷移したら、画面右側の「メソッドを作成」をクリックします。
メソッドを作成画面に遷移したら、メソッドタイプには「POST」、統合タイプには「Lambda関数」を選択し、Lambda関数には前述で作成した「myapp-gitclone-upload-to-s3」を選択して、画面右下の「メソッドを作成」をクリックします。これでPOSTメソッドが作成されます。
次に、API GatewayとLambda間を非同期通信にして、API Gatewayのタイムアウト(29秒)を回避します。
「リソース」画面で「POST」を選択したまま、「統合リクエスト」タブを選択して、「編集」ボタンをクリックします。
ページ下部に「URLリクエストヘッダーのパラメータ」欄があるので、「リクエストヘッダーのパラメータを追加」をクリックして展開します。
展開されたら、
名前:X-Amz-Invocation-Type
マッピング元:'Event' ※シングルクォーテーションで囲む
を入力します。
次に「マッピングテンプレートの設定」を行います。これをしないと400エラーになります。
すぐ下にある「マッピングテンプレートの追加」ボタンをクリックして、展開します。
展開されたら、
コンテンツタイプ:application/json
テンプレート生成:空欄
テンプレート本文:空欄
を入力して、画面右下の「保存」ボタンをクリックします。
リソース画面に戻ったら、画面右上にある「APIをデプロイ」をクリックします。
Deploy API ダイアログが表示されるので、
ステージ:新しいステージ
ステージ名:v1 ※バージョン管理用
と入力して、「デプロイ」ボタンをクリックします。
その後、ステージ画面に遷移しますので、「URLを呼び出す」をコピーして、ブラウザでそのURLを表示してみてください。ブラウザに「{"message":"Missing Authentication Token"}」と表示されれば、とりあえずOKです。
API Gateway のログを見たいとき
必須ではありませんが、API Gateway のログを見たいときは、下図のように「ログとトレース」を編集して、「CloudWatchログ」に見たいログを選択します。
その際、「CloudWatch Logs role ARN must be set in account settings to enable logging」というエラーが出た場合には、こちら の記事を参考にして設定してみてください。
4章. GitLabの設定
Webhookの設定
GitLabの「設定」>「Webhooks」をクリックして、Webhooks画面のURL欄に、上記 API Gateway の設定で表示されていた「URLを呼び出す」のURLを入力して保存して「Add webhook」ボタンクリックで保存してください。
ここで一度、developブランチに git pushしてみてください。
前述のS3バケット「myapp-<ユニークな値>-git」に 「リポジトリ名/develop.zip」ファイルが作成されていればOKです。
5章. Docker関連ファイルの設定
ECSコンテナ内で使用する Dockerコンテナを用意します。
具体的には Laravelプロジェクトディレクトリ直下に、以下の構成でファイルを準備します。
:
:
├── docker_setting
│ ├── nginx # NGINXコンテナ用
│ │ ├── Dockerfile
│ │ └── default.conf
│ └── php # PHPコンテナ用
│ ├── Dockerfile
│ ├── cache.sh
│ ├── php.ini
│ └── www.conf
:
:
まずはディレクトリを作成します。
mkdir -p docker_setting/nginx
mkdir -p docker_setting/php
NGINXコンテナ用の各ファイルの内容
FROM nginx:1.20-alpine
ENV TZ Asia/Tokyo
COPY ./docker_setting/nginx/default.conf /etc/nginx/conf.d/default.conf
# ECS Execでaws cliを使う場合はコメントをはずす
#RUN apt install python3-pip -f
#RUN pip3 install awscli --upgrade --user
#RUN apt install awscli -f
WORKDIR /app
COPY ./ ./
server {
listen 80;
listen [::]:80;
server_name localhost;
root /app/public;
client_body_buffer_size 64m;
client_max_body_size 64m;
#sendfile on;
tcp_nopush on;
tcp_nodelay on;
#keepalive_timeout 65;
server_tokens off;
types_hash_max_size 8192;
add_header X-XSS-Protection "1; mode=block";
add_header X-Content-Type-Options nosniff;
add_header X-Frame-Options SAMEORIGIN;
add_header Strict-Transport-Security 'max-age=31536000; includeSubDomains; preload';
proxy_temp_path /var/cache/nginx/proxy_temp;
proxy_max_temp_file_size 1024m;
proxy_buffers 8 64m;
proxy_buffer_size 64m;
fastcgi_buffer_size 64m;
fastcgi_buffers 50 64m;
fastcgi_busy_buffers_size 64m;
fastcgi_temp_file_write_size 64m;
index index.php index.html;
charset utf-8;
location = /favicon.ico { access_log off; log_not_found off; }
location = /robots.txt { access_log off; log_not_found off; }
location / {
try_files $uri $uri/ /index.php$is_args$args;
}
location ~ \.php$ {
root /app/public;
fastcgi_pass 127.0.0.1:9000;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
}
location ~ /\.(?!well-known).* {
deny all;
}
}
PHPコンテナ用の各ファイルの内容
以下はPHP8.2のイメージ作成用です。
PHP8.2以上にしたい場合には、適宜変更をお願いします。
FROM php:8.2-fpm-alpine
ENV TZ Asia/Tokyo
ENV LANG C.UTF-8
ENV LANGUAGE ja_JP
RUN apk update && \
apk add --update --no-cache libjpeg-turbo-dev libpng-dev git libzip-dev mysql-client && \
docker-php-ext-install pdo_mysql zip && \
docker-php-ext-configure gd --with-jpeg && \
docker-php-ext-install -j$(nproc) gd
RUN git clone --branch release/5.3.6 https://github.com/phpredis/phpredis.git /usr/src/php/ext/redis
RUN docker-php-ext-install redis
COPY ./docker_setting/php/php.ini /usr/local/etc/php/php.ini
COPY ./docker_setting/php/www.conf /usr/local/etc/php-fpm.d/www.conf
COPY --from=composer:2.2 /usr/bin/composer /usr/bin/composer
WORKDIR /app
COPY ./ ./
# php8.2に対応するように以下だけアップデート
RUN composer require nette/schema:"^1.2"
RUN composer install --optimize-autoloader --no-dev
RUN docker-php-ext-install opcache
RUN chmod -R 777 /app/storage
COPY ./docker_setting/php/cache.sh /usr/local/bin/cache.sh
RUN chmod 777 /usr/local/bin/cache.sh
ENTRYPOINT ["/usr/local/bin/cache.sh"]
zend.exception_ignore_args = off
expose_php = on
max_execution_time = 30
max_input_vars = 1000
upload_max_filesize = 512M
post_max_size = 512M
memory_limit = 1GB
max_file_uploads = 300
error_reporting = E_ALL
display_errors = on
display_startup_errors = on
log_errors = on
error_log = /var/log/php/php-error.log
default_charset = UTF-8
expose_php = off
extension=pdo_mysql
[Date]
date.timezone = Asia/Tokyo
[mysqlnd]
mysqlnd.collect_memory_statistics = on
[Assertion]
zend.assertions = 1
[mbstring]
mbstring.language = Japanese
[opcache]
opcache.enable=1
opcache.revalidate_freq=0
opcache.validate_timestamps=1
opcache.max_accelerated_files=10000
opcache.memory_consumption=192
opcache.max_wasted_percentage=10
opcache.interned_strings_buffer=16
opcache.fast_shutdown=1
[www]
user = www-data
group = www-data
listen = 127.0.0.1:9000
pm = static
pm.max_children = 150
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
pm.max_requests = 8192
;メモリリーク対策
env[NSS_SDB_USE_CACHE] = "YES"
env[TMPDIR] = "/dev/shm"
#!/bin/sh
# キャッシュクリア
php /app/artisan event:clear
php /app/artisan view:clear
php /app/artisan route:clear
php /app/artisan config:clear
php /app/artisan clear-compiled
# キャッシュ生成
php /app/artisan config:cache
php /app/artisan route:cache
php /app/artisan view:cache
# php-fpm起動
php-fpm
6章. AWSリソースの用意-後編(DEV環境のみ)
RDSの作成
AWSコンソールから、RDS(MySQL)を作成します。
今回は無料利用枠で最小構成で作成します。
CodeBuildにアタッチするポリシーの作成
AWSコンソールから、IAMポリシーを作成します。
ポリシーエディタの「JSON」タブをクリックして、ポリシーを作成します。
ポリシーエディタには以下のポリシーを張り付けて、ページ下部の「次へ」をクリック。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Resource": [
"arn:aws:logs:ap-northeast-1:<AWSアカウント>:log-group:/aws/codebuild/build-docker",
"arn:aws:logs:ap-northeast-1:<AWSアカウント>:log-group:/aws/codebuild/build-docker:*"
],
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
]
},
{
"Effect": "Allow",
"Resource": [
"arn:aws:s3:::*"
],
"Action": [
"s3:PutObject",
"s3:GetObject",
"s3:GetObjectVersion"
]
},
{
"Action": [
"ecr:BatchCheckLayerAvailability",
"ecr:CompleteLayerUpload",
"ecr:GetAuthorizationToken",
"ecr:InitiateLayerUpload",
"ecr:PutImage",
"ecr:UploadLayerPart"
],
"Resource": "*",
"Effect": "Allow"
}
]
}
ポリシー名に「myapp_ecr_access_policy」を入力して、ページ下部の「ポリシーの作成」をクリック。
ECRの作成
「phpコンテナdevelop」用のECRを作成
AWSコンソールから、ECRを作成します。
ECRの画面から「作成」をクリックし、リポジトリ名に「dev-myapp-php」を入力して、画面右下の「作成」をクリックします。
「nginxコンテナdevelop」用のECRを作成
同じように、ECRの画面から「作成」をクリックし、リポジトリ名に「dev-myapp-nginx」を入力して、画面右下の「作成」をクリックします。
ライフサイクルルールの設定
ECRが溜まるのを防止する為に、ライフサイクルルールを設定します。
※ECRが無くなるとリビジョンを元に戻せなくなるので、運用ルールは事前に決めておく必要があります。
まず、ライフサイクルルールを設定したいECRを選択して、「アクション」>「ライフサイクルポリシー」を選択します。
「ルールの作成」をクリックします。
下図のように、「イメージをプッシュしてから」の日数か、「数値を超えるイメージ数」かを選択してから「数値」を入力します。
CodeBuildの作成
AWSコンソールから、CodeBuildを作成します。
CodeBuild画面から「プロジェクトを作成する」をクリックします。
下図のように入力します。
ソースのバケット名には、前述で作成した「myapp-<ユニークな値>-git」を選択してください。
S3オブジェクトキーまたはS3フォルダの「Gitリポジトリ」には、使っているGitリポジトリを指定してください。
環境の「イメージ」には、phpをサポートしている「x86_64-standard:5.0」を指定します。
ロール名は「codebuild-dev-myapp-service-role」のように自動入力されます
以降利用するので控えておいてください。
環境変数は以下を設定してください。
- AWS_DEFAULT_REGION:ap-northeast-1
- AWS_ACCOUNT_ID:AWSアカウントID
- ENV_NAME:dev
- IMAGE_PHP_REPO_NAME: dev-myapp-php ※前述のECRと同じ
- IMAGE_NGINX_REPO_NAME: dev-myapp-nginx ※前述のECRと同じ
- CONTAINER_PHP_NAME: dev-myapp-php-container
- CONTAINER_NGINX_NAME: dev-myapp-nginx-container
- DOCKERHUB_USER:DockerHubのユーザー(メルアドではない)
- DOCKERHUB_PASS:DockerHubのパスワード
設定が終わったら、「ビルドプロジェクトを作成する」ボタンをクリックしてください。
IAMロールの編集
上記で自動作成された「codebuild-dev-myapp-service-role」に、既に作成済ポリシー「myapp_ecr_access_policy」をアタッチします。
CodeBuildに必要なファイルを設置
Laravelのプロジェクトルートに以下の2つのファイルを設置します。
version: 0.2
phases:
install:
runtime-versions:
php: 8.2
# nodejs: 22
commands:
# 全フェーズでエラー時に即終了
- set -e
# システム依存関係をインストール
- echo Install system dependencies on `date`
- curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
- npm install -g npm
pre_build:
commands:
# イメージのタグ設定
- IMAGE_TAG=`sh getTag.sh`
- export IMAGE_TAG
- echo ${IMAGE_TAG}
# ECR にログイン
- echo Logging in to Amazon ECR on `date`
- aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin ${AWS_ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com
# Docker Hub にログイン
- echo Logging in to Docker Hub on `date`
- echo ${DOCKERHUB_PASS} | docker login -u ${DOCKERHUB_USER} --password-stdin
# Laravel のセットアップ
- echo Set up Laravel on `date`
- composer install --no-interaction --prefer-dist --optimize-autoloader
- php artisan key:generate --env=testing
- php artisan migrate --env=testing --force
- npm install && npm run build
build:
commands:
# Laravel Breeze のユニットテスト
- echo Run Laravel Breeze tests on `date`
- ./vendor/bin/phpunit
# PHPのイメージ作成
- echo Build PHP started on `date`
- echo Building the PHP Docker image on `date`
- docker build -t ${IMAGE_PHP_REPO_NAME} -f ./docker_setting/php/Dockerfile .
- docker tag ${IMAGE_PHP_REPO_NAME}:latest ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_DEFAULT_REGION}.amazonaws.com/${IMAGE_PHP_REPO_NAME}:${IMAGE_TAG}
# NGINXイメージ作成
- echo Build NGINX started on `date`
- echo Building the NGINX Docker image on `date`
- docker build -t ${IMAGE_NGINX_REPO_NAME} -f ./docker_setting/nginx/Dockerfile .
- docker tag ${IMAGE_NGINX_REPO_NAME}:latest ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_DEFAULT_REGION}.amazonaws.com/${IMAGE_NGINX_REPO_NAME}:${IMAGE_TAG}
post_build:
commands:
- echo Build completed on `date`
# PHPのイメージをECRにプッシュ
- echo Pushing the PHP Docker image...
- docker push ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_DEFAULT_REGION}.amazonaws.com/${IMAGE_PHP_REPO_NAME}:${IMAGE_TAG}
# NGINXのイメージをECRにプッシュ
- echo Pushing the NGINX Docker image...
- docker push ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_DEFAULT_REGION}.amazonaws.com/${IMAGE_NGINX_REPO_NAME}:${IMAGE_TAG}
- printf '[{"name":"%s","imageUri":"%s"},{"name":"%s","imageUri":"%s"}]' ${CONTAINER_PHP_NAME} ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_DEFAULT_REGION}.amazonaws.com/${IMAGE_PHP_REPO_NAME}:${IMAGE_TAG} ${CONTAINER_NGINX_NAME} ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_DEFAULT_REGION}.amazonaws.com/${IMAGE_NGINX_REPO_NAME}:${IMAGE_TAG} > ${ENV_NAME}_imagedefinitions.json
artifacts:
files:
# S3のアーティファクトフォルダにアップロード(CodePipelineで設定)
- ${ENV_NAME}_imagedefinitions.json
#!/bin/bash
# 日付をナノ秒まで取得
ymdhisn=`TZ=JST-9 date +'%Y%m%d%H%M%S_%N'`
echo $ymdhisn
ヘルスチェック用ファイルの設置
Laravelのプロジェクトルートの public フォルダ内に以下のファイルを設置します。
Healthy
git push後に、CodeBuildの画面右上にある「ビルドを開始」をクリックして、ECRのイメージが作成されていればOK。
Laravelの .envファイルを S3に配置
Laravelの.envファイルを、dev.env にコピーします。
その後、dev.envの内容をDEV環境用に以下のように修正して S3バケット「myapp-<ユニークな値>-config」バケットの dev フォルダにアップロードしてください。ECSデプロイ時にこの dev.env を参照します。
※アップロード後はローカルにある dev.env は Git にコミットしないよう削除してください。
APP_NAME=Laravel
APP_ENV=develop # develop
:
APP_DEBUG=true
APP_URL=<developのドメイン> # Route53で取得したドメイン(https://~)
ASSET_URL=<developのドメイン> # Route53で取得したドメイン(https://~)
APP_LOCALE=ja # jaに変更
APP_FALLBACK_LOCALE=en
APP_FAKER_LOCALE=ja_JP # ja_JPに変更
:
:
DB_CONNECTION=mysql
DB_HOST=<RDSのホスト名> # RDSのホスト名
DB_PORT=3306
DB_DATABASE=laravel # RDS構築後にCREATE DATABASE で作成したDB
DB_USERNAME=admin # RDSのユーザー名
DB_PASSWORD=xxxxxxxxx # RDSのパスワード
DB_CHARSET=utf8mb4 # UTF-8とする
DB_COLLATION=utf8mb4_unicode_ci # UTF-8とする
:
:
ECS用のIAMロール作成
ecsInstanceRole の作成
AWSコンソールから、ECS用のIAMロール「ecsInstanceRole」を作成します。
ロール作成画面で、以下のように選択して「次へ」をクリック
以下が表示されていることを確認して「次へ」クリック
ロール名に「ecsInstanceRole」と入力して「ロールを作成」をクリックしてください。
ecsRole の作成
続いて、ECS用のIAMロール「ecsRole」を作成します。
ロール作成画面で、以下のように選択して「次へ」をクリック
以下が表示されていることを確認して「次へ」クリック
ロール名に「ecsRole」と入力して「ロールを作成」をクリックしてください。
ecsAutoScalingRole の作成
続いて、ECS用のIAMロール「ecsAutoScalingRole」を作成します。
ロール作成画面で、以下のように選択して「次へ」をクリック
以下が表示されていることを確認して「次へ」クリック
ロール名に「ecsAutoScalingRole」と入力して「ロールを作成」をクリックしてください。
ecsTaskExecutionRole の作成
続いて、ECS用のIAMロール「ecsTaskExecutionRole」を作成します。
ロール作成画面で、以下のように選択して「次へ」をクリック
大量に表示されるので「task」文字で絞り込み、以下が表示されていることを確認する。
また、S3FullAccess も付与 して「次へ」クリック
ロール名に「ecsTaskExecutionRole」と入力
S3FullAccess も付与されていることを確認したら「ロールを作成」をクリックしてください。
ECSクラスターの作成
AWSコンソールから ECSクラスター を作成します。
名前には「dev-myapp-cluster」と入力して、インフラストラクチャには「AWS Fargate」を選択してください。
ECSのタスク定義の作成
AWSコンソールから タスク定義 を作成します。
「新しいタスク定義の作成」ボタンをクリックし、下図のように入力して、画面右下の「作成」をクリックします。
注意点は以下です。
- コンテナ-1の「イメージURL(赤枠)」には、ECR(dev-myapp-php)から任意のイメージのURLをコピーして貼り付けてください
- コンテナ-1の「ロケーション(赤枠)には、前述で dev.env をアップロードした S3のARNを入力してください
- コンテナ-1のログ収集の awslogs-group の値(赤枠)には、/ecs/dev-myapp-php になるよう変更してください
- コンテナ-2の「イメージURL(赤枠)」には、ECR(dev-myapp-nginx)から任意のイメージのURLをコピーして貼り付けてください
- コンテナ-2のログ収集の awslogs-group の値(赤枠)には、/ecs/dev-myapp-nginx になるよう変更してください
ECSサービスの作成
AWSコンソールから サービス を作成します。
dev-myapp-cluster を選択し、「サービス」タブをクリック、「作成」ボタンをクリックします。
下図のように入力して、「作成」ボタンをクリックします。
CodePipelineの作成
AWSコンソールから CodePipeline を作成します。
まずは「作成オプション」を選択します。
下図のように「カスタムパイプラインを構築する」を選択し、「次に」をクリック。
次に「パイプラインの設定」を選択します。
下図のように入力して「次に」をクリック。
※バケットには前述の「myapp-<ユニークな値>-artifact」を選択してください。
次に「ソースステージ」を追加します。
下図のように入力して「次に」をクリック。
※バケットには前述の「myapp-<ユニークな値>-git」を選択してください。
※S3オブジェクトキーには「Gitリポジトリ名/develop.zip」を入力してください。
次に「ビルドステージ」を追加します。
下図のように入力します。
ちなみに、このステージでUnitテストも実施します。
その為Unitテストが失敗したらビルド全体も停止するように、赤枠にある「ステージ障害時の自動再試行を有効にする」のチェックを外してください。
入力したら「次に」をクリックします。
次の「テストステージ」はスキップしてください(※テストはビルドステージで実施するため)
次に「デプロイステージ」を追加します。
下図のように入力してください。
「イメージ定義ファイル」には、CodeBuild(buildspec_dev.yml)で作成した「dev_imagedefinitions.json」を指定します。このファイルの内容を見て、ECSコンテナを作成します)
入力したら「次に」をクリックします。
最後に確認画面が表示されますので、問題なければ画面下部の「パイプラインを作成する」をクリックしてください。
CodePipelineの開始直後は、以下のように自動でパイプラインが実行されます。
以下のように、Source → Build → Deploy ステージが全て成功したらOKです。
Git の develop ブランチに Git Push してみて、上記のようにCodePipeline が実行することも確認してください。
ECS Exec の設定
Fargate の Docker コンテナにログインできるように、ECS Exec の設定をします。
まず最初に、ローカル環境に Session Manager プラグインをインストールします。
Windowsの方は、こちら を参考にインストーラーをダウンロードし、インストーラーを起動して進めてください。
Macの方は、こちら を参考にインストールを進めてください。
インストールが完了したら「session-manager-plugin」コマンドを実行して、以下のように成功しているか確認してください。
$ session-manager-plugin
The Session Manager plugin was installed successfully. Use the AWS CLI to start a session.
次に、VPCエンドポイントを追加します。
サブネットには private を指定してください。
次に ECS Exec 用のポリシーを作成します。
IAM ポリシー画面から「ポリシーの作成」画面を開き、下図のように「JSON」を選択し、ポリシーエディタに以下のコードを貼り付けます。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ssmmessages:CreateControlChannel",
"ssmmessages:CreateDataChannel",
"ssmmessages:OpenControlChannel",
"ssmmessages:OpenDataChannel"
],
"Resource": "*"
}
]
}
貼り付けたら、画面下部の「次へ」をクリックします。
ポリシー名に「ecsFargateExecPolicy」と入力して、画面右下の「ポリシーの作成」をクリックしてポリシーを作成します。
次に、前述で作成した「ecsTaskExecutionRole」ロールに上記で作成したポリシー「ecsFargateExecPolicy」を追加します。
IAMロール画面から「ecsTaskExecutionRole」を選択して、「許可を追加」の「ポリシーをアタッチ」をクリックしてください。
「ecsFargateExecPolicy」を検索して選択し、画面右下の「許可を追加」をクリックします。
次に、以下のコマンドを実行し、ECS Exec フラグを有効化し、サービスに対して ECS Exec の許可を与えます。
※AWS CLIで実施しますので、必要ならプロファイルも指定してください。
aws ecs update-service --region <リージョン名> --cluster <クラスター名> --service <サービス名> --enable-execute-command
例)aws ecs update-service --region ap-northeast-1 --cluster dev-myapp-cluster --service dev-myapp-service --enable-execute-command
実行すると長文の結果が表示されます。以下のように「enableExecuteCommand」の値が「true」になっていればOKです。
:
:
],
"assignPublicIp": "DISABLED"
}
},
"healthCheckGracePeriodSeconds": 0,
"schedulingStrategy": "REPLICA",
"deploymentController": {
"type": "ECS"
},
"createdBy": "arn:aws:iam::792130004390:user/azito-tech",
"enableECSManagedTags": true,
"propagateTags": "NONE",
"enableExecuteCommand": true, # true になっていればOK
"availabilityZoneRebalancing": "ENABLED"
}
}
最後、現在起動中のタスクには、まだ ECS Exec のサービスが適用されていないので、サービスを更新します。
クラスター「dev-myapp-cluster」を選択し、「サービス」タブから「dev-myapp-service」を選択して、「更新」ボタンをクリックします。
「新しいデプロイの強制」を選択して、画面右下の「更新」をクリックしてください。
下図の赤枠内のように、デプロイが進みます。
デプロイがステータスが進行中になります。
デプロイのステータスが「成功」になれば完了です。
デプロイが完了したら、以下のコマンドでECSにログインができます。
aws ecs execute-command --region <リージョン名> --cluster <クラスター名> --task <タスクID> --container <コンテナ名> --interactive --command "/bin/sh"
例)aws ecs execute-command --region ap-northeast-1 --cluster dev-myapp-cluster --task 95d8ab3a92184ffda2c2097795874d2a --container dev-myapp-php-container --interactive --command "/bin/sh"
ECSログイン後、以下コマンドでマイグレーションを実施します。
/app # php artisan migrate
マイグレーションが成功したらブラウザのURL欄に、事前に取得したドメイン名を入力してください。
Laravelの画面が表示できたら成功です。