AWS上でTerraformを使用してDjangoアプリケーションをデプロイする。Amazon S3への接続

これは「AWS上でTerraformを使用してDjangoアプリケーションをデプロイする」ガイドの第6部です。以前のステップはこちらで確認できます:

このステップでは、以下を行います:

S3について

なぜ私たちは実際にS3ストレージが必要なのでしょうか?

ユーザーがウェブサーバーにファイルをアップロードすると、Djangoはデフォルトでこのファイルをファイルシステムに保存します。複数のコンテナーをウェブECSサービスで持つことができるため、web_1コンテナーがユーザーファイルを作成し、web_2コンテナーがそれらを読み取ろうとする状況が発生する可能性があります。さらに、ECSがweb_1コンテナーを再作成すると、ユーザーファイルが失われます。

このような状況では、ステートフル(状態を持つ)な振る舞いがあります。ステートレス(状態を持たない)な振る舞いを実現するには、ユーザーファイルを共通のグローバルストレージに保存する必要があります。私たちが探しているストレージがS3です。

この場合、web_1コンテナーはS3バケットに新しいファイルを作成し、web_2コンテナーはこのファイルを読み取ることができます。

AWSからのS3の説明は以下の通りです:

Amazon Simple Storage Service(Amazon S3)は、業界をリードするスケーラビリティ、データの可用性、セキュリティ、パフォーマンスを提供するオブジェクトストレージサービスです。あらゆる規模の企業や業界の顧客は、データレイク、クラウドネイティブアプリケーション、モバイルアプリのようなあらゆる使用事例に対して、任意の量のデータを保存して保護することができます。コスト効果の高いストレージクラスと使いやすい管理機能を備えることで、コストを最適化し、データを整理し、特定のビジネス、組織、コンプライアンス要件に応じた細かいアクセスコントロールを設定できます。

ローカルストレージへのファイルアップロード

しかしまず、Djangoアプリケーションでファイルのアップロードを追加しましょう。

S3で作業するためには、django-storagesboto3パッケージをインストールする必要があります。そしてDjangoで画像を扱うためには、Pillowライブラリが必要です。これらをrequirements.txtに追加してpip install -r requirements.txtを実行しましょう。

boto3==1.24.45
django_storages==1.13.1
pillow==9.1.0

しかし、今のところは、DjangoデフォルトのFileSystemStorageで作業します。

新しいDjangoアプリケーションphotosを作成し、image ImageFieldを持つPhotoモデルを作りましょう。

django-aws-backendプロジェクトに移動して、python manage.py startapp photosとして新しいDjangoアプリケーションを作成します。

その後、settings.pyINSTALLED_APPSの最後にphotosアプリケーションを追加し、MEDIA_URLMEDIA_ROOTの設定を指定します:

INSTALLED_APPS = [
    ...
    'photos.apps.PhotosConfig',
]

MEDIA_URL = "/media/"
MEDIA_ROOT = BASE_DIR / "media"

さて、Photoモデルをphotos/models.pyで作成できる準備ができました:

from django.db import models

class Photo(models.Model):
    title = models.CharField("Title", max_length=255)
    image = models.ImageField("Image", upload_to="photos/")

管理パネルでPhotoオブジェクトを管理できるようにするために、PhotoAdminクラスをphotos/admin.pyに追加します。

from django.contrib import admin
from photos.models import Photo

@admin.register(Photo)
class PhotoAdmin(admin.ModelAdmin):
    list_display = ["title"]

最後に、Django開発サーバーがメディアファイルを提供できるようにするために、django_aws/urls.pyに以下のコードを追加します:

from django_aws.settings import DEBUG, MEDIA_URL, MEDIA_ROOT
from django.conf.urls.static import static

...

if DEBUG:
    urlpatterns += static(MEDIA_URL, document_root=MEDIA_ROOT)

必要なコードはすべてそろったので、マイグレーションを作成して適用できます。

$ python manage.py makemigrations photos
Migrations for 'photos':
  photos/migrations/0001_initial.py
    - Create model Photo
$ python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, photos, sessions
Running migrations:
  Applying photos.0001_initial... OK

では、Django管理画面で新しくPhotoを作成してみましょう。python manage.py runserverを実行し、管理パネルhttp://127.0.0.1:8000/admin/photos/photo/にアクセスします。「Add Photo」をクリックし、タイトルを入力してあなたのファイルシステムから任意の画像を選んで、「Save and continue editing」をクリックします。

Djangoはイメージをローカルファイルシステムに保存します。"current image"をクリックしてブラウザで表示できることを確認し、django-aws-backend/media/photosフォルダで確認することもできます。

Django S3設定

ローカルファイルシステムに画像をアップロードすることに成功しました。私はローカル開発用にFileSystemStorageを使用するのを好みます。これにより、インターネット接続なしでも作業やテストを実行できます。

しかし、本番環境のためには、S3設定を提供する必要があります。settings.pyに以下の設定を追加しましょう:

DEFAULT_FILE_STORAGE = env(
    "DEFAULT_FILE_STORAGE", default="django.core.files.storage.FileSystemStorage"
)
AWS_ACCESS_KEY_ID = env("AWS_ACCESS_KEY_ID", default="")
AWS_SECRET_ACCESS_KEY = env("AWS_SECRET_ACCESS_KEY", default="")
AWS_STORAGE_BUCKET_NAME = env("AWS_STORAGE_BUCKET_NAME", default="")
AWS_S3_REGION_NAME = env("AWS_S3_REGION_NAME", default="")
AWS_S3_ENDPOINT_URL = env("AWS_S3_ENDPOINT_URL", default="")
AWS_S3_FILE_OVERWRITE = False

ここで定義しました:

  • DEFAULT_FILE_STORAGE。ローカル開発用にFileSystemStorageを保持し、本番用にS3Boto3Storageを設定します。
  • AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY - AWSアクセスキーを文字列として。
  • AWS_STORAGE_BUCKET_NAME - S3バケットの名前。
  • AWS_S3_REGION_NAME - AWSリージョン。私は同じus-east-2リージョンを使用しますが、あなたは自分のリージョンを指定できます。
  • AWS_S3_ENDPOINT_URL - S3に接続するためのURL。
  • AWS_S3_FILE_OVERWRITE - 指定された名前のファイルがすでに存在する場合、django-storagesは余分な文字を追加します。

Djangoの部分はこれで終わりです。アプリケーションはS3で作業する用意ができました。変更をコミットしてプッシュし、CI/CDが正常に通過したことを確認してください。

S3バケットの作成

Terraformプロジェクトに移動しましょう。S3バケットを作成しましょう。まず、variables.tfにバケット名を指定する必要があります。名前はAWSリージョン内で一意である必要があります。

# S3

variable "prod_media_bucket" {
  description = "S3 Bucket for production media files"
  default = "prod-media-427861343"
}

次に、django-aws-infrastructureプロジェクトに新しいs3.tfファイルを以下の内容で作成し、terraform applyを実行します:

resource "aws_s3_bucket" "prod_media" {
  bucket = var.prod_media_bucket
  acl    = "public-read"

  cors_rule {
    allowed_headers = ["*"]
    allowed_methods = ["GET", "HEAD"]
    allowed_origins = ["*"]
    expose_headers  = ["ETag"]
    max_age_seconds = 3000
  }

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Sid = "PublicReadGetObject"
        Principal = "*"
        Action = [
          "s3:GetObject",
        ]
        Effect   = "Allow"
        Resource = [
          "arn:aws:s3:::${var.prod_media_bucket}",
          "arn:aws:s3:::${var.prod_media_bucket}/*"
        ]
      },
    ]
  })
}


resource "aws_iam_user" "prod_media_bucket" {
  name = "prod-media-bucket"
}

resource "aws_iam_user_policy" "prod_media_bucket" {
  user = aws_iam_user.prod_media_bucket.name
  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action = [
          "s3:*",
        ]
        Effect = "Allow"
        Resource = [
          "arn:aws:s3:::${var.prod_media_bucket}",
          "arn:aws:s3:::${var.prod_media_bucket}/*"
        ]
      },
    ]
  })
}

resource "aws_iam_access_key" "prod_media_bucket" {
  user = aws_iam_user.prod_media_bucket.name
}

ここでは作成しました:

  • 新しいS3バケット。このバケットのすべてのファイルにパブリックアクセスを許可し、CORSルールを定義してブラウザが任意の元のファイル(または私たちのドメインのためのもの)を読み込むことを許可します。
  • S3バケットへの接続に使用する新しいIAMユーザーIAMポリシー
  • ユーザーがDjangoアプリケーションで使用するIAMアクセスキー

AWS S3コンソールでバケットをチェックしましょう:

ECSサービスにS3の認証情報を渡す準備ができました。ecs.tfの変数を追加します:

...
locals {
  container_vars = {
    ...

    s3_media_bucket         = var.prod_media_bucket
    s3_access_key           = aws_iam_access_key.prod_media_bucket.id
    s3_secret_key           = aws_iam_access_key.prod_media_bucket.secret
  }
}
...

これらの変数を使ってtemplates/backend_container.json.tplで環境変数を定義し、terraform applyで変更を適用します:

[
  {
    ...
    "environment": [
      ...
      {
        "name": "DEFAULT_FILE_STORAGE",
        "value": "storages.backends.s3boto3.S3Boto3Storage"
      },
      {
        "name": "AWS_ACCESS_KEY_ID",
        "value": "${s3_access_key}"
      },
      {
        "name": "AWS_SECRET_ACCESS_KEY",
        "value": "${s3_secret_key}"
      },
      {
        "name": "AWS_STORAGE_BUCKET_NAME",
        "value": "${s3_media_bucket}"
      },
      {
        "name": "AWS_S3_REGION_NAME",
        "value": "${region}"
      },
      {
       "name": "AWS_S3_ENDPOINT_URL",
       "value": "https://${s3_media_bucket}.s3.${region}.amazonaws.com/"
      }
    ],
    ...
  }
]

ECSサービスの新しいバージョンのデプロイを待ちます。その後、デプロイされたアプリケーションの管理パネルに行って新しい写真を追加してみましょう。フィールドを埋めて「Save and continue editing」をクリックし、画像のURLをクリックしてS3に正しくアップロードされたことを確認してください。

また、S3コンソールで

こちらの記事はdev.toの良い記事を日本人向けに翻訳しています。
https://dev.to/daiquiri_team/deploying-django-application-on-aws-with-terraform-connecting-to-amazon-s3-2a23