ECSにデプロイしたWEBサービスを内部ALBで公開するためのベースとなるCloudformationを作りました。
構成概要
実際は要件次第で色々付け加えると思うので、ここではあまり多くは詰め込まずシンプルな構成としてます。
パラメータ
パラメータ | 説明 | 設定例 |
---|---|---|
ClusterName | クラスタ名 | MyCluster |
ServiceName | クラスタで起動するサービス名 | my-service |
Vpc | ECSを配置するVPC | vpc-1234abcd |
ALBSubnetIds | ALBが利用するサブネットのリスト | subnet-1234abcd,subnet-wxyz6789 |
ALBSecurityGroupId | ALBのセキュリティグループ | sg-1234abcd |
ServiceSubnets | ECSサービスの利用するサブネットのリスト | subnet-1234abcd,subnet-wxyz6789 |
ServiceSecurityGroupId | ECSサービスのセキュリティグループ | sg-1234abcd |
クラスター、サービスなどの概念は以下がわかりやすいです。
ソースコード
以下がソースコードとなります。最低限必要な設定以外はほとんどそぎ落としています。必要に応じて調整して使ってください。
--- AWSTemplateFormatVersion: "2010-09-09" Description: ECS cluster Template Parameters: ClusterName: Type: String ServiceName: Type: String AppName: Type: String VpcId: Type: String ALBSubnetIds: Type: List<String> ALBSecurityGroupId: Type: String ServiceSubnets: Type: List<String> ServiceSecurityGroupId: Type: String AppContainerPort: Type: Number Default: 80 Resources: #--------------------------------- # Internal ALB #--------------------------------- InternalALB: Type: "AWS::ElasticLoadBalancingV2::LoadBalancer" Properties: Name: !Sub "${ClusterName}-alb-internal" Tags: - Key: Name Value: !Sub "${ClusterName}-alb-internal" Scheme: "internal" LoadBalancerAttributes: - Key: "deletion_protection.enabled" Value: false - Key: "idle_timeout.timeout_seconds" Value: 60 - Key: "access_logs.s3.enabled" Value: false SecurityGroups: - !Ref ALBSecurityGroupId Subnets: !Ref ALBSubnetIds InternalALBTargetGroup: Type: "AWS::ElasticLoadBalancingV2::TargetGroup" Properties: VpcId: !Ref VpcId Name: !Sub "${ServiceName}-group" Protocol: HTTP Port: 80 TargetType: ip InternalALBListener: Type: "AWS::ElasticLoadBalancingV2::Listener" Properties: DefaultActions: - TargetGroupArn: !Ref InternalALBTargetGroup Type: forward LoadBalancerArn: !Ref InternalALB Port: 80 Protocol: HTTP #--------------------------------- # ECS Role #--------------------------------- ECSTaskExecutionRole: Type: AWS::IAM::Role Properties: RoleName: !Sub "${ClusterName}-ECSTaskExecutionRole" Path: / AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: ecs-tasks.amazonaws.com Action: sts:AssumeRole ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy ECSTaskRole: Type: AWS::IAM::Role Properties: RoleName: !Sub "${ClusterName}-ECSTaskRole" Path: / AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: ecs-tasks.amazonaws.com Action: sts:AssumeRole #--------------------------------- # ECS Cluster #--------------------------------- ECSCluster: Type: AWS::ECS::Cluster Properties: ClusterName: !Ref ClusterName CapacityProviders: - FARGATE - FARGATE_SPOT DefaultCapacityProviderStrategy: - CapacityProvider: FARGATE Base: 1 Weight: 1 - CapacityProvider: FARGATE_SPOT Weight: 2 ClusterSettings: - Name: containerInsights Value: enabled #--------------------------------- # ECS Task #--------------------------------- ECSLogGroup: Type: "AWS::Logs::LogGroup" Properties: LogGroupName: !Sub "/ecs/logs/${ClusterName}" ECSTaskDefinition: Type: AWS::ECS::TaskDefinition Properties: Family: nginx Cpu: 256 Memory: 512 NetworkMode: awsvpc ExecutionRoleArn: !Ref ECSTaskExecutionRole TaskRoleArn: !Ref ECSTaskRole RequiresCompatibilities: - FARGATE ContainerDefinitions: - Name: !Ref AppName Image: nginx Essential: true LogConfiguration: LogDriver: awslogs Options: awslogs-group: !Ref ECSLogGroup awslogs-region: !Ref AWS::Region awslogs-stream-prefix: !Ref ClusterName mode: non-blocking max-buffer-size: 100m PortMappings: - ContainerPort: !Ref AppContainerPort HostPort: 80 #--------------------------------- # ECS Service #--------------------------------- ECSService: Type: AWS::ECS::Service Properties: DesiredCount: 1 NetworkConfiguration: AwsvpcConfiguration: AssignPublicIp: DISABLED SecurityGroups: - !Ref ServiceSecurityGroupId Subnets: !Ref ServiceSubnets Cluster: !Ref ECSCluster ServiceName: !Ref ServiceName TaskDefinition: !Ref ECSTaskDefinition LoadBalancers: - ContainerName: !Ref AppName ContainerPort: !Ref AppContainerPort TargetGroupArn: !Ref InternalALBTargetGroup DependsOn: - InternalALBListener Outputs: Endpoint: Value: !Sub "http://${InternalALB.DNSName}"
使い方
Cloudformationでパラメータを設定してスタックをデプロイします。
スタックをデプロイ後にOutputにURLが出ているので確認します。
Internal ALBなのでVPC内からcurl等で確認してみます。
$ curl -s http://internal-mycluster-alb-internal-123456789012.ap-northeast-1.elb.amazonaws.com/ | head <!DOCTYPE html> <html> <head> <title>Welcome to nginx!</title> <style> body { width: 35em; margin: 0 auto; font-family: Tahoma, Verdana, Arial, sans-serif; }
Cfn補足説明
ALB
設定的にはECSから参照されるだけです。本当に必要な設定くらいしかしていないです。
#--------------------------------- # Internal ALB #--------------------------------- InternalALB: #--省略
ECS Role
#--------------------------------- # ECS Role #--------------------------------- ECSTaskExecutionRole: Type: AWS::IAM::Role #--省略 ECSTaskRole: Type: AWS::IAM::Role #--省略
タスクロールとタスク実行ロールの二つを定義しています。それぞれがどこに当たるかは以下の図がわかりやすいです。
ECS Cluster
#--------------------------------- # ECS Cluster #--------------------------------- ECSCluster: Type: AWS::ECS::Cluster #--省略
Clusterは境界や箱のようなもので実体は別途タスク定義として設定します。
ここでポイントとなるのは containerInsights
が有効化されていることと、DefaultCapacityProviderStrategy
の部分です。
FARGATEとFARGATE_SPOTを設定していますが、この設定の意味はこちらの記事を参照するとわかりやすいと思います。
ECS Task
#--------------------------------- # ECS Task #--------------------------------- ECSLogGroup: Type: "AWS::Logs::LogGroup" Properties: LogGroupName: !Sub "/ecs/logs/${ClusterName}" ECSTaskDefinition: Type: AWS::ECS::TaskDefinition #--省略
ここもnginxのコンテナ設定しているだけで他は特殊な設定抜くようにしています。
1点だけ、ログの出力先としてCloudWatch Logsを指定するため、awslogsの設定を行っています。設定項目は以下に記載があります。
さいごに
最近EKSをずっと使っていたのですが、その後でECS使うと余計にシンプルに感じますね。