使用无服务器在 AWS Lambda 上部署 Django 项目(第 1 部分)BLUF 现在您位于 container001 的 bash shell 中

2025-06-10

使用无服务器在 AWS Lambda 上部署 Django 项目(第 1 部分)

BLUF

现在你位于 container001 的 bash shell 中

BLUF

作为上一篇探讨了关于云中 Django 最常见问题的文章的后续,现在,我想帮助您在 Amazon Web Services 上部署您的 Django 应用,并让您更加独立于其他开发人员(例如 DevOps 和 CloudOps 工程师)。实现这一点有很多方法,但我想展示其中一种,并希望您最终能够使用无服务器架构在 AWS Lambda 上部署您的 Django 应用。

我受到Daniil Bratchenko 的文章《不要让软件供应商决定您的业务流程》的启发,开始撰写这篇博客文章。

由于每个公司都有其独特性,找到适合所有业务流程的软件非常困难。因此,许多公司决定成立专门的团队,根据其特定的业务流程和需求构建软件。就我个人而言,在 AWS Lambda 上使用无服务器技术开发 Django 应用是这类情况的理想解决方案。

此外,您还可以使用此方法在项目早期阶段对项目进行原型设计。

使用这种方法有一些优点和缺点。

使用AWS Lambdas的优势

  • 成本(与 AWS EC2 相比,AWS Lambda 更便宜);
  • 运行和维护简单;
  • 可扩展性;
  • 快速部署。

缺点

  • AWS Lambda 需要一些额外的时间来运行您的应用程序;
  • 部署包的大小限制;
  • API 网关限制(30 秒超时,6 Mb 响应主体大小);
  • 如果请求太多,其成本可能会比 AWS EC2 更高。

准备 AWS 基础设施

您可能已经了解 Web 应用程序所需的各种 AWS 服务。为了在 AWS Lambdas 上部署 Django 项目,您需要准备好 AWS 基础设施。
以下是我在 Django 项目中使用的 AWS 服务列表:

  1. Lambdas 运行我们的 wsgi 应用程序
  2. API 网关处理 HTTP 请求并将其发送到 Lambdas
  3. 用于 Lambda 部署和存储静态文件的 S3 存储桶
  4. CloudFront 分发,用于从 S3 存储桶提供静态文件
  5. RDS 数据库(我使用 Postgres)
  6. 带有子网的 VPC
  7. EC2 安全组
  8. 角色和策略的 IAM
  9. 用于日志的 CloudWatch

AWS Lambdas 和 API 网关将由 Serverless 自动创建。我将在后续博客文章中尝试引导您完成创建所有必要 AWS 资源的过程。

创建 Django 项目

Djangostartproject命令允许我们创建一个简单的 Django 项目,除此之外,还有一些很棒的 Cookiecutter 项目可以帮助你轻松启动项目(例如Cookiecutter Django)。在此示例中,我使用默认的django-admin startprojectcli 命令。

pip install django
django-admin startproject django_aws_lambda
Enter fullscreen mode Exit fullscreen mode

配置要求

有很多选项可以存储你的项目需求,例如requirements.txt,,Pipfilepyproject.toml你可以选择其中一个选项。我requirements.txt在这里使用。

  • requirements.txt在项目的根目录中创建文件
  • 将以下库添加到requirements.txt文件:
boto3==1.17.17
Collectfast==2.2.0
Django==3.1.7
django-environ==0.4.5
psycopg2-binary==2.8.6
Werkzeug==1.0.1
Enter fullscreen mode Exit fullscreen mode
  • 创建并激活虚拟环境

选择您喜欢的工具来管理虚拟环境(如 conda、pyenv、virtualenv 等)

  • 安装要求
pip install -r requirements.txt
Enter fullscreen mode Exit fullscreen mode

创建helloDjango 应用

  • startapp使用Django 命令创建应用程序
python manage.py startapp hello
Enter fullscreen mode Exit fullscreen mode
  • 创建templates文件夹
mkdir templates
Enter fullscreen mode Exit fullscreen mode
  • 使用以下行在文件夹中创建index.html文件:templates
{% load static %}

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Greeting</title>
</head>
<body>
<div>
  <h1>Hello {{ name }}</h1>
<img src="{% static 'django.jpeg' %}" alt="Django" style="width: 20%">
</div>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode
  • static在项目根目录中创建文件夹
mkdir static
Enter fullscreen mode Exit fullscreen mode
  • static例如将图像文件添加到文件夹django.jpeg

  • 更新hello/views.py

