0%

基于 images.weserv.nl 和 minio 搭建图片处理服务

在使用阿里云 OSS 对象存储服务时,OSS 提供了图片处理的服务,可以通过在请求图片对象的 url 上携带各种图片处理的参数,如:x-oss-process=image/resize,w_100 等来实现图片的缩放、裁剪、水印、内切圆等等图片处理方式。

阿里云 OSS 图片处理服务指南

类似阿里云的这个图片处理服务,我们也可以通过 MinIO 和一个开源的图片处理服务项目 images.weserv.nl 来自建一套同时支持图片处理(和部分兼容阿里云图片处理样式)的对象存储系统。

images.weserv.nl 这个开源项目的最新版本 5.x 在 4.x 版本上完全使用 C++ 重写了,大幅度提升了性能,而 4.x 主要是基于 openresty + lua 来实现的,具体的重写原因,主要也是因为 images.serv.nl 的流量请求越来越多,对图片处理的性能要求也越来越高,所以团队从 5.x 之后完全重写了。不过,我们这里的演示仍然是基于 images.weserv.nl 4.x 分支的 :)

images.weserv.nl支持的图片处理能力比 OSS 的图片处理服务要少一些,不过一些基本的如缩放、裁剪、内切圆等等图片处理操作都还是支持的,可以至官方参考文档中查看具体支持的图片处理参数

关于 images.weserv.nl 从 5.x 开始重写的原因可以从官方的 API 5 Reference 中看到。

示例部署架构

在正式下手搭建之前,先简单看下总体的结构:

这里的示例是通过 docker 容器来安装部署 images.weserv.nl 和 MinIO,最前端的 Nginx 负责将外部的对象访问请求根据对象类型来转发:

  • 如果为非图片类型对象,则默认转发至 MinIO 服务,直接由 MinIO 根据策略返回对戏那个;
  • 如果为图片对象,则转发至 imagesweserv 服务,由 imagesweserv 先从 MinIO 中获取图片并在本地进行图片处理,处理完成后再通过 Nginx 返回给外部请求端;

这个示例架构基本还是很清晰明了的,下面开始搭建示例环境,本文档就忽略了 MinIO 的 docker 容器化搭建步骤,主要记录下 imagesweserv 容器服务和 Nginx 的配置。

搭建 imagesweserv 服务容器

首先把 images.weserv.nl 4.x 分支的代码 clone 到本地,然后打开 app/config.lua 源码文件,更改如下:

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
return {
-- Template options
template = {
name = "API 4 - GitHub, DEMO",
url = "images.weserv.nl",
args = "",
example_image = "ory.weserv.nl/lichtenstein.jpg",
example_transparent_image = "ory.weserv.nl/transparency_demo.png",
example_smartcrop_image = "ory.weserv.nl/zebra.jpg"
},

-- Client options
client = {
-- User agent for this client
user_agent = "Mozilla/5.0 (compatible; ImageFetcher/8.0; +http://images.weserv.nl/)",
-- Sets the connect timeout thresold, send timeout threshold, and read timeout threshold,
-- respetively, in milliseconds.
timeouts = {
connect = 5000,
send = 5000,
read = 15000,
},
-- Number describing the max image size to receive (in bytes). Use 0 for no limits.
max_image_size = 104857600, -- 100 MB
-- Number describing the maximum number of allowed redirects.
max_redirects = 10,
-- Allowed mime types. Use empty table to allow all mime types
allowed_mime_types = {
["image/jpeg"] = "jpg",
["image/png"] = "png",
["image/gif"] = "gif",
["image/bmp"] = "bmp",
["image/tiff"] = "tiff",
["image/webp"] = "webp",
["image/x-icon"] = "ico",
["image/vnd.microsoft.icon"] = "ico",
}
},

-- Throttler options
throttler = {
-- Redis driver
redis = {
scheme = "tcp",
host = "127.0.0.1",
port = 6379,
timeout = 1000, -- 1 sec
-- The max idle timeout (in ms) when the connection is in the pool
max_idle_timeout = 10000,
-- The maximal size of the pool for every nginx worker process
pool_size = 100
},
allowed_requests = 700, -- 700 allowed requests
minutes = 3, -- In 3 minutes
prefix = "c", -- Cache key prefix
whitelist = {
["192.168.1.77"] = true, -- Local IP
["127.0.0.1"] = true, -- Local IP
["192.168.1.168"] = true,
},
policy = {
ban_time = 60, -- If exceed, ban for 60 minutes
cloudflare = {
enabled = false, -- Is CloudFlare enabled?
email = "",
auth_key = "",
zone_id = "",
mode = "block" -- The action to apply if the IP get's banned
}
}
}
}

