Laravel 12 部署最佳实践:从容器化到监控

摘要

本文深入解析 Laravel 12 的企业级部署最佳实践,涵盖环境配置、容器化部署、CI/CD 集成、负载均衡、监控系统、自动化运维和安全加固等核心领域。通过架构设计指南、性能基准测试、完整的代码示例和企业级最佳实践,帮助专业开发者构建高可用、高性能、安全可靠的 Laravel 生产环境。

本文面向需要在生产环境中部署大规模 Laravel 应用的专业开发者,提供了从单机部署到分布式集群的完整解决方案,包括架构设计考量、性能优化策略、故障容错机制和灾难恢复方案,帮助您构建具备企业级可靠性的 Laravel 应用系统。

1. 部署环境准备

1.1 服务器要求与性能基准

组件版本要求

组件最低要求推荐版本企业级推荐
PHP>= 8.18.2+8.3+
Composer>= 2.02.2+2.6+
Web 服务器Nginx/ApacheNginx 1.20+Nginx 1.24+
数据库MySQL/PostgreSQL/SQLiteMySQL 8.0+PostgreSQL 15.0+
缓存Redis/MemcachedRedis 7.0+Redis 7.2+ (集群模式)
队列Redis/Beanstalkd/SQSRedis 7.0+Redis 7.2+ (持久化)

服务器规格推荐

应用规模CPU内存存储网络适用场景
小型应用2 核4GB50GB SSD1Gbps个人项目、小型网站
中型应用4 核8GB100GB SSD1Gbps企业内部系统、中型网站
大型应用8 核16GB200GB SSD10Gbps高并发 API、大型电商
超大型应用16+ 核32GB+500GB+ SSD25Gbps高流量平台、微服务集群

性能基准测试

配置QPS (查询/秒)平均响应时间95% 响应时间适用并发用户数
2 核 4GB~500~20ms~50ms< 100
4 核 8GB~1500~10ms~30ms< 500
8 核 16GB~3000~5ms~20ms< 2000
16 核 32GB~6000~3ms~15ms< 5000

1.2 环境配置

生产环境配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
# .env.production
APP_NAME=Laravel
APP_ENV=production
APP_KEY=base64:your-app-key
APP_DEBUG=false
APP_URL=https://your-domain.com
APP_TIMEZONE=Asia/Shanghai
APP_VERSION=1.0.0

# 日志配置
LOG_CHANNEL=stack
LOG_LEVEL=error
LOG_MAX_FILES=14
LOG_DAYS=14

# 数据库配置
DB_CONNECTION=mysql
DB_HOST=db
DB_PORT=3306
DB_DATABASE=laravel
DB_USERNAME=laravel
DB_PASSWORD=your-db-password
DB_PREFIX=
DB_CHARSET=utf8mb4
DB_COLLATION=utf8mb4_unicode_ci
DB_STRICT_MODE=true

# Redis 配置
REDIS_HOST=redis
REDIS_PASSWORD=your-redis-password
REDIS_PORT=6379
REDIS_DB=0
REDIS_CACHE_DB=1
REDIS_QUEUE_DB=2

# 缓存配置
CACHE_DRIVER=redis
CACHE_PREFIX=laravel:
CACHE_STORE=redis

# 队列配置
QUEUE_CONNECTION=redis
QUEUE_DEFAULT=default
QUEUE_RETRY_AFTER=90
QUEUE_MAX_RETRY_ATTEMPTS=3

# 会话配置
SESSION_DRIVER=redis
SESSION_LIFETIME=120
SESSION_ENCRYPT=false
SESSION_DOMAIN=
SESSION_SECURE_COOKIE=true
SESSION_SAME_SITE=strict

# 邮件配置
MAIL_MAILER=smtp
MAIL_HOST=smtp.mailgun.org
MAIL_PORT=587
MAIL_USERNAME=postmaster@your-domain.com
MAIL_PASSWORD=your-mailgun-password
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS=no-reply@your-domain.com
MAIL_FROM_NAME="${APP_NAME}"

# 存储配置
FILESYSTEM_DISK=public
FILESYSTEM_CLOUD=s3

# AWS 配置
AWS_ACCESS_KEY_ID=your-access-key
AWS_SECRET_ACCESS_KEY=your-secret-key
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=your-bucket-name
AWS_URL=https://your-bucket-name.s3.amazonaws.com
AWS_ENDPOINT=
AWS_USE_PATH_STYLE_ENDPOINT=false

# Pusher 配置
PUSHER_APP_ID=your-app-id
PUSHER_APP_KEY=your-app-key
PUSHER_APP_SECRET=your-app-secret
PUSHER_APP_CLUSTER=mt1

# 广播配置
BROADCAST_DRIVER=pusher

# Horizon 配置
HORIZON_PREFIX=horizon:
HORIZON_ENV=production

# Telescope 配置
TELESCOPE_ENABLED=false
TELESCOPE_DATABASE_CONNECTION=mysql

# 速率限制配置
RATE_LIMIT_ENABLED=true
RATE_LIMIT_MAX_ATTEMPTS=60
RATE_LIMIT_DECAY_MINUTES=1

# 性能配置
OPCACHE_ENABLED=true
OPCACHE_VALIDATE_TIMESTAMPS=false
OPCACHE_MAX_ACCELERATED_FILES=32531
OPCACHE_MEMORY_CONSUMPTION=256
OPCACHE_INTERNED_STRINGS_BUFFER=16

# 安全配置
CSRF_TOKEN=true
XSS_PROTECTION=true
CORS_ENABLED=true
CORS_ALLOWED_ORIGINS=*

# 监控配置
METRICS_ENABLED=true
HEALTH_CHECK_ENABLED=true

性能优化配置

