Python 在S3上存储静态文件,但在本地存储staticfiles.json清单

Python 在S3上存储静态文件,但在本地存储staticfiles.json清单,python,django,amazon-s3,deployment,django-storage,Python,Django,Amazon S3,Deployment,Django Storage,我有一个Django应用程序在Heroku上运行。为了存储和服务静态文件,我使用S3存储桶以及标准的DjangomanifestFileMixin。我也在用 代码: from django.contrib.staticfiles.storage import ManifestFilesMixin from storages.backends.s3boto import S3BotoStorage from pipeline.storage import PipelineMixin class

我有一个Django应用程序在Heroku上运行。为了存储和服务静态文件,我使用S3存储桶以及标准的Django
manifestFileMixin
。我也在用

代码:

from django.contrib.staticfiles.storage import ManifestFilesMixin
from storages.backends.s3boto import S3BotoStorage
from pipeline.storage import PipelineMixin

class S3PipelineManifestStorage(PipelineMixin, ManifestFilesMixin, S3BotoStorage):
    pass
安装程序可以工作,但是
staticfiles.json
manifest也存储在S3上。我认为这有两个问题:

  • 我的应用程序的存储实例必须从S3获取
    staticfiles.json
    ,而不仅仅是从本地文件系统获取。这在性能方面没有什么意义。清单文件的唯一使用者是服务器应用程序本身,因此它也可以存储在本地文件系统上,而不是远程存储

    我不确定这个问题有多重要,因为我认为(或希望)服务器应用程序在读取一次文件后会缓存该文件

  • 清单文件是在部署期间由
    collectstatic
    编写的,因此,如果服务器应用程序早期版本的任何已运行实例在部署完成和新slug接管之前从S3读取清单文件,他们可能会获取错误的静态文件——这些文件应该只用于新slug的实例

    请注意,特别是在Heroku上,新的应用程序实例可能会动态弹出,因此即使应用程序缓存清单文件,也有可能在部署新slug期间首次获取清单文件

    所描述的这个场景是特定于Heroku的,但我想其他环境也会有类似的问题

  • 显而易见的解决方案是将清单文件存储在本地文件系统上。每个slug将有自己的清单文件,性能将是最佳的,并且不会有任何如上所述的部署竞争

    有可能吗?

    不久前,我读到一本我认为很适合你的书。
    在最后一段中有以下内容:

    staticfiles.json在哪里?

    默认情况下,
    staticfiles.json
    将驻留在
    STATIC\u ROOT
    中,它是 收集所有静态文件的目录
    我们将所有静态资产托管在一个S3存储桶上,这意味着默认情况下,
    staticfiles.json
    将最终同步到S3。但是,我们希望它位于代码目录中,这样我们就可以将它打包并发送到每个应用服务器。

    因此,
    ManifestStaticFilesStorage
    将查找
    STATIC\u ROOT
    中的
    staticfiles.json
    ,以读取映射。我们有 为了覆盖此行为,我们将
    ManifestStaticFilesStorage
    子类化:

    from django.contrib.staticfiles.storage import
    ManifestStaticFilesStorage from django.conf import settings
    
    class KoganManifestStaticFilesStorage(ManifestStaticFilesStorage):
    
        def read_manifest(self):
            """
            Looks up staticfiles.json in Project directory
            """
            manifest_location = os.path.abspath(
                os.path.join(settings.PROJECT_ROOT, self.manifest_name)
            )
            try:
                with open(manifest_location) as manifest:
                    return manifest.read().decode('utf-8')
            except IOError:
                return None
    
    通过上述更改,Django静态模板标记现在将读取 来自驻留在项目根目录中的
    staticfiles.json
    的映射

    我自己没用过,所以如果有用的话请告诉我

    关于@John and的答案很好,但没有给出实现这一功能所需的完整代码:@Danra提到,要实现这一功能,您还需要将
    staticfiles.json
    保存在源文件夹中。以下是我根据上述答案创建的代码:

    import json
    import os
    from django.conf import settings
    from django.core.files.base import ContentFile
    from django.core.files.storage import FileSystemStorage
    
    from whitenoise.storage import CompressedManifestStaticFilesStorage
    # or if you don't use WhiteNoiseMiddlware:
    # from django.contrib.staticfiles.storage import ManifestStaticFilesStorage
    
    
    class LocalManifestStaticFilesStorage(CompressedManifestStaticFilesStorage):
        """
        Saves and looks up staticfiles.json in Project directory
        """
        manifest_location = os.path.abspath(settings.BASE_DIR)  # or settings.PROJECT_ROOT depending on how you've set it up in your settings file.
        manifest_storage = FileSystemStorage(location=manifest_location)
    
        def read_manifest(self):
    
            try:
                with self.manifest_storage.open(self.manifest_name) as manifest:
                    return manifest.read().decode('utf-8')
            except IOError:
                return None
    
        def save_manifest(self):
            payload = {'paths': self.hashed_files, 'version': self.manifest_version}
            if self.manifest_storage.exists(self.manifest_name):
                self.manifest_storage.delete(self.manifest_name)
            contents = json.dumps(payload).encode('utf-8')
            self.manifest_storage._save(self.manifest_name, ContentFile(contents))
    
    现在,您可以将
    LocalManifestStaticFilesStorage
    用于
    STATICFILES\u存储
    。运行
    manage.py collectstatic
    时,清单将保存到根项目文件夹中,Django在提供内容时将在那里查找清单


    如果部署有多个虚拟机,请确保仅运行一次
    collectstatic
    ,并将
    staticfiles.json
    文件复制到部署中的所有计算机,作为代码部署的一部分。这样做的好处是,即使某些计算机还没有最新的更新,它们仍然会提供正确的内容(对应于当前版本的代码),因此您可以在存在混合状态的情况下执行逐步部署。

    Django ticket解决了这个问题。票证有一个实现解决方案的拉取请求,但尚未对其进行审查。

    这很有帮助,尽管可能不完整,正如您发布的链接中有人提到的-
    save\u manifest
    可能也需要被覆盖。很高兴知道@Darna!通过自定义存储类
    StaticStorage(manifestfilemixin,S3Boto3Storage)
    将django存储与
    manifestfilemixin一起使用。此解决方案似乎有效。此配置导致我的项目中出现两个问题。首先,我遇到了
    botocore.exceptions.ClientError:调用DeleteObject操作时出错(速度减慢)(达到最大重试次数:4):降低请求速率。
    运行
    collectstatic
    命令后出错。其次,在正常负载下,我的CPU开始最大化。虽然botocore错误显然与自定义文件逻辑有关,但CPU性能峰值却很奇怪。我将它与
    ManifestFilesMixin
    一起使用,有没有一种方法可以在不访问私有保存函数的情况下实现这一点?派林理所当然地抱怨
    \u save
    调用,因为它被故意标记为private,可能会被破坏。