AWS CloudFormationを使用すると、インフラストラクチャをコードとして定義し、AWSリソースのプロビジョニングとデプロイを自動化できます。この記事では、CloudFormationを使用してDockerコンテナを実行するための環境をAWS上に構築する方法を解説します。
目次
- CloudFormationとDockerの概要
- 準備作業
- 基本的なDockerホスト環境の構築
- ECSクラスターの構築
- ECRリポジトリの作成と活用
- より高度な構成例
- ベストプラクティスとヒント
- まとめ
CloudFormationとDockerの概要
CloudFormation はAWSのインフラストラクチャ・アズ・コード(IaC)サービスで、JSONやYAML形式のテンプレートファイルを使用してAWSリソースを定義・管理します。
Docker はコンテナ化技術の代表的なプラットフォームで、アプリケーションとその依存関係をコンテナにパッケージ化し、どこでも同じように実行できるようにします。
AWSでDockerを実行する主な方法:
- EC2インスタンスにDockerをインストールして実行
- Amazon ECS (Elastic Container Service)を使用
- Amazon EKS (Elastic Kubernetes Service)を使用
この記事では、EC2インスタンスを使用した単一のDockerホストと、マネージドサービスであるECSを使用した方法の両方を紹介します。
準備作業
CloudFormationテンプレートを使用するための前提条件:
- AWSアカウント の取得
- AWS CLI のインストールと設定
- 適切なIAM権限 の設定
- 基本的なYAML/JSON の知識
基本的なDockerホスト環境の構築
まず、単一のEC2インスタンスにDockerをインストールして、基本的なコンテナホストを作成する方法を見ていきましょう。
CloudFormationテンプレート例(EC2+Docker)
AWSTemplateFormatVersion: '2010-09-09'
Description: 'CloudFormation template for Docker host on EC2'
Parameters:
InstanceType:
Description: EC2 instance type
Type: String
Default: t3.micro
AllowedValues:
- t3.micro
- t3.small
- t3.medium
ConstraintDescription: Must be a valid EC2 instance type.
KeyName:
Description: SSH Key Pair name
Type: AWS::EC2::KeyPair::KeyName
ConstraintDescription: Must be the name of an existing EC2 KeyPair.
SSHLocation:
Description: IP address range that can SSH to the EC2 instance
Type: String
Default: 0.0.0.0/0
AllowedPattern: (\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/(\d{1,2})
ConstraintDescription: Must be a valid CIDR range.
Resources:
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.0.0.0/16
EnableDnsSupport: true
EnableDnsHostnames: true
Tags:
- Key: Name
Value: Docker-VPC
InternetGateway:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: Docker-IGW
InternetGatewayAttachment:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
InternetGatewayId: !Ref InternetGateway
VpcId: !Ref VPC
PublicSubnet:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
AvailabilityZone: !Select [ 0, !GetAZs '' ]
CidrBlock: 10.0.1.0/24
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: Docker-Public-Subnet
PublicRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: Docker-Public-Route-Table
DefaultPublicRoute:
Type: AWS::EC2::Route
DependsOn: InternetGatewayAttachment
Properties:
RouteTableId: !Ref PublicRouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref InternetGateway
PublicSubnetRouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref PublicRouteTable
SubnetId: !Ref PublicSubnet
DockerSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Security group for Docker host
VpcId: !Ref VPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 22
ToPort: 22
CidrIp: !Ref SSHLocation
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: 0.0.0.0/0
Tags:
- Key: Name
Value: Docker-SG
DockerHost:
Type: AWS::EC2::Instance
Properties:
InstanceType: !Ref InstanceType
SecurityGroupIds:
- !GetAtt DockerSecurityGroup.GroupId
KeyName: !Ref KeyName
ImageId: ami-0c55b159cbfafe1f0 # Amazon Linux 2 AMI (example, replace with current AMI ID)
SubnetId: !Ref PublicSubnet
UserData:
Fn::Base64: !Sub |
#!/bin/bash -xe
yum update -y
amazon-linux-extras install docker -y
service docker start
systemctl enable docker
usermod -a -G docker ec2-user
# Install Docker Compose
curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose
# Create a simple Docker Compose file
mkdir -p /home/ec2-user/docker-demo
cat > /home/ec2-user/docker-demo/docker-compose.yml << 'EOL'
version: '3'
services:
web:
image: nginx:latest
ports:
- "80:80"
restart: always
EOL
chown -R ec2-user:ec2-user /home/ec2-user/docker-demo
# Start the container
cd /home/ec2-user/docker-demo
docker-compose up -d
# Signal CloudFormation that the installation is complete
/opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource DockerHost --region ${AWS::Region}
Tags:
- Key: Name
Value: Docker-Host
Outputs:
InstanceId:
Description: ID of the created EC2 instance
Value: !Ref DockerHost
PublicIP:
Description: Public IP address of the Docker host
Value: !GetAtt DockerHost.PublicIp
WebsiteURL:
Description: URL for the web server
Value: !Sub http://${DockerHost.PublicIp}
デプロイ方法
- 上記のYAMLコードを
docker-host.yml
として保存します。 - AWS CLIを使用してスタックを作成します:
aws cloudformation create-stack \
--stack-name docker-single-host \
--template-body file://docker-host.yml \
--parameters ParameterKey=KeyName,ParameterValue=your-key-pair-name \
--capabilities CAPABILITY_IAM
- AWSマネジメントコンソールでスタックの作成進捗を確認します。
このテンプレートでは、以下のリソースを作成します:
- VPC、サブネット、インターネットゲートウェイなどのネットワークリソース
- セキュリティグループ(SSH、HTTP、HTTPSアクセスを許可)
- DockerとDocker ComposeがインストールされたEC2インスタンス
- NGINXコンテナを実行する簡単なDocker Compose設定
ECSクラスターの構築
次に、より本格的なコンテナオーケストレーションが必要な場合のために、Amazon ECS(Elastic Container Service)クラスターをCloudFormationで作成する方法を紹介します。
CloudFormationテンプレート例(ECS)
AWSTemplateFormatVersion: '2010-09-09'
Description: 'CloudFormation template for ECS Cluster'
Parameters:
EnvironmentName:
Description: An environment name that is prefixed to resource names
Type: String
Default: Docker-ECS-Demo
Resources:
# VPC設定
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.0.0.0/16
EnableDnsSupport: true
EnableDnsHostnames: true
Tags:
- Key: Name
Value: !Sub ${EnvironmentName}-VPC
InternetGateway:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: !Sub ${EnvironmentName}-IGW
InternetGatewayAttachment:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
InternetGatewayId: !Ref InternetGateway
VpcId: !Ref VPC
# サブネット
PublicSubnet1:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
AvailabilityZone: !Select [ 0, !GetAZs '' ]
CidrBlock: 10.0.1.0/24
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: !Sub ${EnvironmentName}-Public-Subnet-1
PublicSubnet2:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
AvailabilityZone: !Select [ 1, !GetAZs '' ]
CidrBlock: 10.0.2.0/24
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: !Sub ${EnvironmentName}-Public-Subnet-2
# ルーティング
PublicRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub ${EnvironmentName}-Public-Routes
DefaultPublicRoute:
Type: AWS::EC2::Route
DependsOn: InternetGatewayAttachment
Properties:
RouteTableId: !Ref PublicRouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref InternetGateway
PublicSubnet1RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref PublicRouteTable
SubnetId: !Ref PublicSubnet1
PublicSubnet2RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref PublicRouteTable
SubnetId: !Ref PublicSubnet2
# ECSクラスター
ECSCluster:
Type: AWS::ECS::Cluster
Properties:
ClusterName: !Sub ${EnvironmentName}-cluster
CapacityProviders:
- FARGATE
- FARGATE_SPOT
DefaultCapacityProviderStrategy:
- CapacityProvider: FARGATE
Weight: 1
# セキュリティグループ
ECSSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Security group for ECS tasks
VpcId: !Ref VPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: 0.0.0.0/0
Tags:
- Key: Name
Value: !Sub ${EnvironmentName}-ECS-SG
# ECSタスク実行ロール
ECSTaskExecutionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Statement:
- Effect: Allow
Principal:
Service: ecs-tasks.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy
# サンプルのECSタスク定義
SampleTaskDefinition:
Type: AWS::ECS::TaskDefinition
Properties:
Family: !Sub ${EnvironmentName}-sample-app
Cpu: '256'
Memory: '512'
NetworkMode: awsvpc
RequiresCompatibilities:
- FARGATE
ExecutionRoleArn: !GetAtt ECSTaskExecutionRole.Arn
ContainerDefinitions:
- Name: nginx
Image: nginx:latest
Essential: true
PortMappings:
- ContainerPort: 80
HostPort: 80
Protocol: tcp
LogConfiguration:
LogDriver: awslogs
Options:
awslogs-group: !Ref CloudWatchLogsGroup
awslogs-region: !Ref AWS::Region
awslogs-stream-prefix: ecs
# ログ
CloudWatchLogsGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Sub /ecs/${EnvironmentName}
RetentionInDays: 30
# ALB
ALB:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
Scheme: internet-facing
LoadBalancerAttributes:
- Key: idle_timeout.timeout_seconds
Value: '30'
Subnets:
- !Ref PublicSubnet1
- !Ref PublicSubnet2
SecurityGroups:
- !Ref ECSSecurityGroup
ALBListener:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
DefaultActions:
- Type: forward
TargetGroupArn: !Ref ALBTargetGroup
LoadBalancerArn: !Ref ALB
Port: 80
Protocol: HTTP
ALBTargetGroup:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
HealthCheckIntervalSeconds: 30
HealthCheckPath: /
HealthCheckProtocol: HTTP
HealthCheckTimeoutSeconds: 5
HealthyThresholdCount: 2
TargetType: ip
Port: 80
Protocol: HTTP
UnhealthyThresholdCount: 5
VpcId: !Ref VPC
# ECSサービス
SampleService:
Type: AWS::ECS::Service
DependsOn: ALBListener
Properties:
ServiceName: !Sub ${EnvironmentName}-sample-service
Cluster: !Ref ECSCluster
TaskDefinition: !Ref SampleTaskDefinition
DesiredCount: 2
LaunchType: FARGATE
NetworkConfiguration:
AwsvpcConfiguration:
AssignPublicIp: ENABLED
SecurityGroups:
- !Ref ECSSecurityGroup
Subnets:
- !Ref PublicSubnet1
- !Ref PublicSubnet2
LoadBalancers:
- ContainerName: nginx
ContainerPort: 80
TargetGroupArn: !Ref ALBTargetGroup
Outputs:
ClusterName:
Description: The name of the ECS cluster
Value: !Ref ECSCluster
ALBDNSName:
Description: DNS name of the load balancer
Value: !GetAtt ALB.DNSName
ServiceURL:
Description: URL of the service
Value: !Sub http://${ALB.DNSName}
デプロイ方法
- 上記のYAMLコードを
ecs-cluster.yml
として保存します。 - AWS CLIを使用してスタックを作成します:
aws cloudformation create-stack \
--stack-name ecs-docker-cluster \
--template-body file://ecs-cluster.yml \
--capabilities CAPABILITY_IAM
このテンプレートでは以下のリソースを作成します:
- VPC、サブネット、ルーティングなどのネットワークインフラストラクチャ
- ECSクラスター(Fargateを使用)
- サンプルのタスク定義とECSサービス
- アプリケーションロードバランサー
- 必要なIAMロールとセキュリティグループ
ECRリポジトリの作成と活用
カスタムDockerイメージを使用する場合は、Amazon ECR(Elastic Container Registry)にプライベートリポジトリを作成しておくと便利です。
CloudFormationテンプレート例(ECR)
AWSTemplateFormatVersion: '2010-09-09'
Description: 'CloudFormation template for ECR Repository'
Parameters:
RepositoryName:
Type: String
Default: docker-demo-repo
Description: Name of the ECR repository
Resources:
ECRRepository:
Type: AWS::ECR::Repository
Properties:
RepositoryName: !Ref RepositoryName
LifecyclePolicy:
LifecyclePolicyText: |
{
"rules": [
{
"rulePriority": 1,
"description": "Keep only the last 10 images",
"selection": {
"tagStatus": "any",
"countType": "imageCountMoreThan",
"countNumber": 10
},
"action": {
"type": "expire"
}
}
]
}
RepositoryPolicyText:
Version: "2012-10-17"
Statement:
- Sid: AllowPull
Effect: Allow
Principal:
Service: ecs-tasks.amazonaws.com
Action:
- ecr:GetDownloadUrlForLayer
- ecr:BatchGetImage
- ecr:BatchCheckLayerAvailability
Outputs:
RepositoryURL:
Description: The URL of the ECR repository
Value: !Sub ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${RepositoryName}
ECRリポジトリの使用方法
- 上記のテンプレートをデプロイして、ECRリポジトリを作成します。
- AWS CLIを使用してECRにログインします:
aws ecr get-login-password --region <リージョン> | docker login --username AWS --password-stdin <アカウントID>.dkr.ecr.<リージョン>.amazonaws.com
- ローカルのDockerイメージにタグを付けます:
docker tag myapp:latest <アカウントID>.dkr.ecr.<リージョン>.amazonaws.com/docker-demo-repo:latest
- イメージをECRにプッシュします:
docker push <アカウントID>.dkr.ecr.<リージョン>.amazonaws.com/docker-demo-repo:latest
- ECSタスク定義でこのイメージを参照します:
ContainerDefinitions:
- Name: myapp
Image: !Sub ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/docker-demo-repo:latest
# その他の設定...
より高度な構成例
ECSクラスターのオートスケーリング
CloudFormationを使用してECSサービスのオートスケーリングを設定できます:
ScalableTarget:
Type: AWS::ApplicationAutoScaling::ScalableTarget
Properties:
MaxCapacity: 10
MinCapacity: 2
ResourceId: !Sub service/${ECSCluster}/${SampleService.Name}
ScalableDimension: ecs:service:DesiredCount
ServiceNamespace: ecs
RoleARN: !GetAtt AutoScalingRole.Arn
ScalingPolicy:
Type: AWS::ApplicationAutoScaling::ScalingPolicy
Properties:
PolicyName: CPUScalingPolicy
PolicyType: TargetTrackingScaling
ScalingTargetId: !Ref ScalableTarget
TargetTrackingScalingPolicyConfiguration:
PredefinedMetricSpecification:
PredefinedMetricType: ECSServiceAverageCPUUtilization
TargetValue: 70.0
ScaleInCooldown: 60
ScaleOutCooldown: 60
ECS Anywhere(オンプレミスサーバーの使用)
オンプレミスサーバーをECSクラスターに登録するための設定も可能です:
ECSAnywhereRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: ssm.amazonaws.com
Action: 'sts:AssumeRole'
ManagedPolicyArns:
- 'arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore'
- 'arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role'
マルチコンテナアプリケーション
複数のコンテナを含むタスク定義の例:
WebAppTaskDefinition:
Type: AWS::ECS::TaskDefinition
Properties:
Family: web-app
Cpu: '512'
Memory: '1024'
NetworkMode: awsvpc
RequiresCompatibilities:
- FARGATE
ExecutionRoleArn: !GetAtt ECSTaskExecutionRole.Arn
ContainerDefinitions:
- Name: web
Image: nginx:latest
Essential: true
PortMappings:
- ContainerPort: 80
HostPort: 80
DependsOn:
- ContainerName: api
Condition: START
- Name: api
Image: node:14-alpine
Command: ["node", "app.js"]
Essential: true
PortMappings:
- ContainerPort: 3000
Environment:
- Name: DB_HOST
Value: !GetAtt Database.Endpoint.Address
- Name: sidecar
Image: datadog/agent:latest
Essential: false
Environment:
- Name: DD_API_KEY
Value: !Ref DatadogApiKey
ベストプラクティスとヒント
セキュリティのベストプラクティス
- 最小権限の原則:IAMロールには必要最小限の権限のみを付与
- コンテナイメージのスキャン:ECRのイメージスキャン機能を有効化
- シークレット管理:AWS Secrets Managerを使用して機密情報を管理
- セキュリティグループの適切な設定:必要なポートのみを開放
コスト最適化
- Fargate Spotの活用:耐障害性のあるワークロードにはFargate Spotを使用
- リソースサイジングの最適化:CPUとメモリを適切に設定
- オートスケーリングの活用:需要に応じたリソースの拡大縮小
- 不要なリソースの削除:使用していないリソースはCloudFormationスタックごと削除
運用の効率化
- CI/CDパイプラインの構築:AWS CodePipelineを使用した自動デプロイ
- モニタリングとログ:CloudWatchと連携して可視性を確保
- モジュラー設計:CloudFormationのネストされたスタックを活用
- Infrastructure as Code(IaC)の徹底:手動変更を避け、すべての変更をコードで管理
まとめ
この記事では、CloudFormationを使用してAWS上にDockerコンテナ環境を構築する方法を解説しました。単一のEC2インスタンスにDockerをインストールする基本的なアプローチから、ECSクラスターを使用した本格的なコンテナオーケストレーションまで、様々な構成例を紹介しました。
CloudFormation テンプレートを使用することで、インフラストラクチャをコードとして管理し、再現性の高い環境を簡単に構築できます。また、環境の変更や更新も容易になり、バージョン管理やチームでの共有も可能になります。
AWS上のDockerコンテナ環境は、アプリケーションの開発からテスト、本番デプロイまで、柔軟でスケーラブルなインフラストラクチャを提供します。CloudFormationと組み合わせることで、そのような環境の管理と自動化が実現できます。
次のステップとして、提供したテンプレートを自分のニーズに合わせてカスタマイズし、CI/CDパイプラインを構築して、コンテナ化されたアプリケーションのデプロイプロセスを自動化することをお勧めします。
コメント