3

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

 3 years ago
source link: https://stevenocean.github.io/2021/01/29/minio-image-serve.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

在使用阿里云 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 中看到。

示例部署架构

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

3MvQ73e.jpg!mobile

这里的示例是通过 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 源码文件,更改如下:

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 镜像,之后再启动相应的容器:

$ 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

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 处理脚本:

-- 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 参数的示例截图:
2YBR3ab.jpg!mobile
  • 下图为携带的 weserv.images 的图片处理参数示例:
ZFN7fmM.jpg!mobile

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK