laravel+mysqlをazureでやりたい
composer require laravel/octane
laravelのconfig/database.phpを修正する必要がある。
config/database.php
<?php
use Illuminate\Support\Str;
use Pdo\Mysql;
return [
/*
|--------------------------------------------------------------------------
| Default Database Connection Name
|--------------------------------------------------------------------------
*/
'default' => env('DB_CONNECTION', 'sqlite'),
/*
|--------------------------------------------------------------------------
| Database Connections
|--------------------------------------------------------------------------
*/
'connections' => [
/*
|--------------------------------------------------------------------------
| SQLite
|--------------------------------------------------------------------------
*/
'sqlite' => [
'driver' => 'sqlite',
'url' => env('DB_URL'),
'database' => env(
'DB_DATABASE',
database_path('database.sqlite')
),
'prefix' => '',
'foreign_key_constraints' => env(
'DB_FOREIGN_KEYS',
true
),
'busy_timeout' => null,
'journal_mode' => null,
'synchronous' => null,
'transaction_mode' => 'DEFERRED',
],
/*
|--------------------------------------------------------------------------
| MySQL
|--------------------------------------------------------------------------
|
| Local:
| MYSQL_ATTR_SSL_CA=
|
| Azure:
| MYSQL_ATTR_SSL_CA=/usr/local/share/ca-certificates/azure/DigiCertGlobalRootG2.crt.pem
|
| AWS:
| MYSQL_ATTR_SSL_CA=/path/to/rds-combined-ca-bundle.pem
|
*/
'mysql' => [
'driver' => 'mysql',
'url' => env('DB_URL'),
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '3306'),
'database' => env('DB_DATABASE', 'laravel'),
'username' => env('DB_USERNAME', 'root'),
'password' => env('DB_PASSWORD', ''),
'unix_socket' => env('DB_SOCKET', ''),
'charset' => env('DB_CHARSET', 'utf8mb4'),
'collation' => env(
'DB_COLLATION',
'utf8mb4_unicode_ci'
),
'prefix' => '',
'prefix_indexes' => true,
'strict' => true,
'engine' => null,
/*
|--------------------------------------------------------------------------
| SSL / TLS
|--------------------------------------------------------------------------
|
| MYSQL_ATTR_SSL_CA が空の場合は、SSL CAオプションを
| 設定しません。
|
| これにより、ローカルMySQLではSSLを強制しません。
|
| Azure / AWSでは環境変数からCAファイルを指定できます。
|
*/
'options' => extension_loaded('pdo_mysql')
? array_filter([
(
PHP_VERSION_ID >= 80500
? Mysql::ATTR_SSL_CA
: PDO::MYSQL_ATTR_SSL_CA
) => env('MYSQL_ATTR_SSL_CA'),
])
: [],
],
/*
|--------------------------------------------------------------------------
| MariaDB
|--------------------------------------------------------------------------
*/
'mariadb' => [
'driver' => 'mariadb',
'url' => env('DB_URL'),
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '3306'),
'database' => env('DB_DATABASE', 'laravel'),
'username' => env('DB_USERNAME', 'root'),
'password' => env('DB_PASSWORD', ''),
'unix_socket' => env('DB_SOCKET', ''),
'charset' => env('DB_CHARSET', 'utf8mb4'),
'collation' => env(
'DB_COLLATION',
'utf8mb4_unicode_ci'
),
'prefix' => '',
'prefix_indexes' => true,
'strict' => true,
'engine' => null,
'options' => extension_loaded('pdo_mysql')
? array_filter([
(
PHP_VERSION_ID >= 80500
? Mysql::ATTR_SSL_CA
: PDO::MYSQL_ATTR_SSL_CA
) => env('MYSQL_ATTR_SSL_CA'),
])
: [],
],
/*
|--------------------------------------------------------------------------
| PostgreSQL
|--------------------------------------------------------------------------
*/
'pgsql' => [
'driver' => 'pgsql',
'url' => env('DB_URL'),
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '5432'),
'database' => env('DB_DATABASE', 'laravel'),
'username' => env('DB_USERNAME', 'root'),
'password' => env('DB_PASSWORD', ''),
'charset' => env('DB_CHARSET', 'utf8'),
'prefix' => '',
'prefix_indexes' => true,
'search_path' => 'public',
'sslmode' => env(
'DB_SSLMODE',
'prefer'
),
],
/*
|--------------------------------------------------------------------------
| SQL Server
|--------------------------------------------------------------------------
*/
'sqlsrv' => [
'driver' => 'sqlsrv',
'url' => env('DB_URL'),
'host' => env('DB_HOST', 'localhost'),
'port' => env('DB_PORT', '1433'),
'database' => env('DB_DATABASE', 'laravel'),
'username' => env('DB_USERNAME', 'root'),
'password' => env('DB_PASSWORD', ''),
'charset' => env('DB_CHARSET', 'utf8'),
'prefix' => '',
'prefix_indexes' => true,
// 'encrypt' => env('DB_ENCRYPT', 'yes'),
// 'trust_server_certificate' => env(
// 'DB_TRUST_SERVER_CERTIFICATE',
// 'false'
// ),
],
],
/*
|--------------------------------------------------------------------------
| Migration Repository Table
|--------------------------------------------------------------------------
*/
'migrations' => [
'table' => 'migrations',
'update_date_on_publish' => true,
],
/*
|--------------------------------------------------------------------------
| Redis Databases
|--------------------------------------------------------------------------
*/
'redis' => [
'client' => env(
'REDIS_CLIENT',
'phpredis'
),
'options' => [
'cluster' => env(
'REDIS_CLUSTER',
'redis'
),
'prefix' => env(
'REDIS_PREFIX',
Str::slug(
(string) env(
'APP_NAME',
'laravel'
)
) . '-database-'
),
'persistent' => env(
'REDIS_PERSISTENT',
false
),
],
/*
|--------------------------------------------------------------------------
| Default Redis Connection
|--------------------------------------------------------------------------
*/
'default' => [
'url' => env('REDIS_URL'),
'host' => env(
'REDIS_HOST',
'127.0.0.1'
),
'username' => env(
'REDIS_USERNAME'
),
'password' => env(
'REDIS_PASSWORD'
),
'port' => env(
'REDIS_PORT',
'6379'
),
'database' => env(
'REDIS_DB',
'0'
),
'max_retries' => env(
'REDIS_MAX_RETRIES',
3
),
'backoff_algorithm' => env(
'REDIS_BACKOFF_ALGORITHM',
'decorrelated_jitter'
),
'backoff_base' => env(
'REDIS_BACKOFF_BASE',
100
),
'backoff_cap' => env(
'REDIS_BACKOFF_CAP',
1000
),
],
/*
|--------------------------------------------------------------------------
| Cache Redis Connection
|--------------------------------------------------------------------------
*/
'cache' => [
'url' => env('REDIS_URL'),
'host' => env(
'REDIS_HOST',
'127.0.0.1'
),
'username' => env(
'REDIS_USERNAME'
),
'password' => env(
'REDIS_PASSWORD'
),
'port' => env(
'REDIS_PORT',
'6379'
),
'database' => env(
'REDIS_CACHE_DB',
'1'
),
'max_retries' => env(
'REDIS_MAX_RETRIES',
3
),
'backoff_algorithm' => env(
'REDIS_BACKOFF_ALGORITHM',
'decorrelated_jitter'
),
'backoff_base' => env(
'REDIS_BACKOFF_BASE',
100
),
'backoff_cap' => env(
'REDIS_BACKOFF_CAP',
1000
),
],
],
];
main.tf
terraform {
required_version = ">= 1.3.0"
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 4.0"
}
random = {
source = "hashicorp/random"
version = "~> 3.0"
}
}
}
provider "azurerm" {
features {
resource_group {
prevent_deletion_if_contains_resources = false
}
}
}
# ============================================================
# 1. Resource Group
# ============================================================
resource "azurerm_resource_group" "rg" {
name = "rg-laravel"
location = "japaneast"
}
# ============================================================
# 2. Log Analytics
# ============================================================
resource "azurerm_log_analytics_workspace" "law" {
name = "law-laravel"
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
sku = "PerGB2018"
retention_in_days = 30
}
# ============================================================
# 3. Azure Container Registry
# ============================================================
resource "azurerm_container_registry" "acr" {
name = "myappacr817263"
resource_group_name = azurerm_resource_group.rg.name
location = azurerm_resource_group.rg.location
sku = "Basic"
admin_enabled = true
}
# ============================================================
# 4. MySQL Password
# ============================================================
resource "random_password" "mysql" {
length = 24
special = true
override_special = "!#&*"
}
# ============================================================
# 5. Laravel APP_KEY
# ============================================================
resource "random_id" "app_key" {
byte_length = 32
}
# ============================================================
# 6. MySQL Flexible Server
# ============================================================
resource "azurerm_mysql_flexible_server" "mysql" {
name = "mysql-laravel-app-817263"
resource_group_name = azurerm_resource_group.rg.name
location = azurerm_resource_group.rg.location
administrator_login = "laravel_admin"
administrator_password = random_password.mysql.result
version = "8.0.21"
sku_name = "B_Standard_B1ms"
storage {
size_gb = 20
}
backup_retention_days = 7
}
# ============================================================
# 7. MySQL Database
# ============================================================
resource "azurerm_mysql_flexible_database" "db" {
name = "laravel_production"
resource_group_name = azurerm_resource_group.rg.name
server_name = azurerm_mysql_flexible_server.mysql.name
charset = "utf8mb4"
collation = "utf8mb4_unicode_ci"
}
# ============================================================
# 8. MySQL Firewall
# ============================================================
resource "azurerm_mysql_flexible_server_firewall_rule" "allow_all" {
name = "allow-container-apps"
resource_group_name = azurerm_resource_group.rg.name
server_name = azurerm_mysql_flexible_server.mysql.name
start_ip_address = "0.0.0.0"
end_ip_address = "255.255.255.255"
}
# ============================================================
# 9. Container Apps Environment
# ============================================================
resource "azurerm_container_app_environment" "cae" {
name = "cae-laravel"
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
log_analytics_workspace_id = azurerm_log_analytics_workspace.law.id
}
# ============================================================
# 10. Laravel Container App
# ============================================================
resource "azurerm_container_app" "laravel" {
name = "ca-laravel"
resource_group_name = azurerm_resource_group.rg.name
container_app_environment_id = azurerm_container_app_environment.cae.id
revision_mode = "Single"
secret {
name = "db-password"
value = random_password.mysql.result
}
secret {
name = "acr-password"
value = azurerm_container_registry.acr.admin_password
}
secret {
name = "app-key"
value = "base64:${random_id.app_key.b64_std}"
}
registry {
server = azurerm_container_registry.acr.login_server
username = azurerm_container_registry.acr.admin_username
password_secret_name = "acr-password"
}
ingress {
external_enabled = true
target_port = 8000
transport = "auto"
traffic_weight {
percentage = 100
latest_revision = true
}
}
template {
min_replicas = 1
max_replicas = 1
container {
name = "laravel-app"
image = "${azurerm_container_registry.acr.login_server}/myapp-app:latest"
cpu = 0.5
memory = "1Gi"
env {
name = "APP_ENV"
value = "production"
}
env {
name = "APP_DEBUG"
value = "false"
}
env {
name = "APP_KEY"
secret_name = "app-key"
}
env {
name = "DB_CONNECTION"
value = "mysql"
}
env {
name = "DB_HOST"
value = azurerm_mysql_flexible_server.mysql.fqdn
}
env {
name = "DB_PORT"
value = "3306"
}
env {
name = "DB_DATABASE"
value = azurerm_mysql_flexible_database.db.name
}
env {
name = "DB_USERNAME"
value = azurerm_mysql_flexible_server.mysql.administrator_login
}
env {
name = "DB_PASSWORD"
secret_name = "db-password"
}
# ★ Azure MySQLへのSSL接続用パス(OS標準証明書を指定)
env {
name = "MYSQL_ATTR_SSL_CA"
value = "/etc/ssl/certs/ca-certificates.crt"
}
env {
name = "OTEL_SDK_DISABLED"
value = "true"
}
}
}
lifecycle {
ignore_changes = [
template[0].container[0].image
]
}
depends_on = [
azurerm_mysql_flexible_database.db,
azurerm_mysql_flexible_server_firewall_rule.allow_all
]
}
# ============================================================
# 11. Outputs
# ============================================================
output "resource_group_name" {
value = azurerm_resource_group.rg.name
}
output "acr_login_server" {
value = azurerm_container_registry.acr.login_server
}
output "mysql_host" {
value = azurerm_mysql_flexible_server.mysql.fqdn
}
output "container_app_url" {
value = "https://${azurerm_container_app.laravel.ingress[0].fqdn}"
}
Dockerfile
FROM dunglas/frankenphp:php8.3
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
ca-certificates \
curl \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
RUN install-php-extensions \
pdo_mysql \
gd \
pcntl \
zip \
bcmath \
opcache \
opentelemetry
RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"
RUN { \
echo 'opcache.enable=1'; \
echo 'opcache.enable_cli=1'; \
echo 'opcache.validate_timestamps=0'; \
echo 'opcache.max_accelerated_files=20000'; \
echo 'opcache.memory_consumption=256'; \
} > "$PHP_INI_DIR/conf.d/opcache-recommended.ini"
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
WORKDIR /app
COPY composer.json composer.lock ./
RUN composer install \
--no-dev \
--no-interaction \
--prefer-dist \
--no-scripts \
--no-autoloader
COPY . .
# ローカルの .env の混入を防ぐ
RUN rm -f .env
RUN rm -f bootstrap/cache/*.php
RUN composer dump-autoload \
--optimize \
--no-dev
RUN mkdir -p \
storage/framework/cache \
storage/framework/sessions \
storage/framework/views \
storage/logs \
bootstrap/cache
RUN chown -R www-data:www-data \
/app/storage \
/app/bootstrap/cache \
&& chmod -R 775 \
/app/storage \
/app/bootstrap/cache
ENV APP_ENV=production \
APP_DEBUG=false \
OTEL_SERVICE_NAME=myapp \
OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
EXPOSE 8000
# 起動時にキャッシュクリア、マイグレーション自動実行、Octane起動を行う
CMD php artisan config:cache && \
php artisan event:cache && \
php artisan migrate --force && \
php artisan octane:start --server=frankenphp --host=0.0.0.0 --port=8000 --workers=auto --max-requests=500