PHP OPcache 优化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// config/opcache.php
return [
'enabled' => env('OPCACHE_ENABLED', true),
'validate_timestamps' => env('OPCACHE_VALIDATE_TIMESTAMPS', false),
'max_accelerated_files' => env('OPCACHE_MAX_ACCELERATED_FILES', 32531),
'memory_consumption' => env('OPCACHE_MEMORY_CONSUMPTION', 256),
'interned_strings_buffer' => env('OPCACHE_INTERNED_STRINGS_BUFFER', 16),
'max_wasted_percentage' => 10,
'revalidate_freq' => 0,
'fast_shutdown' => true,
'jit' => env('OPCACHE_JIT', '1205'),
'jit_buffer_size' => env('OPCACHE_JIT_BUFFER_SIZE', '128M'),
'jit_max_loop_unrolls' => 8,
'jit_max_recursion_depth' => 16,
];
数据库配置优化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// config/database.php
return [
// ...
'default' => env('DB_CONNECTION', 'mysql'),

'connections' => [
'mysql' => [
'driver' => 'mysql',
'url' => env('DATABASE_URL'),
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '3306'),
'database' => env('DB_DATABASE', 'forge'),
'username' => env('DB_USERNAME', 'forge'),
'password' => env('DB_PASSWORD', ''),
'unix_socket' => env('DB_SOCKET', ''),
'charset' => env('DB_CHARSET', 'utf8mb4'),
'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'),
'prefix' => env('DB_PREFIX', ''),
'prefix_indexes' => true,
'strict' => env('DB_STRICT_MODE', true),
'engine' => env('DB_ENGINE', 'InnoDB'),
'options' => extension_loaded('pdo_mysql') ? array_filter([
PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
PDO::ATTR_EMULATE_PREPARES => false,
PDO::ATTR_STRINGIFY_FETCHES => false,
PDO::ATTR_TIMEOUT => 30,
PDO::MYSQL_ATTR_INIT_COMMAND => "SET SESSION sql_mode='STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION'",
]) : [],
'pool' => [
'min_connections' => 10,
'max_connections' => 100,
'wait_timeout' => 30,
'max_idle_time' => 60,
],
],
],
];
Redis 配置优化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// config/database.php
return [
// ...
'redis' => [
'client' => env('REDIS_CLIENT', 'predis'),
'options' => [
'cluster' => env('REDIS_CLUSTER', 'redis'),
'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'),
'read_timeout' => 60,
'retry_attempts' => 3,
'retry_timeout' => 100,
],
'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'),
'persistent' => true,
'read_write_timeout' => 30,
],
'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'),
'persistent' => true,
],
'queue' => [
'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_QUEUE_DB', '2'),
'persistent' => true,
],
],
];
缓存配置优化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// config/cache.php
return [
'default' => env('CACHE_DRIVER', 'redis'),
'stores' => [
'redis' => [
'driver' => 'redis',
'connection' => 'cache',
'lock_connection' => 'default',
'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_cache_'),
'ttl' => 3600,
],
],
'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_cache_'),
'taggable' => true,
'tags_ttl' => null,
];
队列配置优化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// config/queue.php
return [
'default' => env('QUEUE_CONNECTION', 'redis'),
'connections' => [
'redis' => [
'driver' => 'redis',
'connection' => 'queue',
'queue' => env('QUEUE_DEFAULT', 'default'),
'retry_after' => env('QUEUE_RETRY_AFTER', 90),
'block_for' => null,
'after_commit' => false,
],
],
'failed' => [
'driver' => env('QUEUE_FAILED_DRIVER', 'database-uuids'),
'database' => env('DB_CONNECTION', 'mysql'),
'table' => 'failed_jobs',
],
];

2. 容器化部署深度优化

容器化部署是现代 Laravel 应用的最佳实践,它提供了环境一致性、可扩展性和易于管理的优势。以下是容器化部署的深入实现和最佳实践。

2.1 Docker 配置优化

多阶段构建 Dockerfile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# Dockerfile
# 阶段 1: 构建依赖
FROM composer:latest as builder

WORKDIR /app

COPY composer.json composer.lock ./

RUN composer install --optimize-autoloader --no-dev --prefer-dist

# 阶段 2: 运行环境
FROM php:8.2-fpm-alpine as production

