docker 容器中的 django + nginx : can not upload any file with forms

标签 django nginx docker docker-compose gunicorn

我正在尝试将我的应用程序迁移到 docker 容器,但在使用 django 表单上传文件时遇到了问题。一切正常,但是当我尝试从我的应用程序中的表单上传任何文件时,我什么也没得到,比如 multipart/form-data 未在 html 表单标签中设置。但是如果我直接安装没有 docker 的应用程序,它就会设置并且一切正常。无论如何,这是我的配置,我希望有人能帮助我。

这是我的 docker-compose.yml

version: '2' 
services:
    db_postgres:
        build:
            context: .
            dockerfile: dockerfiles/docker-postgres/Dockerfile
            args:
            - db_user=username
            - db_name=databasename
            - db_pass=password
        environment:
            LC_ALL: C.UTF-8

    app:
        restart: always
        build:
            context: .
            dockerfile: dockerfiles/docker-app/Dockerfile
        links:
            - db_postgres:db_postgres

    nginx:
        restart: always
        build: dockerfiles/docker-nginx
        volumes_from:
            - app
        ports:
            - "80:80"
            - "443:443"
        links:
            - app:applink

这是应用程序 Dockerfile:

FROM ubuntu:16.04

RUN \
  apt-get update && \
  apt-get install -y python-pip python-dev build-essential python-virtualenv && \
  apt-get install -y libjpeg8 libjpeg62-dev libfreetype6 libfreetype6-dev && \
  apt-get install -y libpq-dev libffi-dev && \
  apt-get install -y libssl-dev git 

RUN mkdir app
COPY requrements.txt /app
RUN pip install --upgrade pip
RUN pip install -r /app/requrements.txt
ADD . /app
WORKDIR /app
VOLUME ["/app/staticfiles/", "/app/media/", "/app/protected/"]
# I tried this but it seams no effect at all
# this is media folders where users can upload their files
# nginx can read from this folders with no problem
# I tried to docker exec and nginx can even write there
# anyways I tried to start nginx as root
RUN chown www-data:www-data -R /app/media/
RUN chown www-data:www-data -R /app/protected/    

ADD dockerfiles/docker-app/django_entrypoint.sh .
RUN chmod +x django_entrypoint.sh  
CMD ["./django_entrypoint.sh"]
EXPOSE 8000

这里是django_entrypoint.sh

#!/bin/bash
NAME=my_app_name
USER=www-data
GROUP=www-data
NUM_WORKERS=8
DJANGO_WSGI_MODULE=my_application.wsgi

python manage.py makemigrations
python manage.py migrate
echo "Collenting staticfiles..."
python manage.py collectstatic --noinput > /dev/null
python manage.py initadmin
python manage.py init_default_settings
exec gunicorn ${DJANGO_WSGI_MODULE}:application \
  --name $NAME \
  --workers $NUM_WORKERS \
  --user=$USER --group=$GROUP \
  --bind=:8000 \
  --log-level=debug \
  --capture-output

这里是 nginx Dockerfile:

FROM ubuntu:16.04
# Install Nginx.
RUN apt-get update && \
  apt-get install -y nginx && \
  rm -rf /var/lib/apt/lists/* && \
  echo "\ndaemon off;" >> /etc/nginx/nginx.conf && \
  chown -R www-data:www-data /var/lib/nginx

# Define mountable directories.
VOLUME ["/etc/nginx/sites-available", "/etc/nginx/certs", "/etc/nginx/conf.d"]
ADD confgfile /etc/nginx/sites-available/
RUN rm /etc/nginx/sites-enabled/default && rm /etc/nginx/sites-available/default
RUN ln -s /etc/nginx/sites-available/ctrd /etc/nginx/sites-enabled/ctrd
# Define default command.
CMD ["nginx"]
# Expose ports.
EXPOSE 80
EXPOSE 443

这是 nginx 的配置文件:

server {
    listen 80;
    # I pasted my server ip in sever name
    server_name 175.116.110.231;
    client_max_body_size 300M;

    error_log stdout debug;
    location /static/ {
        alias   /app/staticfiles/;
    }
    location /media/ {
        alias   /app/media/;
    }
    location / {
        try_files $uri @proxy;
    }

    location @proxy {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-FILE $request_body_file;
        proxy_set_header Host $http_host;
        proxy_redirect off;
        proxy_pass http://applink:8000;
    }

    location /private-uploads/ {
        internal;
        alias /app/protected/records/;
    }

}

该应用程序运行良好,但当我想上传任何文件时它什么也没做,没有文件,错误日志中也没有错误。如果 filefield 不能为空(在 django 形式中 requred=True),我得到了关于那个的错误。 在浏览器工具中请求:

Request URL:http://175.116.110.231/edit-avatar/2
Request Method:POST
Status Code:302 Found
Remote Address:175.116.110.231:80
Response Headers
view source
Connection:keep-alive
Content-Language:ru
Content-Type:text/html; charset=utf-8
Date:Mon, 19 Sep 2016 13:26:59 GMT
Location:/profile/
Server:nginx/1.10.0 (Ubuntu)
Transfer-Encoding:chunked
Vary:Accept-Language, Cookie
X-Frame-Options:SAMEORIGIN
Request Headers
view source
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Encoding:gzip, deflate
Accept-Language:ru-RU,ru;q=0.8,en-US;q=0.6,en;q=0.4
Cache-Control:max-age=0
Connection:keep-alive
Content-Length:30316
Content-Type:multipart/form-data; boundary=----WebKitFormBoundarySCXVqlqPsCHyaHtU
Cookie:JSESSIONID=dummy; sessionid=htnbd9coal4ws2ansxzze8bhtu4fq6do; csrftoken=tCD5cVrR0IUGkjkkbJKDsxdRrtyLIUGbOIHkjHKjhkjhmnVJhvKUGkBDjZ
DNT:1
Host:175.116.110.231
Origin:http://175.116.110.231
Referer:http://175.116.110.231/edit-avatar/2
Upgrade-Insecure-Requests:1
User-Agent:Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.90 Safari/537.36 Vivaldi/1.4.589.11
Request Payload
------WebKitFormBoundarySCXVqlqPsCHyaHtU
Content-Disposition: form-data; name="csrfmiddlewaretoken"

zaAvb1KoCCovbuCbik261UDeZgQbJdumzcvpcqOHqTKIrRN826lEoeb5AvU7SrG6
------WebKitFormBoundarySCXVqlqPsCHyaHtU
Content-Disposition: form-data; name="avatar"; filename="ava2.jpg"
Content-Type: image/jpeg


------WebKitFormBoundarySCXVqlqPsCHyaHtU--

在启动时更新 gunicorn 调试输出:

ctrd_app_1      | [2016-09-19 16:33:47 +0000] [1] [DEBUG] Current configuration:
ctrd_app_1      |   secure_scheme_headers: {'X-FORWARDED-PROTOCOL': 'ssl', 'X-FORWARDED-PROTO': 'https', 'X-FORWARDED-SSL': 'on'}
ctrd_app_1      |   proxy_protocol: False
ctrd_app_1      |   worker_connections: 1000
ctrd_app_1      |   statsd_host: None
ctrd_app_1      |   max_requests_jitter: 0
ctrd_app_1      |   post_fork: <function post_fork at 0x7fb7e74ee230>
ctrd_app_1      |   pythonpath: None
ctrd_app_1      |   enable_stdio_inheritance: False
ctrd_app_1      |   worker_class: sync
ctrd_app_1      |   ssl_version: 3
ctrd_app_1      |   suppress_ragged_eofs: True
ctrd_app_1      |   syslog: False
ctrd_app_1      |   syslog_facility: user
ctrd_app_1      |   when_ready: <function when_ready at 0x7fb7e74e5ed8>
ctrd_app_1      |   pre_fork: <function pre_fork at 0x7fb7e74ee0c8>
ctrd_app_1      |   cert_reqs: 0
ctrd_app_1      |   preload_app: False
ctrd_app_1      |   keepalive: 2
ctrd_app_1      |   accesslog: None
ctrd_app_1      |   group: 33
ctrd_app_1      |   graceful_timeout: 30
ctrd_app_1      |   do_handshake_on_connect: False
ctrd_app_1      |   spew: False
ctrd_app_1      |   workers: 8
ctrd_app_1      |   proc_name: django_ctrd_app
ctrd_app_1      |   sendfile: None
ctrd_app_1      |   pidfile: None
ctrd_app_1      |   umask: 0
ctrd_app_1      |   on_reload: <function on_reload at 0x7fb7e74e5d70>
ctrd_app_1      |   pre_exec: <function pre_exec at 0x7fb7e74ee7d0>
ctrd_app_1      |   worker_tmp_dir: None
ctrd_app_1      |   post_worker_init: <function post_worker_init at 0x7fb7e74ee398>
ctrd_app_1      |   limit_request_fields: 100
ctrd_app_1      |   on_exit: <function on_exit at 0x7fb7e74eee60>
ctrd_app_1      |   config: None
ctrd_app_1      |   logconfig: None
ctrd_app_1      |   check_config: False
ctrd_app_1      |   statsd_prefix: 
ctrd_app_1      |   proxy_allow_ips: ['127.0.0.1']
ctrd_app_1      |   pre_request: <function pre_request at 0x7fb7e74ee938>
ctrd_app_1      |   post_request: <function post_request at 0x7fb7e74eea28>
ctrd_app_1      |   user: 33
ctrd_app_1      |   forwarded_allow_ips: ['127.0.0.1']
ctrd_app_1      |   worker_int: <function worker_int at 0x7fb7e74ee500>
ctrd_app_1      |   threads: 1
ctrd_app_1      |   max_requests: 0
ctrd_app_1      |   limit_request_line: 4094
ctrd_app_1      |   access_log_format: %(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s"
ctrd_app_1      |   certfile: None
ctrd_app_1      |   worker_exit: <function worker_exit at 0x7fb7e74eeb90>
ctrd_app_1      |   chdir: /app
ctrd_app_1      |   paste: None
ctrd_app_1      |   default_proc_name: calltrade.wsgi:application
ctrd_app_1      |   errorlog: -
ctrd_app_1      |   loglevel: debug
ctrd_app_1      |   capture_output: True
ctrd_app_1      |   syslog_addr: udp://localhost:514
ctrd_app_1      |   syslog_prefix: None
ctrd_app_1      |   daemon: False
ctrd_app_1      |   ciphers: TLSv1
ctrd_app_1      |   on_starting: <function on_starting at 0x7fb7e74e5c08>
ctrd_app_1      |   worker_abort: <function worker_abort at 0x7fb7e74ee668>
ctrd_app_1      |   bind: [':8000']
ctrd_app_1      |   raw_env: []
ctrd_app_1      |   reload: False
ctrd_app_1      |   limit_request_field_size: 8190
ctrd_app_1      |   nworkers_changed: <function nworkers_changed at 0x7fb7e74eecf8>
ctrd_app_1      |   timeout: 30
ctrd_app_1      |   ca_certs: None
ctrd_app_1      |   django_settings: None
ctrd_app_1      |   tmp_upload_dir: None
ctrd_app_1      |   keyfile: None
ctrd_app_1      |   backlog: 2048
ctrd_app_1      |   logger_class: gunicorn.glogging.Logger
ctrd_app_1      | [2016-09-19 16:33:47 +0000] [1] [INFO] Starting gunicorn 19.6.0
ctrd_app_1      | [2016-09-19 16:33:47 +0000] [1] [DEBUG] Arbiter booted
ctrd_app_1      | [2016-09-19 16:33:47 +0000] [1] [INFO] Listening at: http://0.0.0.0:8000 (1)
ctrd_app_1      | [2016-09-19 16:33:47 +0000] [1] [INFO] Using worker: sync
ctrd_app_1      | [2016-09-19 16:33:47 +0000] [38] [INFO] Booting worker with pid: 38
ctrd_app_1      | [2016-09-19 16:33:47 +0000] [39] [INFO] Booting worker with pid: 39
ctrd_app_1      | [2016-09-19 16:33:47 +0000] [44] [INFO] Booting worker with pid: 44
ctrd_app_1      | [2016-09-19 16:33:47 +0000] [47] [INFO] Booting worker with pid: 47
ctrd_app_1      | [2016-09-19 16:33:47 +0000] [48] [INFO] Booting worker with pid: 48
ctrd_app_1      | [2016-09-19 16:33:47 +0000] [51] [INFO] Booting worker with pid: 51
ctrd_app_1      | [2016-09-19 16:33:47 +0000] [54] [INFO] Booting worker with pid: 54
ctrd_app_1      | [2016-09-19 16:33:48 +0000] [59] [INFO] Booting worker with pid: 59
ctrd_app_1      | [2016-09-19 16:33:48 +0000] [1] [DEBUG] 8 workers

我也在 gunicorn 容器上尝试了 8000:8000 端口暴露,它也无法保存文件,所以原因不是 nginx,但可能是我的 docker 卷配置错误。有人可以解释 docker volumes 吗?请检查我的卷配置,我一定是不明白它应该如何工作。 请帮忙。

这是媒体服务 View :

@login_required
def private_media_response(request, username, filename):
    """
    In the nginx setting we can use something like here:
    location /private-uploads/ {
        internal;
        alias /place/to/private/media/;
    }
    """
    user = request.user
    if user.username == username:
        response = HttpResponse()
        url = '/private-uploads/{0}/{1}'.format(username, filename)
        response.status_code = 200
        response['X-Accel-Redirect'] = url.encode('utf-8')
        response['X-Accel-Buffering'] = 'yes'
        return response
    else:
        return HttpResponseForbidden("Restricted Access")

问题是没有得到文件,我不能用表格保存它。 我认为保存文件看起来像模型更新 CBV 或此标准 View 的所有观点:

@login_requred
def file_save_view(request, **kwags):
    if request.POST:
        form = MyForm(request.POST, request.FILES)
        if form.is_valid()
            form.save()
    else:
        form = myForm();
    return render(request, 'some_template.html', {'form':form})

标准模型将文件数据保存在媒体中,对于我创建的 protected 文件 FileSystemStorage

location = os.path.join(settings.BASE_DIR, 'protected')
fs = FileSystemStorage(location=location)

然后在模型中使用它:

def get_upload_path(instance, filename):
    user = instance.user
    return 'documents/{0}/{1}'.format(user, filename)

class MyModel(models.Model):
    date = models.DateTimeField(auto_now=True, blank=False)
    user = models.ForeignKey(User)
    document = models.FileField(
        upload_to=get_upload_path,
        storage=fs,
    )
    pass

这段代码有点抽象,但它适用于标准部署方式。我猜卷一定有问题。

最佳答案

所以,您似乎确实在某种程度上误解了卷(尽管我没有看到任何会导致您出现问题的内容)。

卷来自 Docker 用于其图像的“写入时复制”文件系统。每个层只包含自以前使用以来未更改的新信息 - 这使您可以高效地构建图像,而不必为从该图像开始的每个容器复制大量数据。

一个卷说“不要为这个目录使用写文件系统的复制”。所以 VOLUME ["/etc/nginx/sites-available", "/etc/nginx/certs", "/etc/nginx/conf.d"] 实际上是告诉 Docker 离开这些目录 OUT你建立的形象。这不是您想要的...我看到您在将配置目录标记为卷后将配置文件添加到您的 nginx 配置中...这些文件更改不会传播到您正在构建的图像中。

关于您的实际问题 - 我不知道那是什么。所有部署配置都与“保存文件”无关。唯一可能的地方是,如果你试图从 django 中保存它,然后用 nginx 备份它......但你说文件没有被保存,这意味着 nginx 与它无关。我会向您的应用程序本身添加一些调试输出,以尝试弄清楚发生了什么。如果它返回一个 200 而没有按照它所说的去做(将一些文件保存到磁盘)...这就是我要开始的地方。

关于docker 容器中的 django + nginx : can not upload any file with forms,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39574538/

相关文章:

python - docker 内的Django服务器是否导致机器人测试显示空白屏幕?

docker - 从 Cloud Shell 启动 Datalab

python - AttributeError: 'ContactUs' 对象没有属性 'model'

django - 在基于类的 View 中设置基于条件的重定向(Django)

javascript - django:如何将模板变量传递给 javascript onclick 例程?

python - 如何修复生产中的 502 错误网关错误(Nginx)?

docker - Docker构建一个.net核心应用程序,该应用程序引用不同文件夹中的项目

Django-cms - CMS javascript 未包含在前端

node.js - 在同一 nginx 服务器上使用 SSL 的两个 node.js 域 - 443 的重复监听选项

nginx - Flask + uwsgi + nginx + 调试。 502错误而不是调试器页面