Laravel 使用 MinIO 作为文件存储
起因⌗
我们的图片资源和其他静态资源都采用 S3 来进行存储,于是我们为了降低代码复杂度,我们在开发和测试阶段用的存储页时线上 S3 环境,一直以来一切都正常运行着。但是突然有一天,在国内的测试服上传文件到 S3 出现了超时的问题,且这个问题一直无法得到解决。
起初我们考虑在测试服的时候,将文件上传到项目目录的 Storage 下,但是在配置和生成 URL 的时候非常麻烦,不利于 DevOps。
我们的项目中,大量使用自定义的配置或者 ENV 来拼接静态资源的 URL,导致代码的可维护性极差。而且如果要保证访问的一致性,还需要为上传的文件做单独的 Web 服务。
于是乎我就想起了之前在 Github 上看到的一个开源项目 MinIO,没错它就是今天的主角。
MinIO 简介⌗
MinIO 是采用 Go 开发的一套类似于 S3 的存储服务,为什么说用它替代 S3 呢,因为它能兼容 S3 的 API。这样一来,在项目中集成的时候,降低了复杂度。主要在资源 URL 的生成中。
部署 MinIO⌗
我这里采用 Docker Compose 来进行部署,配置文件如下:
services:
minio:
image: minio/minio:latest
restart: always
hostname: minio
container_name: minio
ports:
- 9000:9000
- 9001:9001
volumes:
- /usr/www/data/minio:/data
environment:
- MINIO_DOMAIN=example.dev # 开启 DNS Style Bucket 模式
- MINIO_ROOT_USER=${MINIO_ROOT_USER}
- MINIO_ROOT_PASSWORD=${MINIO_ROOT_PASSWORD}
command: server /data --console-address ":9001"
设置 Nginx 代理:
server {
listen 80;
listen [::]:80;
server_name minio.example.dev bucket.example.dev minio-console.example.dev;
location / {
return 301 https://$host$request_uri;
}
}
# 文件上传时用到的域名
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name minio.example.dev;
# SSL
ssl_certificate certs/fullchain.cer;
ssl_certificate_key certs/example.dev.key;
ssl_stapling on;
ssl_stapling_verify on;
proxy_buffering off;
client_max_body_size 0;
ssl_trusted_certificate /etc/nginx/certs/ca-bundle.trust.crt;
include components/security.conf;
# gzip
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml application/json application/javascript application/rss+xml application/atom+xml image/svg+xml;
location / {
proxy_pass http://minio:9000;
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;
}
}
# MinIO Bucket 的域名,这样访问上传文件的 URL 中不用加 Bucket 名称
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name bucket.example.dev;
# SSL
ssl_certificate certs/fullchain.cer;
ssl_certificate_key certs/example.dev.key;
ssl_stapling on;
ssl_stapling_verify on;
proxy_buffering off;
client_max_body_size 0;
add_header X-Frame-Options "SAMEORIGIN";
add_header X-XSS-Protection "1; mode=block";
add_header X-Content-Type-Options "nosniff";
ssl_trusted_certificate /etc/nginx/certs/ca-bundle.trust.crt;
include components/security.conf;
# gzip
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml application/json application/javascript application/rss+xml application/atom+xml image/svg+xml;
location / {
#在不开启 DNS Style Bucket 的情况下,这里需要将 MinIO 里创建的 Bucket 作为代理的 Endpoint
proxy_pass http://minio:9000/bucket/;
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;
}
}
# MinIO 管理后台的代理配置
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name minio-console.example.dev;
# SSL
ssl_certificate certs/fullchain.cer;
ssl_certificate_key certs/example.dev.key;
ssl_stapling on;
ssl_stapling_verify on;
proxy_buffering off;
client_max_body_size 0;
ssl_trusted_certificate /etc/nginx/certs/ca-bundle.trust.crt;
include components/security.conf;
location / {
proxy_pass http://minio:9001;
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;
}
location /ws {
proxy_pass http://minio:9001;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
chunked_transfer_encoding off;
}
}
MinIO 同样支持 DNS Style Bucket,但默认情况不开启,要开启需要使用环境变量 MINIO_DOMAIN=domain.com
来为 MinIO 设置 FQDN。例如你的 Bucket 名称是 assets,则访问方式就是 assets.domain.com
。开启了 DNS Style Bucket 模式后就不用在通过 Nginx 层来实现 subdomain 到 /path 的映射了。需要注意的是:这种方式需要将 TLD
和 *.TLD
都解析到 MinIO 的代理服务器。
例如 Nginx 中使用通配域名作为 Bucket 的访问域名:
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name *.example.dev;
# SSL
ssl_certificate certs/fullchain.cer;
ssl_certificate_key certs/example.dev.key;
ssl_stapling on;
ssl_stapling_verify on;
proxy_buffering off;
client_max_body_size 0;
add_header X-Frame-Options "SAMEORIGIN";
add_header X-XSS-Protection "1; mode=block";
add_header X-Content-Type-Options "nosniff";
ssl_trusted_certificate /etc/nginx/certs/ca-bundle.trust.crt;
include components/security.conf;
# gzip
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml application/json application/javascript application/rss+xml application/atom+xml image/svg+xml;
location / {
#开启 DNS Style Bucket 的情况下,这里就不需要需要将 MinIO 里创建的 Bucket 作为代理的 URL PATH。
proxy_pass http://minio:9000;
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;
}
}
如果你使用的是 Traefik,则可以参考如下配置:
services:
minio:
image: minio/minio:latest
labels:
- traefik.enable=true
- traefik.http.routers.minio.tls=true
- traefik.http.routers.minio.tls.certresolver=step-ca
- traefik.http.routers.minio.rule=Host(`example.dev`)
- traefik.http.routers.minio.service=minio
- traefik.http.routers.minio.entrypoints=http,https
- traefik.http.services.minio.loadbalancer.server.port=9000
- traefik.http.routers.minio-console.tls=true
- traefik.http.routers.minio-console.tls.certresolver=step-ca
- traefik.http.routers.minio-console.rule=Host(`minio-console.example.dev`) || PathPrefix(`/ws`)
- traefik.http.routers.minio-console.service=minio-console
- traefik.http.routers.minio-console.entrypoints=http,https
- traefik.http.services.minio-console.loadbalancer.server.port=9001
- traefik.http.routers.bucket.tls=true
- traefik.http.routers.bucket.tls.certresolver=step-ca
- traefik.http.routers.bucket.tls.domains[0].main=example.dev
- traefik.http.routers.bucket.tls.domains[0].sans=*.example.dev
- traefik.http.routers.bucket.rule=HostRegexp(`^.+.example.dev$`)
- traefik.http.routers.bucket.service=bucket
- traefik.http.routers.bucket.priority=10
- traefik.http.routers.bucket.entrypoints=http,https
- traefik.http.services.bucket.loadbalancer.server.port=9000
restart: no
hostname: minio
container_name: minio
networks:
- traefik
volumes:
- minio-data:/data
environment:
TZ: Asia/Shanghai
MINIO_DOMAIN: minio.svc.dev
MINIO_ROOT_USER: developer
MINIO_ROOT_PASSWORD: Developer@1994
command: server /data --console-address ":9001"
需要注意的点⌗
- 一定要加上
proxy_set_header
的相关配置,否则无法正常访问 MinIO; - 为 Bucket 单独分配一个域名,这样可以完美模拟 S3 的访问 URL;
Laravel 中集成 MinIO⌗
安装依赖⌗
composer require league/flysystem-aws-s3-v3:~1.0
修改配置文件⌗
修改 config/filesystems.php
文件中的配置如下:
's3' => [
'driver' => 's3',
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION', 'ap-northeast-1'),
'bucket' => env('AWS_BUCKET', 'neox'),
'url' => env('AWS_URL'),
'endpoint' => env('AWS_ENDPOINT'),
'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false)
]
修改环境配置⌗
修改 .env
文件中的配置如下:
FILESYSTEM_DRIVER=s3
AWS_BUCKET=static # 这里是你 Bucket 的名称
AWS_URL=https://bucket.example.dev # 用于生成 URL 的前缀
AWS_ENDPOINT=https://minio.example.dev # 用于上传文件时访问的 URL
AWS_DEFAULT_REGION=ch-shanghai # 这个是 MinIO 后台配置的 Region
AWS_ACCESS_KEY_ID=MINIO_ACCESS_KEY_ID # 这里是你在 MinIO 后台创建的 User Access Key ID
AWS_SECRET_ACCESS_KEY=MINIO_ACCESS_KEY_SECRET # 这里是你在 MinIO 后台创建的 User Access Key SECRET
AWS_USE_PATH_STYLE_ENDPOINT=true # 这里一定要用 true,才能完美兼容 S3
上传文件⌗
$uri = Storage::put($path, $request->file('file'), ['visibility' => 'public']);
if ($uri) {
return response()->json([
'uri' => $uri,
'url' => Storage::url($uri),
'filename' => Str::afterLast($uri, '/')
]);
}
响应如下:
{
"uri": "trend/reports/8q472L1asBz06mM7VK7i4gd1Kyen4eWRaAcxlmX5.jpg",
"url": "https://bucket.example.dev/trend/reports/8q472L1asBz06mM7VK7i4gd1Kyen4eWRaAcxlmX5.jpg",
"filename": "8q472L1asBz06mM7VK7i4gd1Kyen4eWRaAcxlmX5.jpg"
}
到这里就完成了 MinIO 的集成和使用了,在生成环境中我们只需要将 .env
的配置项修改为生产环境的配置项就可以了。
预告⌗
因为我们的测试服数据是克隆自生产环境的,所以很多图片资源是存储在 S3 伤的,那么如何将 S3 上的文件同步到测试服的 MinIO 服务上呢?
后面我会分享如何使用 MinIO CLI 进行同步的经验,以及使用事件和消息队列的方式进行资源同步。
I hope this is helpful, Happy hacking…