使用Nginx来处理Docker容器中Flask app的静态资源 - All About Free

当我们使用docker来部署flask app的时候, 通常会使用uwsgi来启动app, 并且开放端口(or unix socket)给nginx做proxy_pass转发

这样的方案简单粗暴, 同时也会带来一些问题, 就是会使用uwsgi本身来分发app中的静态资源, 但在伴旅的生产环境中已经能明显感觉到uwsgi的效率问题, 那么就来想办法解决吧!

解决的方法毋庸置疑就是使用nginx来直接处理static相关的请求, 如果排除掉docker, 只需要在nginx的配置文件中新增诸如下面的几行

location /static {
    alias /static;
}

但是在docker中, flask app的static目录和nginx的环境是相对隔离的, 如果不修改当前的已经运行的container, 很难让nginx可以直接访问到静态资源目录.

但是好在我们的nginx还是支持cache的, 于是就有了一种相对简单有效的方案.

使用Nginx缓存静态资源

要开启nginx的缓存机制, 可以随意谷歌就能找到非常完美的文档, 所以我们只需要修改nginx的conf, 新增如下代码

proxy_cache_path /tmp/cache levels=1:2 keys_zone=cache:30m max_size=1G;

upstream app_upstream {
  server app:5000;
}

# 修改server block
location /static {
    proxy_cache cache;
    proxy_cache_valid 30m;
    proxy_pass  http://app_upstream;
}

这个方案有着很强的扩展性, 可以有效的支援多个flask app, 但是唯一的缺陷就是静态资源的刷新需要等待30分钟, 或者手动重启nginx实例.

所以我们还有一套相对复杂的方案, 也算是有效利用了docker中volume的概念.

使用Volume来让Nginx直接解析静态资源

这种方式可以避免cache刷新的时间, 同时在我目前的项目架构上更适合, 并且已经上线到了production环境, 效果有明显提升.

目前我们有多个(>10)flask app, 通过docker container部署在服务器上, 每个app使用uwsgi启动, 并且开放socket端口给host本地

同时还通过docker部署了MySQL, Redis和Nginx-Proxy

最初每个flask app的content都保存在container中, 这也就导致nginx并不能获取到每个flask app的static文件中的内容, 于是对docker container做了一些修改, 包括

  • 在host中建立一个app_content聚合的folder
  • 每个flask app都在app_content中存在一个独立的folder
  • 将folder自动以Volume的形式挂接到container内
  • 将app_content以只读Volume的形式挂接到nginx container内

这样一来, 就可以通过修改nginx-proxy的tmplate来实现static的加速了

大概的template段如下

{{ range $host, $containers := groupByMulti $ "Env.VIRTUAL_HOST" "," }}

upstream {{ $host }} {
{{ range $container := $containers }}
    {{ $addrLen := len $container.Addresses }}
    {{/* If only 1 port exposed, use that */}}
    {{ if eq $addrLen 1 }}
        {{ with $address := index $container.Addresses 0 }}
           # {{$container.Name}}
           server {{ $address.IP }}:{{ $address.Port }};
        {{ end }}
    {{/* If more than one port exposed, use the one matching VIRTUAL_PORT env var */}}
    {{ else if $container.Env.VIRTUAL_PORT }}
        {{ range $address := .Addresses }}
           {{ if eq $address.Port $container.Env.VIRTUAL_PORT }}
           # {{$container.Name}}
           server {{ $address.IP }}:{{ $address.Port }};
           {{ end }}
        {{ end }}
    {{/* Else default to standard web port 80 */}}
    {{ else }}
        {{ range $address := $container.Addresses }}
            {{ if eq $address.Port "80" }}
            # {{$container.Name}}
            server {{ $address.IP }}:{{ $address.Port }};
            {{ end }}
        {{ end }}
    {{ end }}
{{ end }}
}

server {
    server_name {{ $host }};
    # client_max_body_size 50M;

    {{ if (exists (printf "/etc/nginx/vhost.d/%s" $host)) }}
    include {{ printf "/etc/nginx/vhost.d/%s" $host }};
    {{ end }}

    location / {
        uwsgi_pass {{ $host }};
        include uwsgi_params;
        {{ if (exists (printf "/etc/nginx/htpasswd/%s" $host)) }}
        auth_basic  "Restricted {{ $host }}";
        auth_basic_user_file    {{ (printf "/etc/nginx/htpasswd/%s" $host) }};
        {{ end }}
    }
    location ^~ /static/  {
        {{ $host_array := split $host "."}}
        {{ $loc := first $host_array}}
        {{ $suf := "app-dev.xyzabc.com"}}
        {{ if hasSuffix $suf $host}}
            root /app_contents/volumes/dev/{{$loc}}/app/;
        {{ end }}
        {{ $suf := "app.xyzabc.com"}}
        {{ if hasSuffix $suf $host}}
            root /app_contents/volumes/production/{{$loc}}/app/;
        {{ end }}
        {{ $suf := "app-staging.xyzabc.com"}}
        {{ if hasSuffix $suf $host}}
            root /app_contents/volumes/staging/{{$loc}}/app/;
        {{ end }}
    }
}

{{ end }}

思考

最初的最初对于uwsgi的性能理解过于乐观, 在部署到production环境后发现uwsgi经常会在处理static内容的时候阻塞, 并且导致其他restful接口效率降低. 最初并没有怀疑效率是由于uwsgi引起的, 直到在本地完全搭建了一套环境后才发现即便在本地加载静态文件时也会有明显的延迟.

从目前production环境的效果看, 使用nginx来处理static资源是一个比较正确的选择, 但是我依然还是对uwsgi的效率存有执念的, 在uwsgi的doc中发现了一篇讲解优化uwsgi静态资源处理能力的文章(点这里查看), 其中提到了不少之前没有注意到的参数和方法, 之后可以再次试试, 看看能否解开uwsgi的能力封印.

Free /
Published under (CC) BY-NC-SA in categories technology