from django.shortcuts import render


# Create your views here.
def hello(request, resource=None):

    return render(request, "index.html", {"name": resource or 'World'})

Enter fullscreen mode Exit fullscreen mode

配置环境变量:

  • .env在项目根目录中创建文件
  • 配置以下变量:
STAGE='production'
DB_HOST=<your database host>
DB_USER=<your database user name>
DB_PASSWORD=<your database password>
DB_NAME=<your database name>
DJANGO_SECRET_KEY=<some django secret key>
AWS_S3_CDN_DOMAIN=<your Cloud Front distribution, like: `<distribution id>.cloudfront.net`>
AWS_S3_REGION_NAME=<your AWS region>
AWS_STORAGE_BUCKET_NAME=<AWS s3 bucket for static files with punlic policies>
DEPLOYMENT_BUCKET=<AWS s3 bucket for deployment>
AWS_KEY_ID=<your AWS Key Id>
AWS_SECRET=<your AWS Secret>
DJANGO_ADMIN_URL=<Django admin url>
DJANGO_ALLOWED_HOSTS=<list of allowed hosts separated by coma>
Enter fullscreen mode Exit fullscreen mode

为本地开发和生产创建配置

  • 使用以下行在文件夹settings.py更新:django_aws_lambda
"""
Django settings for django_aws_lambda project.

Generated by 'django-admin startproject' using Django 1.11.29.

For more information on this file, see
https://docs.djangoproject.com/en/1.11/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/1.11/ref/settings/
"""

from pathlib import Path

import environ

ROOT_DIR = Path(__file__).resolve(strict=True).parent.parent
env = environ.Env()
READ_DOT_ENV_FILE = env.bool('DJANGO_READ_DOT_ENV_FILE', default=True)
if READ_DOT_ENV_FILE:
    env.read_env(str(ROOT_DIR / '.env'))

# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = env('DJANGO_SECRET_KEY', default='<some-secured-key>')
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = False

ALLOWED_HOSTS = env.list('DJANGO_ALLOWED_HOSTS', default=['127.0.0.1', 'localhost'])
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'hello',
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

ROOT_URLCONF = 'django_aws_lambda.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [
            str(ROOT_DIR / 'templates'),
            str(ROOT_DIR / 'staticfiles'),
        ],
        'OPTIONS': {
            'loaders': [
                'django.template.loaders.filesystem.Loader',
                'django.template.loaders.app_directories.Loader',
            ],
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.template.context_processors.i18n',
                'django.template.context_processors.media',
                'django.template.context_processors.static',
                'django.template.context_processors.tz',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

WSGI_APPLICATION = 'django_aws_lambda.wsgi.application'


# Database
# https://docs.djangoproject.com/en/3.0/ref/settings/#databases

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': ROOT_DIR / "db.sqlite3",
    }
}


# Password validation
# https://docs.djangoproject.com/en/3.0/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]


# Internationalization
# https://docs.djangoproject.com/en/1.11/topics/i18n/

LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'UTC'

USE_I18N = True

USE_L10N = True

USE_TZ = True


# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.11/howto/static-files/

STATIC_ROOT = str(ROOT_DIR / 'staticfiles')
STATIC_URL = '/static/'

STATICFILES_DIRS = [str(ROOT_DIR / 'static')]
STATICFILES_FINDERS = [
    'django.contrib.staticfiles.finders.FileSystemFinder',
    'django.contrib.staticfiles.finders.AppDirectoriesFinder',
]
MEDIA_ROOT = str(ROOT_DIR / 'media')
MEDIA_URL = '/media/'
ADMIN_URL = env('DJANGO_ADMIN_URL')
Enter fullscreen mode Exit fullscreen mode
  • 在与以下文件夹同级的文件夹中创建local.pyproduction.py文件django_aws_lambdasettings.py
  • 添加以下几行到local.py
from .settings import *  # noqa


DEBUG = True

Enter fullscreen mode Exit fullscreen mode
  • 添加以下几行到production.py
from .settings import *  # noqa

DEBUG = False
DATABASES["default"] = {
    'ENGINE': 'django.db.backends.postgresql',
    'NAME': env("DB_NAME"),
    'USER': env("DB_USER"),
    'PASSWORD': env("DB_PASSWORD"),
    'HOST': env("DB_HOST"),
    'PORT': '5432',
}
DATABASES["default"]["ATOMIC_REQUESTS"] = True  # noqa F405
DATABASES["default"]["CONN_MAX_AGE"] = env.int("CONN_MAX_AGE", default=60)  # noqa F405
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
SECURE_SSL_REDIRECT = env.bool("DJANGO_SECURE_SSL_REDIRECT", default=False)
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SECURE_HSTS_SECONDS = 60
SECURE_HSTS_INCLUDE_SUBDOMAINS = env.bool("DJANGO_SECURE_HSTS_INCLUDE_SUBDOMAINS", default=True)
SECURE_HSTS_PRELOAD = env.bool("DJANGO_SECURE_HSTS_PRELOAD", default=True)
SECURE_CONTENT_TYPE_NOSNIFF = env.bool("DJANGO_SECURE_CONTENT_TYPE_NOSNIFF", default=True)
INSTALLED_APPS += ["storages"]  # noqa F405
AWS_KEY = env("AWS_KEY_ID")
AWS_SECRET = env("AWS_SECRET")
AWS_STORAGE_BUCKET_NAME = env("AWS_STORAGE_BUCKET_NAME")
AWS_QUERYSTRING_AUTH = False
_AWS_EXPIRY = 60 * 60 * 24 * 7
AWS_S3_OBJECT_PARAMETERS = {"CacheControl": f"max-age={_AWS_EXPIRY}, s-maxage={_AWS_EXPIRY}, must-revalidate"}
AWS_S3_REGION_NAME = env("AWS_S3_REGION_NAME", default=None)
AWS_S3_CUSTOM_DOMAIN = env("DJANGO_AWS_S3_CUSTOM_DOMAIN", default=None)
aws_s3_domain = AWS_S3_CUSTOM_DOMAIN or f"{AWS_STORAGE_BUCKET_NAME}.s3.amazonaws.com"
STATICFILES_STORAGE = "django_aws_lambda.utils.StaticRootS3Boto3Storage"
COLLECTFAST_STRATEGY = "collectfast.strategies.boto3.Boto3Strategy"
STATIC_URL = f"https://{aws_s3_domain}/static/"
DEFAULT_FILE_STORAGE = "django_aws_lambda.utils.MediaRootS3Boto3Storage"
MEDIA_URL = f"https://{aws_s3_domain}/media/"
MEDIAFILES_LOCATION = "/media"

STATICFILES_LOCATION = "/static"
TEMPLATES[-1]["OPTIONS"]["loaders"] = [  # type: ignore[index] # noqa F405
    (
        "django.template.loaders.cached.Loader",
        [
            "django.template.loaders.filesystem.Loader",
            "django.template.loaders.app_directories.Loader",
        ],
    )
]
Enter fullscreen mode Exit fullscreen mode
  • 使用以下行更新文件夹wsgi.py中的文件:django_aws_lambda
"""
WSGI config for django_aws_lambda project.

It exposes the WSGI callable as a module-level variable named ``application``.

For more information on this file, see
https://docs.djangoproject.com/en/1.11/howto/deployment/wsgi/
"""

"""
WSGI config for django_aws_lambda project.

It exposes the WSGI callable as a module-level variable named ``application``.

For more information on this file, see
https://docs.djangoproject.com/en/3.0/howto/deployment/wsgi/
"""

import os

from django.core.wsgi import get_wsgi_application

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'django_aws_lambda.production')

application = get_wsgi_application()

Enter fullscreen mode Exit fullscreen mode
  • 使用以下行更新文件夹urls.py中的文件:django_aws_lambda
