docker containers中的django + nginx:无法上传任何带有表单

时间:2016-09-19 13:33:35

标签: django nginx docker docker-compose gunicorn

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

这是我的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),我得到了关于它的错误。 根据broswer工具的要求:

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

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

以下是媒体投放视图:

@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或这个标准视图:

@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})

Standart模型在媒体中保存文件数据,对于我创建的受保护文件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

这段代码有点抽象,但它适用于标准部署方式。我想,卷一定是错的。

2 个答案:

答案 0 :(得分:2)

这个问题对我来说非常奇怪。

有型号:

use-kss-for-demo

如果我在这里删除class Document(Model): name = CharField() file = ImageField( upload_to='documents/%Y/%m/', default='default.doc' ) ,它就可以了!但是默认的版本在docker之外工作正常。

答案 1 :(得分:1)

所以,你似乎在某种程度上误解了音量(虽然我没有看到任何可能导致你的问题的东西)。

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

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

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