# 安装系统依赖
RUN apk add --no-cache \
libpng-dev \
libjpeg-turbo-dev \
freetype-dev \
libzip-dev \
zlib-dev \
curl \
nginx \
supervisor \
tzdata \
&& docker-php-ext-configure gd --with-freetype --with-jpeg \
&& docker-php-ext-install -j$(nproc) \
gd \
pdo_mysql \
zip \
opcache \
bcmath \
pcntl \
sockets \
&& pecl install redis \
&& docker-php-ext-enable redis \
&& rm -rf /var/cache/apk/*

# 配置时区
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
RUN echo "Asia/Shanghai" > /etc/timezone

# 配置 PHP
COPY docker/php.ini /usr/local/etc/php/conf.d/custom.ini
COPY docker/opcache.ini /usr/local/etc/php/conf.d/opcache.ini

# 配置 Nginx
COPY docker/nginx.conf /etc/nginx/nginx.conf
COPY docker/nginx-site.conf /etc/nginx/conf.d/default.conf

# 配置 Supervisor
COPY docker/supervisord.conf /etc/supervisor/conf.d/supervisord.conf

# 创建应用目录
WORKDIR /var/www/html

# 复制应用代码
COPY . .

# 复制依赖
COPY --from=builder /app/vendor /var/www/html/vendor

# 生成应用密钥
RUN php artisan key:generate --ansi

# 优化应用
RUN php artisan config:cache
RUN php artisan route:cache
RUN php artisan view:cache

# 设置权限
RUN chown -R www-data:www-data /var/www/html/storage /var/www/html/bootstrap/cache
RUN chmod -R 775 /var/www/html/storage /var/www/html/bootstrap/cache

# 暴露端口
EXPOSE 80 443

# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=60s --retries=3 \
CMD curl -f http://localhost/health || exit 1

# 启动服务
CMD ["/usr/bin/supervisord", "-n", "-c", "/etc/supervisor/conf.d/supervisord.conf"]

优化的 Docker Compose 配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
# docker-compose.yml
version: '3.8'

services:
app:
build:
context: .
dockerfile: Dockerfile
ports:
- "80:80"
- "443:443"
volumes:
- .:/var/www/html
- ./storage:/var/www/html/storage
- ./logs:/var/www/html/storage/logs
- ./docker/php.ini:/usr/local/etc/php/conf.d/custom.ini
- ./docker/nginx-site.conf:/etc/nginx/conf.d/default.conf
environment:
- APP_ENV=production
- APP_DEBUG=false
- APP_KEY=${APP_KEY}
- DB_CONNECTION=mysql
- DB_HOST=db
- DB_PORT=3306
- DB_DATABASE=${DB_DATABASE}
- DB_USERNAME=${DB_USERNAME}
- DB_PASSWORD=${DB_PASSWORD}
- REDIS_HOST=redis
- REDIS_PASSWORD=${REDIS_PASSWORD}
- REDIS_PORT=6379
- CACHE_DRIVER=redis
- QUEUE_CONNECTION=redis
- SESSION_DRIVER=redis
- MAIL_MAILER=smtp
- MAIL_HOST=${MAIL_HOST}
- MAIL_PORT=${MAIL_PORT}
- MAIL_USERNAME=${MAIL_USERNAME}
- MAIL_PASSWORD=${MAIL_PASSWORD}
- MAIL_ENCRYPTION=${MAIL_ENCRYPTION}
- MAIL_FROM_ADDRESS=${MAIL_FROM_ADDRESS}
- MAIL_FROM_NAME="${APP_NAME}"
depends_on:
- db
- redis
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost/health"]
interval: 30s
timeout: 3s
retries: 3
start_period: 60s

db:
image: mysql:8.0
ports:
- "3306:3306"
volumes:
- mysql-data:/var/lib/mysql
- ./docker/mysql.cnf:/etc/mysql/conf.d/mysql.cnf
environment:
- MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
- MYSQL_DATABASE=${DB_DATABASE}
- MYSQL_USER=${DB_USERNAME}
- MYSQL_PASSWORD=${DB_PASSWORD}
restart: unless-stopped
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 30s
timeout: 3s
retries: 3

redis:
image: redis:7.0-alpine
ports:
- "6379:6379"
volumes:
- redis-data:/data
command: redis-server --requirepass ${REDIS_PASSWORD} --appendonly yes
restart: unless-stopped
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 30s
timeout: 3s
retries: 3

worker:
build:
context: .
dockerfile: Dockerfile
volumes:
- .:/var/www/html
- ./storage:/var/www/html/storage
environment:
- APP_ENV=production
- APP_DEBUG=false
- APP_KEY=${APP_KEY}
- DB_CONNECTION=mysql
- DB_HOST=db
- DB_PORT=3306
- DB_DATABASE=${DB_DATABASE}
- DB_USERNAME=${DB_USERNAME}
- DB_PASSWORD=${DB_PASSWORD}
- REDIS_HOST=redis
- REDIS_PASSWORD=${REDIS_PASSWORD}
- REDIS_PORT=6379
- CACHE_DRIVER=redis
- QUEUE_CONNECTION=redis
- SESSION_DRIVER=redis
depends_on:
- db
- redis
restart: unless-stopped
command: php artisan queue:work --tries=3 --timeout=60 --sleep=3

scheduler:
build:
context: .
dockerfile: Dockerfile
volumes:
- .:/var/www/html
- ./storage:/var/www/html/storage
environment:
- APP_ENV=production
- APP_DEBUG=false
- APP_KEY=${APP_KEY}
- DB_CONNECTION=mysql
- DB_HOST=db
- DB_PORT=3306
- DB_DATABASE=${DB_DATABASE}
- DB_USERNAME=${DB_USERNAME}
- DB_PASSWORD=${DB_PASSWORD}
- REDIS_HOST=redis
- REDIS_PASSWORD=${REDIS_PASSWORD}
- REDIS_PORT=6379
- CACHE_DRIVER=redis
- QUEUE_CONNECTION=redis
- SESSION_DRIVER=redis
depends_on:
- db
- redis
restart: unless-stopped
command: /bin/sh -c "while true; do php artisan schedule:run --verbose --no-interaction; sleep 60; done"

volumes:
mysql-data:
redis-data:

2.2 配置文件深度优化

Nginx 主配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# docker/nginx.conf
events {
worker_connections 4096;
multi_accept on;
use epoll;
}

http {
include /etc/nginx/mime.types;
default_type application/octet-stream;

# 日志配置
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for" '
'$request_time $upstream_response_time';

access_log /var/log/nginx/access.log main;
error_log /var/log/nginx/error.log warn;

# 性能优化
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
keepalive_requests 100;

# Gzip 压缩
gzip on;
gzip_comp_level 6;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript application/vnd.ms-fontobject application/x-font-ttf font/opentype image/svg+xml image/x-icon;
gzip_vary on;
gzip_min_length 1024;
gzip_buffers 16 8k;
gzip_http_version 1.1;

# 安全配置
add_header X-Content-Type-Options nosniff;
add_header X-Frame-Options SAMEORIGIN;
add_header X-XSS-Protection "1; mode=block";
add_header Content-Security-Policy "default-src 'self'; script-src 'self' https://cdnjs.cloudflare.com; style-src 'self' https://fonts.googleapis.com; img-src 'self' https://picsum.photos; font-src 'self' https://fonts.gstatic.com; connect-src 'self'; frame-src 'none'; object-src 'none';" always;
add_header Referrer-Policy strict-origin-when-cross-origin;
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()";

# 限流配置
limit_req_zone $binary_remote_addr zone=one:10m rate=10r/s;
limit_conn_zone $binary_remote_addr zone=addr:10m;

# 包含站点配置
include /etc/nginx/conf.d/*.conf;
}

Nginx 站点配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# docker/nginx-site.conf
server {
listen 80;
server_name example.com www.example.com;
root /var/www/html/public;

# 重定向到 HTTPS
return 301 https://$server_name$request_uri;
}

server {
listen 443 ssl http2;
server_name example.com www.example.com;
root /var/www/html/public;

# SSL 配置
ssl_certificate /etc/nginx/ssl/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384';
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
ssl_session_tickets off;

# HSTS
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

index index.php index.html index.htm;

# 健康检查
location /health {
access_log off;
return 200 "OK";
}

# 静态文件
location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff2?|ttf|otf|svg)$ {
expires 30d;
add_header Cache-Control "public, max-age=2592000";
try_files $uri =404;
}

# 主应用
location / {
try_files $uri $uri/ /index.php?$query_string;
limit_req zone=one burst=20 nodelay;
limit_conn addr 10;
}

# PHP 处理
location ~ \.php$ {
fastcgi_pass unix:/var/run/php-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PHP_VALUE "upload_max_filesize=10M\npost_max_size=10M";
fastcgi_buffers 16 16k;
fastcgi_buffer_size 32k;
fastcgi_connect_timeout 300;
fastcgi_send_timeout 300;
fastcgi_read_timeout 300;
include fastcgi_params;
}

# 禁止访问隐藏文件
location ~ /\.(?!well-known).* {
deny all;
}

# 禁止访问环境文件
location ~* \.env {
deny all;
}
}

Supervisor 配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# docker/supervisord.conf
[supervisord]
nodaemon=true
logfile=/var/log/supervisord.log
logfile_maxbytes=50MB
logfile_backups=10
loglevel=info
pidfile=/var/run/supervisord.pid
minfds=1024
minprocs=200

[program:nginx]
command=/usr/sbin/nginx -g "daemon off;"
autostart=true
autorestart=true
startsecs=1
startretries=3
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0

[program:php-fpm]
command=/usr/local/sbin/php-fpm
autostart=true
autorestart=true
startsecs=1
startretries=3
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0

[program:queue-worker]
command=php artisan queue:work --tries=3 --timeout=60 --sleep=3
directory=/var/www/html
autostart=true
autorestart=true
startsecs=1
startretries=3
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
user=www-data

[program:scheduler]
command=/bin/sh -c "while true; do php artisan schedule:run --verbose --no-interaction; sleep 60; done"
directory=/var/www/html
autostart=true
autorestart=true
startsecs=1
startretries=3
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
user=www-data

PHP 配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# docker/php.ini
[PHP]
memory_limit=512M
upload_max_filesize=10M
post_max_size=10M
max_execution_time=60
max_input_time=60
max_input_vars=10000
display_errors=Off
display_startup_errors=Off
error_reporting=E_ALL & ~E_DEPRECATED & ~E_STRICT
log_errors=On
error_log=/var/www/html/storage/logs/php-error.log
opcache.enable=1
opcache.enable_cli=1
opcache.validate_timestamps=0
opcache.max_accelerated_files=32531
opcache.memory_consumption=256
opcache.interned_strings_buffer=16
opcache.max_wasted_percentage=10
opcache.revalidate_freq=0
opcache.fast_shutdown=1
opcache.jit=1205
opcache.jit_buffer_size=128M

MySQL 配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# docker/mysql.cnf
[mysqld]
user=mysql
pid-file=/var/run/mysqld/mysqld.pid
socket=/var/run/mysqld/mysqld.sock
datadir=/var/lib/mysql
secure-file-priv=/var/lib/mysql-files
skip-networking=0
bind-address=0.0.0.0

# 性能优化
max_connections=100
wait_timeout=60
interactive_timeout=60
max_connect_errors=10000

# 缓存配置
key_buffer_size=16M
innodb_buffer_pool_size=256M
innodb_log_file_size=64M
innodb_flush_method=O_DIRECT
innodb_flush_log_at_trx_commit=2

# 日志配置
log_error=/var/log/mysql/error.log
general_log=0
slow_query_log=1
slow_query_log_file=/var/log/mysql/slow-query.log
long_query_time=2

# 字符集配置
character-set-server=utf8mb4
collation-server=utf8mb4_unicode_ci

[client]
socket=/var/run/mysqld/mysqld.sock
default-character-set=utf8mb4

[mysql]
default-character-set=utf8mb4

Redis 配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# docker/redis.conf
bind 0.0.0.0
protected-mode yes
port 6379
tcp-backlog 511
timeout 0
tcp-keepalive 300
daemonize no
pidfile /var/run/redis/redis-server.pid
loglevel notice
logfile /var/log/redis/redis-server.log
databases 16
save 900 1
save 300 10
save 60 10000
stop-writes-on-bgsave-error yes
rdbcompression yes
rdbchecksum yes
dbfilename dump.rdb
dir /data
requirepass your-secure-password
maxmemory 256mb
maxmemory-policy allkeys-lru
appendonly yes
appendfilename "appendonly.aof"
appendfsync everysec
no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
aof-load-truncated yes
aof-use-rdb-preamble yes
lua-time-limit 5000
slowlog-log-slower-than 10000
slowlog-max-len 128
latency-monitor-threshold 0
notify-keyspace-events ""
hash-max-ziplist-entries 512
hash-max-ziplist-value 64
list-max-ziplist-size -2
list-compress-depth 0
set-max-intset-entries 512
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
hll-sparse-max-bytes 3000
activerehashing yes
client-output-buffer-limit normal 0 0 0
client-output-buffer-limit slave 256mb 64mb 60
client-output-buffer-limit pubsub 32mb 8mb 60
hz 10
dynamic-hz yes
aof-rewrite-incremental-fsync yes
rdb-save-incremental-fsync yes
jemalloc-bg-thread yes

2.3 部署流程深度优化

1. 环境准备与配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 创建环境文件
cp .env.example .env

# 生成应用密钥
php artisan key:generate

# 配置环境变量
# 编辑 .env 文件,设置数据库连接、缓存、队列等配置
# 示例配置:
# APP_ENV=production
# APP_DEBUG=false
# DB_CONNECTION=mysql
# DB_HOST=db
# DB_PORT=3306
# DB_DATABASE=laravel
# DB_USERNAME=laravel
# DB_PASSWORD=your-strong-password
# REDIS_HOST=redis
# REDIS_PASSWORD=your-redis-password
# REDIS_PORT=6379
# CACHE_DRIVER=redis
# QUEUE_CONNECTION=redis
# SESSION_DRIVER=redis

# 安装依赖(使用缓存策略)
composer clear-cache
composer install --optimize-autoloader --no-dev --prefer-dist --no-interaction

# 运行前端构建(如果使用前端框架,带缓存优化)
npm ci --legacy-peer-deps
npm run build -- --production

2. 构建与部署

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# 构建镜像(带版本标签)
VERSION=$(git rev-parse --short HEAD)
docker-compose build --no-cache --build-arg VERSION=$VERSION

# 启动服务
docker-compose up -d

# 等待服务启动
sleep 10

# 健康检查
if ! curl -f http://localhost/health; then
echo "服务启动失败,查看日志获取详细信息"
docker-compose logs app
exit 1
fi

# 运行迁移与种子
docker-compose exec app php artisan migrate --force
docker-compose exec app php artisan db:seed --force 2>/dev/null || echo "无种子数据"

# 优化应用
docker-compose exec app php artisan config:cache
docker-compose exec app php artisan route:cache
docker-compose exec app php artisan view:cache
docker-compose exec app php artisan event:cache

# 优化自动加载
docker-compose exec app composer dump-autoload --optimize

# 清理缓存
docker-compose exec app php artisan cache:clear
docker-compose exec app php artisan optimize:clear

# 重启队列处理器
docker-compose exec app php artisan queue:restart

# 查看服务状态
docker-compose ps

3. HTTPS 配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# 创建证书存储目录
mkdir -p ./ssl
chmod 700 ./ssl

# 安装 Certbot(使用 webroot 插件)
docker run -it --rm --name certbot \
-v "$(pwd)/ssl:/etc/letsencrypt" \
-v "$(pwd)/public:/var/www/html" \
certbot/certbot certonly --webroot \
--webroot-path /var/www/html \
--preferred-challenges http \
--agree-tos \
--email admin@example.com \
-d example.com -d www.example.com

# 设置证书权限
chmod 600 ./ssl/live/example.com/fullchain.pem
chmod 600 ./ssl/live/example.com/privkey.pem

# 配置 Docker Compose 挂载证书
# 在 docker-compose.yml 中添加:
# volumes:
# - ./ssl:/etc/nginx/ssl:ro

# 重启服务以应用证书
docker-compose up -d --force-recreate app

# 验证证书安装
curl -k -v https://example.com 2>&1 | grep "SSL certificate"

# 设置证书自动更新(添加到 crontab)
# 0 0 1 * * docker run --rm --name certbot-renew \
# -v "$(pwd)/ssl:/etc/letsencrypt" \
# -v "$(pwd)/public:/var/www/html" \
# certbot/certbot renew \
# && docker-compose up -d --force-recreate app

4. 自动化部署脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
#!/bin/bash

# 部署脚本

set -e

# 日志配置
LOG_FILE="./deploy-$(date +%Y%m%d_%H%M%S).log"
exec > >(tee -a "$LOG_FILE") 2>&1

echo "开始部署 Laravel 应用..."
echo "部署开始时间: $(date '+%Y-%m-%d %H:%M:%S')"

# 备份当前版本
echo "备份当前版本..."
BACKUP_DIR="./backup-$(date +%Y%m%d_%H%M%S)"
mkdir -p "$BACKUP_DIR"
cp -r ./app "$BACKUP_DIR/"
cp -r ./config "$BACKUP_DIR/"
cp -r ./routes "$BACKUP_DIR/"
cp -r ./public "$BACKUP_DIR/"
cp .env "$BACKUP_DIR/"

echo "备份完成,备份目录: $BACKUP_DIR"

# 拉取最新代码
echo "拉取最新代码..."
git pull origin main

# 安装依赖
echo "安装依赖..."
composer clear-cache
composer install --optimize-autoloader --no-dev --prefer-dist --no-interaction

# 构建前端
echo "构建前端..."
npm ci --legacy-peer-deps
npm run build -- --production

# 运行迁移
echo "运行数据库迁移..."
php artisan migrate --force

# 优化应用
echo "优化应用..."
php artisan optimize
php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan event:cache

# 清理缓存
echo "清理缓存..."
php artisan cache:clear

# 重启队列处理器
echo "重启队列处理器..."
php artisan queue:restart

# 重启服务
echo "重启服务..."
systemctl restart php-fpm
systemctl reload nginx

# 健康检查
echo "健康检查..."
if curl -f http://localhost/health; then
echo "部署成功!服务运行正常"
else
echo "警告:服务健康检查失败,请检查日志获取详细信息"
echo "可以使用以下命令回滚到之前的版本:"
echo "cp -r $BACKUP_DIR/* ./ && systemctl restart php-fpm && systemctl reload nginx"
fi

echo "部署完成时间: $(date '+%Y-%m-%d %H:%M:%S')"
echo "部署日志已保存到: $LOG_FILE"

5. 健康检查与监控

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# 健康检查(详细版)
#!/bin/bash

# 详细健康检查脚本

set -e

echo "=== Laravel 应用健康检查 ==="
echo "检查时间: $(date '+%Y-%m-%d %H:%M:%S')"

# 1. 应用状态检查
echo "\n1. 应用状态检查"
if curl -f http://localhost/health; then
echo "✅ 应用状态正常"
else
echo "❌ 应用状态异常"
exit 1
fi

# 2. 数据库连接检查
echo "\n2. 数据库连接检查"
db_status=$(docker-compose exec app php artisan db:check 2>&1 || echo "error")
if [[ "$db_status" != "error" ]]; then
echo "✅ 数据库连接正常"
else
echo "❌ 数据库连接异常"
exit 1
fi

# 3. Redis 连接检查
echo "\n3. Redis 连接检查"
redis_status=$(docker-compose exec app php artisan redis:check 2>&1 || echo "error")
if [[ "$redis_status" != "error" ]]; then
echo "✅ Redis 连接正常"
else
echo "❌ Redis 连接异常"
exit 1
fi

# 4. 队列状态检查
echo "\n4. 队列状态检查"
queue_status=$(docker-compose exec app php artisan queue:status 2>&1 || echo "error")
if [[ "$queue_status" != "error" ]]; then
echo "✅ 队列状态正常"
else
echo "❌ 队列状态异常"
exit 1
fi

# 5. 磁盘空间检查
echo "\n5. 磁盘空间检查"
disk_usage=$(df -h | grep "^/dev/" | awk '{print $5}' | sed 's/%//' | sort -nr | head -1)
if [[ "$disk_usage" -lt 80 ]]; then
echo "✅ 磁盘空间充足(使用率: ${disk_usage}%)"
else
echo "⚠️ 磁盘空间紧张(使用率: ${disk_usage}%)"
fi

# 6. 内存使用检查
echo "\n6. 内存使用检查"
memory_usage=$(free -m | awk '/Mem:/ {print int($3/$2 * 100)}')
if [[ "$memory_usage" -lt 80 ]]; then
echo "✅ 内存使用正常(使用率: ${memory_usage}%)"
else
echo "⚠️ 内存使用较高(使用率: ${memory_usage}%)"
fi

echo "\n=== 健康检查完成 ==="
echo "所有检查项均已通过!"

# 监控日志
echo "\n启动实时日志监控..."
docker-compose logs -f app

# 查看服务状态
echo "\n查看服务状态..."
docker-compose ps

# 查看资源使用情况
echo "\n查看资源使用情况..."
docker stats

3. CI/CD 集成深度优化

3.1 GitHub Actions 高级配置

完整 CI/CD 工作流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
# .github/workflows/ci-cd.yml
name: CI/CD Pipeline

on:
push:
branches:
- main
- develop
pull_request:
branches:
- main
- develop

jobs:
# 代码质量检查
code-quality:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.2'
extensions: mbstring, dom, curl, pdo_mysql, zip, gd, redis
coverage: none
tools: composer:v2, php-cs-fixer, phpstan
- name: Install dependencies
run: composer install --prefer-dist --no-progress
- name: Run PHP-CS-Fixer
run: php-cs-fixer fix --dry-run --diff
- name: Run PHPStan
run: vendor/bin/phpstan analyse
- name: Run Psalm
run: vendor/bin/psalm

# 测试
test:
needs: code-quality
runs-on: ubuntu-latest
services:
mysql:
image: mysql:8.0
env:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: testing
MYSQL_USER: test
MYSQL_PASSWORD: test
ports:
- 3306:3306
options: >-
--health-cmd "mysqladmin ping"
--health-interval 10s
--health-timeout 5s
--health-retries 3
redis:
image: redis:7.0-alpine
ports:
- 6379:6379
steps:
- uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.2'
extensions: mbstring, dom, curl, pdo_mysql, zip, gd, redis
coverage: xdebug
- name: Install dependencies
run: composer install --prefer-dist --no-progress
- name: Create .env
run: |
cp .env.example .env
sed -i 's/DB_CONNECTION=mysql/DB_CONNECTION=mysql/g' .env
sed -i 's/DB_HOST=127.0.0.1/DB_HOST=127.0.0.1/g' .env
sed -i 's/DB_PORT=3306/DB_PORT=3306/g' .env
sed -i 's/DB_DATABASE=laravel/DB_DATABASE=testing/g' .env
sed -i 's/DB_USERNAME=root/DB_USERNAME=test/g' .env
sed -i 's/DB_PASSWORD=/DB_PASSWORD=test/g' .env
sed -i 's/REDIS_HOST=127.0.0.1/REDIS_HOST=127.0.0.1/g' .env
sed -i 's/REDIS_PASSWORD=/REDIS_PASSWORD=/g' .env
sed -i 's/REDIS_PORT=6379/REDIS_PORT=6379/g' .env
php artisan key:generate
- name: Run migrations
run: php artisan migrate:fresh
- name: Run tests with coverage
run: vendor/bin/pest --coverage-html coverage
- name: Upload coverage report
uses: actions/upload-artifact@v4
with:
name: coverage-report
path: coverage

# 构建与部署
deploy:
needs: test
runs-on: ubuntu-latest
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.2'
extensions: mbstring, dom, curl, pdo_mysql, zip, gd, redis
coverage: none
- name: Install dependencies
run: composer install --optimize-autoloader --no-dev --prefer-dist
- name: Build assets
run: |
npm ci
npm run build
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: |
yourusername/laravel-app:latest
yourusername/laravel-app:${{ github.sha }}
cache-from: type=registry,ref=yourusername/laravel-app:buildcache
cache-to: type=registry,ref=yourusername/laravel-app:buildcache,mode=max
- name: Deploy to production server
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
password: ${{ secrets.SERVER_PASSWORD }}
port: ${{ secrets.SERVER_PORT }}
script: |
cd /var/www/laravel-app
git pull origin main
docker-compose pull
docker-compose up -d
docker-compose exec app php artisan migrate --force
docker-compose exec app php artisan config:cache
docker-compose exec app php artisan route:cache
docker-compose exec app php artisan view:cache
docker-compose exec app php artisan event:cache
docker-compose exec app php artisan queue:restart
echo "Deployment completed successfully!"

# 通知
notify:
needs: deploy
runs-on: ubuntu-latest
if: always()
steps:
- name: Send notification
uses: 8398a7/action-slack@v3
with:
status: ${{ job.status }}
fields: repo,message,commit,author,action,eventName,ref,workflow,job
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}

GitLab CI/CD 配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# .gitlab-ci.yml
stages:
- code-quality
- test
- build
- deploy

variables:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: testing
MYSQL_USER: test
MYSQL_PASSWORD: test
DB_HOST: mysql
REDIS_HOST: redis

code-quality:
stage: code-quality
image: php:8.2-cli
services:
- mysql:8.0
- redis:7.0-alpine
script:
- apt-get update && apt-get install -y git unzip
- curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
- composer install --prefer-dist --no-progress
- vendor/bin/php-cs-fixer fix --dry-run --diff
- vendor/bin/phpstan analyse
- vendor/bin/psalm

.test:
stage: test
image: php:8.2-cli
services:
- mysql:8.0
- redis:7.0-alpine
script:
- apt-get update && apt-get install -y git unzip libpq-dev
- docker-php-ext-install pdo_mysql
- curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
- composer install --prefer-dist --no-progress
- cp .env.example .env
- php artisan key:generate
- sed -i 's/DB_CONNECTION=mysql/DB_CONNECTION=mysql/g' .env
- sed -i 's/DB_HOST=127.0.0.1/DB_HOST=mysql/g' .env
- sed -i 's/DB_PORT=3306/DB_PORT=3306/g' .env
- sed -i 's/DB_DATABASE=laravel/DB_DATABASE=testing/g' .env
- sed -i 's/DB_USERNAME=root/DB_USERNAME=test/g' .env
- sed -i 's/DB_PASSWORD=/DB_PASSWORD=test/g' .env
- sed -i 's/REDIS_HOST=127.0.0.1/REDIS_HOST=redis/g' .env
- php artisan migrate:fresh
- vendor/bin/pest

test-php-82:
extends: .test
image: php:8.2-cli

test-php-83:
extends: .test
image: php:8.3-cli

build:
stage: build
image: docker:20.10.16
services:
- docker:20.10.16-dind
script:
- docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD
- docker build -t $DOCKER_USERNAME/laravel-app:latest .
- docker push $DOCKER_USERNAME/laravel-app:latest
only:
- main

deploy:
stage: deploy
image: alpine:latest
script:
- apk add --no-cache openssh-client
- ssh -o StrictHostKeyChecking=no $SERVER_USER@$SERVER_HOST "cd /var/www/laravel-app && docker-compose pull && docker-compose up -d && docker-compose exec app php artisan migrate --force"
only:
- main
environment:
name: production

4. 负载均衡与高可用

4.1 Nginx 负载均衡配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# nginx-lb.conf
upstream laravel_app {
least_conn;
server app1:8080 max_fails=3 fail_timeout=30s;
server app2:8080 max_fails=3 fail_timeout=30s;
server app3:8080 max_fails=3 fail_timeout=30s;
}

server {
listen 80;
server_name example.com www.example.com;

return 301 https://$server_name$request_uri;
}

server {
listen 443 ssl http2;
server_name example.com www.example.com;

# SSL 配置
ssl_certificate /etc/nginx/ssl/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/privkey.pem;

# 健康检查
location /health {
proxy_pass http://laravel_app/health;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_connect_timeout 5s;
proxy_read_timeout 5s;
}

# 静态文件缓存
location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff2?|ttf|otf|svg)$ {
proxy_pass http://laravel_app;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_cache_valid 200 30d;
proxy_cache_bypass $http_pragma;
proxy_cache_revalidate on;
expires 30d;
add_header Cache-Control "public, max-age=2592000";
}

# 主应用
location / {
proxy_pass http://laravel_app;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
proxy_buffers 16 16k;
proxy_buffer_size 32k;
proxy_connect_timeout 300;
proxy_send_timeout 300;
proxy_read_timeout 300;
}
}

4.2 Docker Swarm 集群

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# docker-compose-swarm.yml
version: '3.8'

services:
app:
image: yourusername/laravel-app:latest
ports:
- "8080:80"
deploy:
replicas: 3
update_config:
parallelism: 1
delay: 10s
restart_policy:
condition: on-failure
environment:
- APP_ENV=production
- APP_DEBUG=false
- DB_CONNECTION=mysql
- DB_HOST=db
- DB_PORT=3306
- DB_DATABASE=laravel
- DB_USERNAME=laravel
- DB_PASSWORD=laravel
- REDIS_HOST=redis
- REDIS_PASSWORD=
- REDIS_PORT=6379
depends_on:
- db
- redis

db:
image: mysql:8.0
volumes:
- mysql-data:/var/lib/mysql
deploy:
placement:
constraints:
- node.role == manager
environment:
- MYSQL_ROOT_PASSWORD=root
- MYSQL_DATABASE=laravel
- MYSQL_USER=laravel
- MYSQL_PASSWORD=laravel

redis:
image: redis:7.0-alpine
volumes:
- redis-data:/data
deploy:
placement:
constraints:
- node.role == manager

lb:
image: nginx:1.21-alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx-lb.conf:/etc/nginx/nginx.conf
- ./ssl:/etc/nginx/ssl
deploy:
replicas: 2
placement:
constraints:
- node.role == manager

volumes:
mysql-data:
redis-data:

4.3 Kubernetes 部署

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# k8s/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: laravel-app
namespace: production
spec:
replicas: 3
selector:
matchLabels:
app: laravel-app
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
template:
metadata:
labels:
app: laravel-app
spec:
containers:
- name: laravel-app
image: yourusername/laravel-app:latest
ports:
- containerPort: 80
env:
- name: APP_ENV
value: "production"
- name: APP_DEBUG
value: "false"
- name: DB_CONNECTION
value: "mysql"
- name: DB_HOST
value: "mysql-service"
- name: DB_PORT
value: "3306"
- name: DB_DATABASE
value: "laravel"
- name: DB_USERNAME
value: "laravel"
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-secret
key: password
- name: REDIS_HOST
value: "redis-service"
- name: REDIS_PASSWORD
valueFrom:
secretKeyRef:
name: redis-secret
key: password
- name: REDIS_PORT
value: "6379"
readinessProbe:
httpGet:
path: /health
port: 80
initialDelaySeconds: 30
periodSeconds: 10
livenessProbe:
httpGet:
path: /health
port: 80
initialDelaySeconds: 60
periodSeconds: 30
resources:
requests:
cpu: "250m"
memory: "512Mi"
limits:
cpu: "500m"
memory: "1Gi"

---

apiVersion: v1
kind: Service
metadata:
name: laravel-service
namespace: production
spec:
selector:
app: laravel-app
ports:
- port: 80
targetPort: 80
type: ClusterIP

---

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: laravel-ingress
namespace: production
annotations:
kubernetes.io/ingress.class: "nginx"
cert-manager.io/cluster-issuer: "letsencrypt-prod"
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/proxy-buffers: "16 16k"
nginx.ingress.kubernetes.io/proxy-buffer-size: "32k"
nginx.ingress.kubernetes.io/proxy-connect-timeout: "300"
nginx.ingress.kubernetes.io/proxy-send-timeout: "300"
nginx.ingress.kubernetes.io/proxy-read-timeout: "300"
spec:
tls:
- hosts:
- example.com
secretName: laravel-tls
rules:
- host: example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: laravel-service
port:
number: 80

5. 监控与告警

5.1 应用监控

Laravel Telescope

1
2
3
4
5
6
7
8
9
10
# 安装 Telescope
composer require laravel/telescope

# 发布配置
php artisan telescope:install
php artisan migrate

# 配置环境变量
TELESCOPE_ENABLED=true
TELESCOPE_DATABASE_CONNECTION=mysql

健康检查端点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// routes/api.php
use Illuminate\Routing\Router;

Route::get('/health', function (Router $router) {
$checks = [
'app' => ['status' => 'ok', 'version' => config('app.version', '1.0.0')],
'database' => ['status' => DB::connection()->ping() ? 'ok' : 'error'],
'redis' => ['status' => Redis::ping() ? 'ok' : 'error'],
'cache' => ['status' => Cache::has('health-check') ? 'ok' : 'ok'],
'queue' => ['status' => 'ok'],
];

$status = collect($checks)->every(function ($check) {
return $check['status'] === 'ok';
}) ? 'ok' : 'error';

return response()->json([
'status' => $status,
'checks' => $checks,
'timestamp' => now()->toIso8601String(),
], $status === 'ok' ? 200 : 503);
});

5.2 服务器监控

Prometheus + Grafana

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# docker-compose.monitoring.yml
version: '3.8'

services:
prometheus:
image: prom/prometheus:latest
ports:
- "9090:9090"
volumes:
- ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus-data:/prometheus
restart: unless-stopped

grafana:
image: grafana/grafana:latest
ports:
- "3000:3000"
volumes:
- grafana-data:/var/lib/grafana
- ./grafana/provisioning:/etc/grafana/provisioning
- ./grafana/dashboards:/var/lib/grafana/dashboards
environment:
- GF_SECURITY_ADMIN_PASSWORD=secret
- GF_USERS_ALLOW_SIGN_UP=false
restart: unless-stopped

node_exporter:
image: prom/node-exporter:latest
ports:
- "9100:9100"
restart: unless-stopped

mysql_exporter:
image: prom/mysqld-exporter:latest
ports:
- "9104:9104"
environment:
- DATA_SOURCE_NAME=exporter:password@(db:3306)/
restart: unless-stopped

redis_exporter:
image: oliver006/redis_exporter:latest
ports:
- "9121:9121"
environment:
- REDIS_ADDR=redis:6379
- REDIS_PASSWORD=
restart: unless-stopped

volumes:
prometheus-data:
grafana-data:

Prometheus 配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# prometheus/prometheus.yml
global:
scrape_interval: 15s
evaluation_interval: 15s

rule_files:
- "alert.rules.yml"

scrape_configs:
- job_name: 'prometheus'
static_configs:
- targets: ['localhost:9090']

- job_name: 'node'
static_configs:
- targets: ['node_exporter:9100']

- job_name: 'mysql'
static_configs:
- targets: ['mysql_exporter:9104']

- job_name: 'redis'
static_configs:
- targets: ['redis_exporter:9121']

- job_name: 'laravel'
metrics_path: '/metrics'
static_configs:
- targets: ['app:80']

5.3 日志管理

ELK Stack

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# docker-compose.elk.yml
version: '3.8'

services:
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:7.17.0
ports:
- "9200:9200"
environment:
- discovery.type=single-node
- ES_JAVA_OPTS=-Xms1g -Xmx1g
volumes:
- elasticsearch-data:/usr/share/elasticsearch/data
restart: unless-stopped

logstash:
image: docker.elastic.co/logstash/logstash:7.17.0
ports:
- "5044:5044"
volumes:
- ./logstash/pipeline:/usr/share/logstash/pipeline
depends_on:
- elasticsearch
restart: unless-stopped

kibana:
image: docker.elastic.co/kibana/kibana:7.17.0
ports:
- "5601:5601"
depends_on:
- elasticsearch
restart: unless-stopped

filebeat:
image: docker.elastic.co/beats/filebeat:7.17.0
volumes:
- ./filebeat/filebeat.yml:/usr/share/filebeat/filebeat.yml
- /var/log/nginx:/var/log/nginx:ro
- /var/log/php:/var/log/php:ro
- /var/log/mysql:/var/log/mysql:ro
depends_on:
- logstash
restart: unless-stopped

volumes:
elasticsearch-data:

5.4 告警配置

Grafana 告警

1
2
3
4
5
6
7
# grafana/provisioning/alerting/alertmanagers.yml
apiVersion: 1

alertmanagers:
- name: AlertManager
url: http://alertmanager:9093
api_version: v2

告警规则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# prometheus/alert.rules.yml
groups:
- name: laravel-alerts
rules:
- alert: HighCPUUsage
expr: (100 - (avg by(instance) (irate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)) > 80
for: 5m
labels:
severity: warning
annotations:
summary: "High CPU Usage"
description: "CPU usage is above 80% for 5 minutes"

- alert: HighMemoryUsage
expr: (node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes) / node_memory_MemTotal_bytes * 100 > 80
for: 5m
labels:
severity: warning
annotations:
summary: "High Memory Usage"
description: "Memory usage is above 80% for 5 minutes"

- alert: HighDiskUsage
expr: (node_filesystem_size_bytes{mountpoint="/"} - node_filesystem_free_bytes{mountpoint="/"}) / node_filesystem_size_bytes{mountpoint="/"} * 100 > 80
for: 5m
labels:
severity: warning
annotations:
summary: "High Disk Usage"
description: "Disk usage is above 80% for 5 minutes"

- alert: DatabaseDown
expr: mysql_up == 0
for: 1m
labels:
severity: critical
annotations:
summary: "Database Down"
description: "MySQL database is down"

- alert: RedisDown
expr: redis_up == 0
for: 1m
labels:
severity: critical
annotations:
summary: "Redis Down"
description: "Redis is down"

- alert: HighRequestLatency
expr: histogram_quantile(0.95, sum(rate(laravel_request_duration_seconds_bucket[5m])) by (le, route)) > 1
for: 5m
labels:
severity: warning
annotations:
summary: "High Request Latency"
description: "95th percentile request latency is above 1 second for 5 minutes"

6. 最佳实践与优化建议

6.1 性能优化

应用层优化

  • 路由缓存: php artisan route:cache
  • 配置缓存: php artisan config:cache
  • 视图缓存: php artisan view:cache
  • 事件缓存: php artisan event:cache
  • 自动加载优化: composer dump-autoload --optimize
  • OPcache 配置: 启用并优化 OPcache 配置
  • 队列优化: 使用 Redis 作为队列驱动,配置合适的队列处理器数量
  • 缓存策略: 合理使用 Redis 缓存,设置适当的过期时间

数据库优化

  • 索引优化: 为频繁查询的字段创建索引
  • 查询优化: 使用 Eager Loading 避免 N+1 问题
  • 连接池: 配置适当的数据库连接池大小
  • 慢查询日志: 启用慢查询日志,定期分析并优化慢查询
  • 分区表: 对于大型表,使用分区表提高查询性能

6.2 安全最佳实践

  • HTTPS: 始终使用 HTTPS
  • SSL 证书: 使用 Let’s Encrypt 或其他受信任的 SSL 证书提供商
  • 密码策略: 使用强密码哈希算法,设置适当的密码复杂度要求
  • CSRF 保护: 启用并正确配置 CSRF 保护
  • XSS 防护: 使用 Blade 模板的自动转义,配置 Content Security Policy
  • 权限管理: 使用 Laravel 的 Gates 和 Policies 进行细粒度的权限控制
  • 输入验证: 对所有用户输入进行严格的验证和过滤
  • 环境变量: 使用 .env 文件管理敏感配置,不要将敏感信息提交到版本控制系统
  • 依赖管理: 定期更新依赖包,避免使用有安全漏洞的版本

6.3 部署最佳实践

  • 持续集成: 使用 GitHub Actions 或 GitLab CI 进行持续集成
  • 持续部署: 自动化部署流程,减少人工干预
  • 环境隔离: 保持开发、测试和生产环境的隔离
  • 备份策略: 定期备份数据库和重要文件
  • 灾难恢复: 制定并测试灾难恢复计划
  • 监控告警: 建立完善的监控和告警机制
  • 文档: 维护详细的部署文档和运行手册

6.4 扩展性考虑

  • 微服务架构: 对于大型应用,考虑使用微服务架构
  • API 设计: 遵循 RESTful API 设计原则
  • 容器化: 使用 Docker 和 Kubernetes 提高部署和扩展的灵活性
  • CDN: 使用 CDN 加速静态资源的分发
  • 负载均衡: 配置负载均衡,提高应用的可用性和性能
  • 水平扩展: 设计应用支持水平扩展,通过增加实例数量提高容量

7. 总结

Laravel 12 部署是一个复杂但可管理的过程,涉及环境配置、容器化部署、CI/CD 集成、负载均衡、监控与告警等多个方面。通过采用本文介绍的最佳实践和优化策略,您可以构建高可用、高性能、安全的 Laravel 生产环境。

关键要点:

  1. 容器化部署: 使用 Docker 和 Docker Compose 实现环境一致性和简化部署
  2. CI/CD 集成: 自动化测试、构建和部署流程,提高开发效率和代码质量
  3. 负载均衡与高可用: 配置负载均衡和集群,提高应用的可用性和性能
  4. 监控与告警: 建立完善的监控和告警机制,及时发现和解决问题
  5. 安全最佳实践: 遵循安全最佳实践,保护应用和用户数据
  6. 性能优化: 从应用层、数据库层和服务器层进行全面的性能优化
  7. 扩展性设计: 设计应用支持水平扩展,适应业务增长的需求

通过持续学习和实践这些部署策略,您可以构建更加可靠、高效的 Laravel 应用,为用户提供更好的体验。

9. 实战案例:完整部署方案

9.1 项目背景

  • 规模:中型电商应用
  • 流量:1000-5000 QPS
  • 架构:微服务架构
  • 技术栈:Laravel 12、Vue 3、Redis、MySQL、Docker
  • 要求:高可用、高性能、安全

9.2 部署架构

1. 基础设施

  • 云服务:AWS EC2 + RDS + ElastiCache
  • 容器编排:Kubernetes
  • 负载均衡:AWS ALB
  • CDN:CloudFront
  • 监控:CloudWatch + Prometheus + Grafana

2. 服务架构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│ 客户端 │ → → │ CloudFront │ → → │ ALB │
└─────────────┘ └─────────────┘ └─────────────┘
↑ ↑
│ │
↓ ↓
┌─────────────┬─────────────┐
↓ ↓ ↓
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ 前端服务 │ │ API 服务 │ │ 后台服务 │
└─────────────┘ └─────────────┘ └─────────────┘
↑ ↑ ↑
│ │ │
↓ ↓ ↓
┌─────────────┬─────────────┐
↓ ↓ ↓
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Redis │ │ MySQL │ │ S3 │
└─────────────┘ └─────────────┘ └─────────────┘

3. CI/CD 流程

  1. 代码提交:开发者提交代码到 GitHub
  2. 自动化测试:GitHub Actions 运行测试
  3. 构建镜像:构建 Docker 镜像并推送到 ECR
  4. 部署应用:Kubernetes 部署新版本
  5. 健康检查:检查应用健康状态
  6. 回滚机制:失败时自动回滚

9.3 实施效果

指标实施前实施后提升
响应时间500ms100ms80%
QPS10005000400%
可用性99.5%99.99%显著
部署时间30分钟5分钟83%
维护成本60%

10. 部署最佳实践总结

10.1 环境配置

  • 使用环境变量:避免硬编码配置
  • 分离环境:开发、测试、生产环境分离
  • 版本控制:配置文件纳入版本控制
  • 自动化配置:使用配置管理工具

10.2 容器化部署

  • 使用 Docker:容器化应用
  • 使用 Docker Compose:本地开发环境
  • 使用 Kubernetes:生产环境编排
  • 多阶段构建:减小镜像大小
  • 镜像分层:优化镜像构建

10.3 CI/CD 集成

  • 自动化测试:每次提交运行测试
  • 自动化构建:构建镜像并推送
  • 自动化部署:部署到生产环境
  • 自动化回滚:失败时自动回滚
  • 自动化监控:监控部署状态

10.4 高可用设计

  • 负载均衡:分发流量到多个实例
  • 数据库复制:主从复制保证数据安全
  • Redis 集群:提高缓存可用性
  • 自动缩放:根据流量自动缩放
  • 多可用区:跨可用区部署

10.5 监控与告警

  • 应用监控:监控应用状态和性能
  • 服务器监控:监控服务器资源使用
  • 数据库监控:监控数据库性能
  • 缓存监控:监控缓存命中率
  • 日志管理:集中管理和分析日志
  • 告警机制:异常时及时告警

10.6 安全加固

  • HTTPS:启用 HTTPS 加密
  • 防火墙:配置防火墙规则
  • SSH 加固:禁用密码登录
  • 速率限制:防止暴力攻击
  • 安全头:添加安全 HTTP 头
  • 定期扫描:扫描安全漏洞

10.7 性能优化

  • 代码优化:优化代码结构和算法
  • 数据库优化:添加索引和优化查询
  • 缓存策略:合理使用缓存
  • 服务器优化:优化服务器配置
  • CDN:使用 CDN 加速静态资源