"""django_aws_lambda URL Configuration

The `urlpatterns` list routes URLs to views. For more information please see:
    https://docs.djangoproject.com/en/3.0/topics/http/urls/
Examples:
Function views
    1. Add an import:  from my_app import views
    2. Add a URL to urlpatterns:  path('', views.home, name='home')
Class-based views
    1. Add an import:  from other_app.views import Home
    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
Including another URLconf
    1. Import the include() function: from django.urls import include, path
    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path

from hello.views import hello

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', hello),
    path('<path:resource>', hello),
]

Enter fullscreen mode Exit fullscreen mode
  • manage.py使用以下几行进行更新:
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys


def main():
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'django_aws_lambda.production')
    try:
        from django.core.management import execute_from_command_line
    except ImportError as exc:
        raise ImportError(
            "Couldn't import Django. Are you sure it's installed and "
            "available on your PYTHONPATH environment variable? Did you "
            "forget to activate a virtual environment?"
        ) from exc
    execute_from_command_line(sys.argv)


if __name__ == '__main__':
    main()

Enter fullscreen mode Exit fullscreen mode
  • utils在里面创建一个文件夹django_aws_lambda
  • 使用以下行在文件夹内创建storages.py文件:utils
from storages.backends.s3boto3 import S3Boto3Storage


class StaticRootS3Boto3Storage(S3Boto3Storage):
    location = "static"
    default_acl = "public-read"


class MediaRootS3Boto3Storage(S3Boto3Storage):
    location = "media"
    file_overwrite = False
Enter fullscreen mode Exit fullscreen mode

在本地运行 Django 项目

  • 使用 Django 本地配置文件的路径设置环境变量
export DJANGO_SETTINGS_MODULE=django_aws_lambda.local
Enter fullscreen mode Exit fullscreen mode
  • 迁移数据库更改
python manage.py migrate
Enter fullscreen mode Exit fullscreen mode
  • 在数据库中创建超级用户
python manage.py createsuperuser
Enter fullscreen mode Exit fullscreen mode

然后提供用户名、用户电子邮件、密码并确认密码

  • 收集静态文件
python manage.py collectstatic
Enter fullscreen mode Exit fullscreen mode
  • 本地运行服务器
python manage.py runserver
Enter fullscreen mode Exit fullscreen mode

创建无服务器配置

  • 初始化 npm:
npm init
Enter fullscreen mode Exit fullscreen mode
  • 安装无服务器
npm install -g serverless
Enter fullscreen mode Exit fullscreen mode
  • 安装无服务器插件
npm install -P serverless-dotenv-plugin
npm install -P serverless-prune-plugin
npm install -P serverless-python-requirements
npm install -P serverless-wsgi
Enter fullscreen mode Exit fullscreen mode
  • 使用以下配置创建 serverless.yaml 文件:
service: django-aws-lambda

plugins:
  - serverless-dotenv-plugin
  - serverless-prune-plugin
  - serverless-python-requirements
  - serverless-wsgi
useDotenv: true

custom:
  dotenv:
    logging: false
  pythonRequirements:
    dockerizePip: non-linux
    zip: true
    fileName: requirements.txt
  stage: ${env:STAGE}
  wsgi:
    app: django_aws_lambda.wsgi.application
    packRequirements: false
  prune:
    automatic: true
    number: 3

functions:
  - app:
      handler: wsgi_handler.handler
      events:
        - http: ANY /
        - http: ANY /{proxy+}
      timeout: 30

provider:
  name: aws
  role: arn:aws:iam::<role_id>:role/<role_name>
  profile: <your-profile-name>  # make sure that you configured aws profile using `aws configure --profile <your-profile-name>`
  region: us-east-1
  runtime: python3.8
  versionFunctions: false
  stage: ${env:STAGE}
  timeout: 60
  vpc:
    securityGroupIds:
      - <your-security-group-id>
      - <your-security-group-id>
    subnetIds:
      - <your-subnet-id>
      - <your-subnet-id>
  deploymentBucket:
    name: ${env:DEPLOYMENT_BUCKET}
  apiGateway:
    shouldStartNameWithService: true
  lambdaHashingVersion: 20201221

package:
  individually:
    true
  exclude:
    - .env
    - .git/**
    - .github/**
    - .serverless/**
    - static/**
    - .cache/**
    - .pytest_cache/**
    - node_modules/**
Enter fullscreen mode Exit fullscreen mode

使用 Docker 通过无服务器将您的 Django 项目部署到 AWS Lambda

  • 运行 Amazon Linux 2 docker 镜像:
docker run -it -v $(pwd):/root/src/ -v /Users/<your_user>/.aws:/root/.aws amazonlinux:latest bash
Enter fullscreen mode Exit fullscreen mode
  • 安装必要的 Unix 依赖项:
yum install sudo -y
sudo yum install -y gcc openssl-devel bzip2-devel libffi-devel wget tar sqlite-devel gcc-c++ make
Enter fullscreen mode Exit fullscreen mode
  • 安装 node.js 版本 14:
curl -sL https://rpm.nodesource.com/setup_14.x | sudo -E bash - 
sudo yum install -y nodejs
Enter fullscreen mode Exit fullscreen mode
  • 安装 Python 3.8.7:
cd /opt
sudo wget https://www.python.org/ftp/python/3.8.7/Python-3.8.7.tgz
sudo tar xzf Python-3.8.7.tgz
cd Python-3.8.7
sudo ./configure --enable-optimizations
sudo make altinstall
sudo rm -f /opt/Python-3.8.7.tgz
Enter fullscreen mode Exit fullscreen mode
  • 创建 python 和 pip 别名:
alias python='python3.8'
alias pip='pip3.8'
Enter fullscreen mode Exit fullscreen mode
  • 更新 pip 和 setuptools:
pip install --upgrade pip setuptools
Enter fullscreen mode Exit fullscreen mode
  • 安装无服务器:
npm install -g serverless
Enter fullscreen mode Exit fullscreen mode
  • 移动到项目目录
cd /root/src/
Enter fullscreen mode Exit fullscreen mode
  • 在 Docker 容器内安装要求:
pip install -r requirements.txt
Enter fullscreen mode Exit fullscreen mode
  • 使用 django 生产配置文件的路径设置环境变量
export DJANGO_SETTINGS_MODULE=django_aws_lambda.production
Enter fullscreen mode Exit fullscreen mode
  • 迁移数据库更改
python manage.py migrate
Enter fullscreen mode Exit fullscreen mode
  • 在数据库中创建超级用户
python manage.py createsuperuser
Enter fullscreen mode Exit fullscreen mode

然后提供用户名、用户电子邮件、密码并确认密码

  • 将静态文件收集到 AWS S3 存储桶
python manage.py collectstatic
Enter fullscreen mode Exit fullscreen mode

如果你NoCredentialsError从中获取botocore,则应添加到环境变量中AWS_PROFILE

export AWS_PROFILE=<your-aws-profile-name>
Enter fullscreen mode Exit fullscreen mode
  • 从 package.json 安装无服务器包
npm install
Enter fullscreen mode Exit fullscreen mode
  • 使用无服务器将您的 Django 项目部署到 AWS Lambda
serverless deploy -s production
Enter fullscreen mode Exit fullscreen mode

您的回复将如下所示:

Serverless: Adding Python requirements helper to ....
Serverless: Generated requirements from /root/src/requirements.txt in /root/src/.serverless/requirements.txt...
Serverless: Installing requirements from /root/.cache/serverless-python-requirements/ ...
Serverless: Using download cache directory /root/.cache/serverless-python-requirements/downloadCacheslspyc
Serverless: Running ...
Serverless: Zipping required Python packages for ....
Serverless: Using Python specified in "runtime": python3.8
Serverless: Packaging Python WSGI handler...
Serverless: Packaging service...
Serverless: Excluding development dependencies...
Serverless: Removing Python requirements helper from ....
Serverless: Injecting required Python packages to package...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading artifacts...
Serverless: Uploading service app.zip file to S3 (60.48 MB)...
Serverless: Validating template...
Serverless: Updating Stack...
Serverless: Checking Stack update progress...
..........
Serverless: Stack update finished...
Service Information
service: <your-serverless-service-name>
stage: production
region: <your-aws-region>
stack: <your-serverless-service-name>-pronduction
resources: 8
api keys:
  None
endpoints:
  ANY - https://<some-id>.execute-api.<your-aws-region>.amazonaws.com/production
  ANY - https://<some-id>.execute-api.<your-aws-region>.amazonaws.com/production/{proxy+}
functions:
  app: <your-serverless-service-name>-production-app
layers:
  None
Serverless: Prune: Running post-deployment pruning
Serverless: Prune: Querying for deployed function versions
Serverless: Prune: <your-serverless-service-name>-production-app has 3 additional versions published and 0 aliases, 0 versions selected for deletion
Serverless: Prune: Pruning complete.
Serverless: Removing old service artifacts from S3...

**************************************************************************************************************************************
Serverless: Announcing Metrics, CI/CD, Secrets and more built into Serverless Framework. Run "serverless login" to activate for free..
**************************************************************************************************************************************
Enter fullscreen mode Exit fullscreen mode

现在,您的 Django 项目将通过以下 URL 访问:
https://<some-id>.execute-api.<your-aws-region>.amazonaws.com/production
图像

恭喜!

这是包含本博文中显示的代码的GitHub 存储库的链接。

如果您想了解有关 AWS Lambdas 上的 Django 项目的更多信息,请在 Twitter 上关注我(@vadim_khodak),我计划撰写一篇文章,展示如何为这个 Django 项目创建所有必要的 AWS 资源,如何将 React.js 客户端添加到 Django 项目等等。

鏂囩珷鏉ユ簮锛�https://dev.to/vaddimart/deploy-django-app-on-aws-lambda-using-serverless-part-1-1i90
PREV
每个人都需要使用这个 API!!
NEXT
仅需 1.5KB 即可制作背景 Blob 动画:分步指南