はじめに
この記事では、GitHub Actionsを活用したDjangoアプリ開発において、CloudFormationを用いてインフラをコード化し、ECSがECRからDockerイメージを自動的にプルして本番環境にデプロイする手順を紹介します。
この記事は、過去の技術検証内容を組み合わせた実践的な応用例です。
今回の内容は少し難易度が高いかもしれませんが、自分の備忘録も兼ねて、手順や実施内容を詳しくまとめていきます。
補足:過去記事の組み合わせによる検証
本記事では、以下の記事で取り上げた技術検証をマージして進めています。
CloudFormationでインフラをコード化し、ECSからECRのイメージを自動プルするデプロイ手順
DjangoプロジェクトとDocker関連ファイルを一括作成するシェルスクリプトで環境構築を自動化
GitHub Actionsを使用したECRへの自動プッシュ設定(CI/CD)
やりたいこと
今回のゴールを言葉で説明するのは少し難しいため、以下のイラストで概要を示しています。
CloudFormationテンプレートを使用して、ECSクラスタやタスク定義を自動で作成し、GitHub Actionsを利用してDjangoアプリのDockerイメージをECRに自動プッシュしています。
最終的に、ECSがECRからイメージを取得し、本番環境へデプロイする流れとなります(以下、CloudFormationテンプレートは一部簡略化していますので、ご了承ください)。
CloudFormationテンプレートの準備
以下は、今回使用したCloudFormationテンプレートです。このテンプレートには、ECSクラスター、タスク定義、IAMロールなどの必要なリソース設定が含まれています。
AWSTemplateFormatVersion: "2010-09-09"
Resources:
# VPCの作成
VPC:
Type: "AWS::EC2::VPC"
Properties:
CidrBlock: "10.0.0.0/16"
EnableDnsSupport: true
EnableDnsHostnames: true
Tags:
- Key: "Name"
Value: "honda-vpc"
# サブネットの作成
PublicSubnet1:
Type: "AWS::EC2::Subnet"
Properties:
VpcId: !Ref VPC
CidrBlock: "10.0.0.0/20"
AvailabilityZone: "ap-northeast-1a"
Tags:
- Key: "Name"
Value: "honda-subnet-public1-ap-northeast-1a"
# インターネットゲートウェイの設定
InternetGateway:
Type: "AWS::EC2::InternetGateway"
Properties:
Tags:
- Key: "Name"
Value: "honda-igw"
AttachInternetGateway:
Type: "AWS::EC2::VPCGatewayAttachment"
Properties:
InternetGatewayId: !Ref InternetGateway
VpcId: !Ref VPC
# ルートテーブルの設定
PublicRouteTable:
Type: "AWS::EC2::RouteTable"
Properties:
VpcId: !Ref VPC
Tags:
- Key: "Name"
Value: "honda-rtb-public"
PublicRoute:
Type: "AWS::EC2::Route"
Properties:
RouteTableId: !Ref PublicRouteTable
DestinationCidrBlock: "0.0.0.0/0"
GatewayId: !Ref InternetGateway
SubnetRouteTableAssociation:
Type: "AWS::EC2::SubnetRouteTableAssociation"
Properties:
SubnetId: !Ref PublicSubnet1
RouteTableId: !Ref PublicRouteTable
# ECS用セキュリティグループ
SecurityGroup:
Type: "AWS::EC2::SecurityGroup"
Properties:
GroupDescription: "Allow HTTP traffic"
VpcId: !Ref VPC
SecurityGroupIngress:
- IpProtocol: "tcp"
FromPort: 80
ToPort: 80
CidrIp: "0.0.0.0/0"
- IpProtocol: "tcp"
FromPort: 8000
ToPort: 8000
CidrIp: "0.0.0.0/0" # 追加: ポート8000も許可
# ECSクラスターの作成
ECSCluster:
Type: "AWS::ECS::Cluster"
Properties:
ClusterName: "my-ecs-cluster"
# ECSタスク定義
ECSTaskDefinition:
Type: "AWS::ECS::TaskDefinition"
Properties:
Family: "my-app-task"
RequiresCompatibilities:
- FARGATE
NetworkMode: "awsvpc"
Cpu: "256"
Memory: "512"
ExecutionRoleArn: !GetAtt ECSTaskExecutionRole.Arn
ContainerDefinitions:
- Name: "my-docker-repo"
Image: "xxx.dkr.ecr.ap-northeast-1.amazonaws.com/my-docker-repo:latest"
PortMappings:
- ContainerPort: 8000 # 変更: コンテナのポートを8000に
HostPort: 8000 # 変更: ホストのポートも8000に
Protocol: "tcp"
# ECSタスク実行ロール
ECSTaskExecutionRole:
Type: "AWS::IAM::Role"
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Principal:
Service: "ecs-tasks.amazonaws.com"
Action: "sts:AssumeRole"
Policies:
- PolicyName: "ecsTaskExecutionPolicy"
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Action:
- "ecr:GetAuthorizationToken"
- "ecr:GetDownloadUrlForLayer"
- "ecr:BatchGetImage"
- "logs:CreateLogStream"
- "logs:PutLogEvents"
- "s3:GetObject"
Resource: "*"
# ECSサービスの作成
ECSService:
Type: "AWS::ECS::Service"
Properties:
Cluster: !Ref ECSCluster
TaskDefinition: !Ref ECSTaskDefinition
DesiredCount: 1
LaunchType: "FARGATE"
NetworkConfiguration:
AwsvpcConfiguration:
Subnets:
- !Ref PublicSubnet1
SecurityGroups:
- !Ref SecurityGroup
AssignPublicIp: "ENABLED"
※今回は、過去の記事で作成したテンプレートを基に、コンテナとホストのポート設定、およびセキュリティグループ周りの設定を修正しました。
補足事項:Django アプリケーションの ALLOWED_HOSTS
設定について
本番環境へのデプロイに際し、Djangoプロジェクトの準備や設定は以下の記事で実施済みであることを想定しています。
しかし、本番環境でのデプロイ時に設定ファイルの修正が必要となった箇所がありましたので、その内容を紹介します。
発生したエラー
本番環境にCloudFormationテンプレートを使ってデプロイした際、以下のエラーが発生しました。
DisallowedHost at /
Invalid HTTP_HOST header: '43.207.164.191:8000'. You may need to add '43.207.164.191' to ALLOWED_HOSTS.
このエラーは、DjangoのALLOWED_HOSTS設定が適切に設定されていないことを示しています。
具体的には、HTTPリクエストのHostヘッダーに含まれる値(例: IPアドレス:8000
)が、Djangoの許可リストに登録されていないため、DisallowedHostエラーが発生しました。
エラーの修正方法
開発環境などで、IPアドレスが変動する場合、一時的な対応としてすべてのホストを許可する方法があります。以下のように、settings.pyのALLOWED_HOSTSを修正します。
# settings.py
ALLOWED_HOSTS = ['*']
この設定により、すべてのホストからのリクエストが許可されるようになります。
ただし、本番環境ではセキュリティリスクを回避するため、具体的なホスト名またはIPアドレスのみを設定することを強く推奨します。
本番環境では、以下のように明確にホストを指定するようにしてください。
# settings.py
ALLOWED_HOSTS = ['43.207.164.191', 'example.com']
実際にやってみた
上記のCloudFormationテンプレートを使って、ECSクラスターを作成し、タスク定義とサービスを設定しました。
CloudFormationのサービス画面からスタックの作成に進み、作成したyamlファイル
をアップロードします。
スタック名は任意で設定できます。ここでは私の名前「honda
」としています。
画面遷移後のスタックオプション設定については、個々の設定に依存するため、私は設定せずに進めていきます。
ただ、スタックの中でIAMを使う場合は、以下のように確認が求められますでの、✅を入れて進んでください。
最後に確認を行い、問題がなければ最下部までスクロールし、「送信
」をクリックして完了です。
スタックを実行後、イベントタブで成功したことが確認できました(しばらく時間がかかります)。
デプロイ後の動作確認
デプロイが完了したら、タスクが正常に実行されていることを確認します。ここでは、ECRのイメージコンテナからプルされていることも合わせて確認しました。
ECSページの「クラスター」→「タスク」タブから、実行中のタスクを選択し、タスクのパブリックIPを確認します。
ここで、タスクが使用しているパブリックIP(例:54.250.2.18
)を確認したら、ブラウザでアクセスしてみます。
http://54.250.2.18:8000/
Djangoの初期ページ("The install worked successfully!"
)が表示され、アプリケーションが正しく動作していることを確認できました!
技術検証自体はスムーズに行えましたが、複雑なアウトプットを1つの記事として整理するのは非常に力を使いました!笑
まとめ
この記事では、CloudFormation、GitHub Actions、ECR、ECSを組み合わせて、Djangoアプリケーションのインフラ構築と自動デプロイの手順を解説しました。
これらの仕組みを取り入れることで、本番環境へのデプロイが大幅に簡略化されます。
今後も技術検証を続けていきますので、この記事がどなたかの技術的な支えとなれば幸いです。
おまけ:私はLambda関数がめちゃくちゃ好き
私はLambda関数が大好きで、自宅でもいろいろな検証を行っています。興味のある方は、ぜひ読んで技術検証に挑戦してみてください!