我们根据自己的运行环境来更改其中的配置项:

  • client:配置 imagesweserv 作为客户端向后向的 MinIO 发起获取对象文件的参数;
  • throttler:配置限流控制的策略等相关参数;

更改之后,可以开始构建 imagesweserv 镜像,之后再启动相应的容器:

1
2
$ docker build . -t imagesweserv
$ docker run --shm-size=1gb -p 18080:80 -d --name=imagesweserv imagesweserv

配置和启动前端的 Nginx

因为需要编写 lua 脚本来实现对阿里云 oss 样式的解析,因此这里示例基于 Openresty 来搭建前端 Nginx+Lua 环境,具体安装参考 Openresty 官网。这里重点看下 Nginx 的配置以及解析图片处理请求参数的部分代码。

下面为 nginx/conf/conf.d/minio.conf

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
upstream minio_cluster {
server 192.168.1.77:19000;
}

server {
listen 80;
server_name 192.168.1.77 localhost;

# To allow special characters in headers
ignore_invalid_headers off;

# Allow any size file to be uploaded.
# Set to a value such as 1000m; to restrict file size to a specific value
client_max_body_size 100m;

# To disable buffering
proxy_buffering off;

location ~* /kyc/(.*)/(.*).(jpg|jpeg|png|gif|webp)$ {

set $upstream "";
rewrite_by_lua_file /usr/local/openresty/nginx/lua/image.lua;

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_set_header Host $http_host;
proxy_connect_timeout 30;
proxy_send_timeout 30;
proxy_read_timeout 30;

# Default is HTTP/1, keepalive is only enabled in HTTP/1.1
proxy_http_version 1.1;
proxy_set_header Connection "";
chunked_transfer_encoding off;

proxy_pass http://$upstream;
}

location /kyc {
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_set_header Host $http_host;
proxy_connect_timeout 30;
proxy_send_timeout 30;
proxy_read_timeout 30;
proxy_http_version 1.1;
proxy_set_header Connection "";
chunked_transfer_encoding off;
proxy_pass http://minio_cluster;
}
}

该配置中将指定匹配路径的图片请求通过第一个 location section 来处理,而剩余的其他对象请求,均通过后面的 location /kyc 来处理,也就是直接转发给了后面的 minio server cluster。

这里重点看下图片处理的逻辑,从上面的配置中可以看到遇到匹配的图片请求,在 rewrite 阶段由 nginx/lua/image.lua 来处理,下面为示例中的 image.lua 处理脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
-- get style, eg: x-oss-process=style/preview1
local img_style = nil
local uri_args = ngx.req.get_uri_args()
for key, val in pairs(uri_args) do
if key == "x-oss-process" and type(val) == "string" then
local i, j = string.find(val, "style/")
if i and j and i == 1 and j == 6 then
img_style = string.sub(val, 7, -1)
end
end
end

if img_style == "preview1" then
ngx.var.upstream = "192.168.1.77:18080/?url=172.17.0.2:9000" .. ngx.var.uri .. "?q=90&w=600"
ngx.log(ngx.ERR, "==> upstream of <preview1>: " .. ngx.var.upstream)
return ngx.exit(ngx.OK)
end

ngx.var.upstream = "192.168.1.77:18080/?url=172.17.0.2:9000" .. ngx.var.request_uri
ngx.log(ngx.ERR, "==> upstream of <nostyle>: " .. ngx.var.upstream)
return ngx.exit(ngx.OK)

我们这里的示例是演示了一个 oss style 为 preview1 的图片样式,这里是假设该样式要的是图片质量(q)为 90% 和图片宽度(w)最大为 600 像素的图片,在代码的开始处对 x-oss-process=style/xxxx 进行解析,并在后面根据解析出来的样式进行处理,如果为需要处理的样式,则根据样式规则进行转换(为 imagesweserv 支持的图片处理参数);如果不是样式或无法解析的,则直接将请求url转发至 imagesweserv 进行处理。

简单测试验证

首先通过 minio 控制台上传一张图片到 bucket kyc 的根目录 public 中,其中 public 需要设置为公共访问权限。

设置访问策略请参考前一篇文章《为 MinIO 设置 Nginx 代理以及访问策略的设置》

  • 下面是通过携带 oss style 的 x-oss-process 参数的示例截图:
  • 下图为携带的 weserv.images 的图片处理参数示例:

如果查看 nginx 日志也可以看到通过 image.lua 直接转发给了 imagesweserv

示例请求的完整 url 为:
http://192.168.1.77/kyc/public/2019-10-16_17-14-46.jpg?&w=500&h=500&fit=cover&mask=circle&mtrim&mbg=00f

更多的参数说明至官方参考文档中查看具体支持的图片处理参数

Welcome to my other publishing channels