#
ドキュメント

Document

自分のための備忘録です。

AWS Copilot

ドキュメント:https://aws.github.io/copilot-cli/ja/

AWS Copilot は Amazon ECS CLI の後継に当たるものです。

https://aws.amazon.com/jp/blogs/news/introducing-aws-copilot/

Ref.

インストール・アップデート

Homebrewを使用しない方法。

$ curl -Lo copilot https://github.com/aws/copilot-cli/releases/latest/download/copilot-darwin && chmod u+x copilot && sudo mv copilot /usr/local/bin/copilot && copilot --help

ref. https://aws.amazon.com/jp/blogs/news/introducing-aws-copilot/

プロジェクト構成

 |-- first-app
      |
      |-- .docker
          |
          |-- nginx
              |
              |-- Dockerfile
          |
          |-- apache
              |
              |-- Dockerfile
          |
          |-- php
              |
              |-- Dockerfile
      |
      |- src
          |
          |-- nginx
              |
              |-- index.html
          |
          |-- apache
              |
              |-- index.html
          |
          |-- php
              |
              |-- script.php

ファイルを作成

Nginx用

.docker/nginx/Dockerfile

FROM nginx:alpine
EXPOSE 80
COPY ./src/nginx /usr/share/nginx/html

src/nginx/index.html

<html>
    <head>
        <title>Nginx Hello World</title>
    </head>
    <body>
       Hello World from Nginx
    </body>
</html>

Apache用

.docker/apache/Dockerfile`

FROM apache:alpine
EXPOSE 80
COPY ./src/apache /usr/local/apache2/htdocs/

src/apache/index.html

<html>
    <head>
        <title>Apache Hello World</title>
    </head>
    <body>
       Hello World from Apache
    </body>
</html>

フロー

  1. アプリケーションを作成&(最初の)サービスを追加:$ copilot init
  2. 環境を構築:$ copilot env init
  3. サービスを追加:$ copilot svc init
  4. サービスをビルド&デプロイ:$ copilot svc deploy
  5. ジョブを追加
  6. ジョブをデプロイ

アプリケーションを作成&(最初の)サービスを追加

$ copilot init

Use existing application: No
Application name: first-app <========================================= application名を指定
Workload type: Request-Driven Web Service
Service name: nginx <================================================= サービス名を指定
Dockerfile: Enter custom path for your Dockerfile
Dockerfile: .docker/nginx/Dockerfile <================================ Dockerfileを指定
Ok great, we'll set up a Request-Driven Web Service named nginx in application first-app listening on port 80.

✔ Created the infrastructure to manage services and jobs under application first-app..

✔ The directory copilot will hold service manifests for application first-app.

✔ Wrote the manifest for service nginx at copilot/nginx/manifest.yml
Your manifest contains configurations like your container size and port (:80).

✔ Created ECR repositories for service nginx..

All right, you're all set for local development.
Deploy: No

DeployはVPC CIDR rangeを指定したいのでNoにします。
以下のファイルが作成されます。

  • copilotディレクトリ作成
  • copilot/.workspace.yml作成
  • copilot/nginx/manifest.yml作成

作成リソース

  • AdministrationRole AWS::IAM::Role
  • ExecutionRole AWS::IAM::Role
  • ECRリポジトリ(first-app/nginx

作成ファイル

copilot/.workspace

application: first-app

copilot/nginx/manifest.ymlをイメージをビルドする際のcontextを指定するように修正。

# 筆者注 コメントを省略

name: nginx
type: Request-Driven Web Service

image:
-  build: .docker/nginx/Dockerfile
+  build: 
+    context: .
+    dockerfile: .docker/nginx/Dockerfile
  port: 80

cpu: 1024
memory: 2048

アプリケーションの情報を確認

$ copilot app show first-app

環境を構築

例としてtest環境を作成します。

$ copilot env init --name test

VPCCIDR range10.0.0.0/16します。 VPC CIDR rangePublic Subnet(2つ)のCIDR rangePraivate Subnet(2つ)のCIDR rangeを指定してtest環境をします。

$ copilot env init --name test --override-vpc-cidr 10.1.0.0/16 \
  --override-public-cidrs 10.1.0.0/24,10.1.1.0/24 \
  --override-private-cidrs 10.1.2.0/24,10.1.3.0/24

作成リソース

  • KMSKey AWS::KMS::Key
  • PipelineBuiltArtifactBucket AWS::S3::Bucket
  • PipelineBuiltArtifactBucketPolicy AWS::S3::BucketPolicy
  • CloudformationExecutionRole AWS::IAM::Role
  • Cluster AWS::ECS::Cluster
  • DefaultPublicRoute AWS::EC2::Route
  • EnableLongARNFormatAction Custom::EnableLongARNFormatFunction
  • EnableLongARNFormatFunction AWS::Lambda::Function
  • EnvironmentManagerRole AWS::IAM::Role
  • EnvironmentSecurityGroup AWS::EC2::SecurityGroup
  • EnvironmentSecurityGroupIngressFromSelf AWS::EC2::SecurityGroupIngress
  • InternetGateway AWS::EC2::InternetGateway
  • InternetGatewayAttachment AWS::EC2::VPCGatewayAttachment
  • PrivateSubnet1 AWS::EC2::Subnet
  • PrivateSubnet2 AWS::EC2::Subnet
  • PublicRouteTable AWS::EC2::RouteTable
  • PublicSubnet1 AWS::EC2::Subnet
  • PublicSubnet1RouteTableAssociation AWS::EC2::SubnetRouteTableAssociation
  • PublicSubnet2 AWS::EC2::Subnet
  • PublicSubnet2RouteTableAssociation AWS::EC2::SubnetRouteTableAssociation
  • ServiceDiscoveryNamespace AWS::ServiceDiscovery::PrivateDnsNamespace
  • VPC vpc-0390e11acf7f75d88 AWS::EC2::VPC

サービスを追加

サービスapacheを追加します。

$ copilot svc init

Note: It's best to run this command in the root of your workspace.
Service type: Request-Driven Web Service
Service name: apache
Dockerfile: Enter custom path for your Dockerfile
Dockerfile: .docker/apache/Dockerfile
✔ Wrote the manifest for service apache at copilot/apache/manifest.yml
Your manifest contains configurations like your container size and port (:80).

✔ Created ECR repositories for service apache..
  • ECRリポジトリを作成(first-app/apache)
  • ローカルにcopilot/apache/manifest.ymlを作成

copilot/apache/manifest.yml

# 筆者注 コメントを省略

name: apache
type: Request-Driven Web Service

image:
-  build: .docker/apache/Dockerfile
+  build: 
+    context: .
+    dockerfile: .docker/apache/Dockerfile  
  port: 80

cpu: 1024
memory: 2048

サービスを確認

$ copilot svc ls --app first-app

サービスをビルド&デプロイ

サービスnginxをデプロイします。

$ copilot svc deploy --name nginx --env test
# ...
- Creating the infrastructure for stack first-app-test-nginx                      [create complete]  [244.1s]
  - An IAM Role for App Runner to use on your behalf to pull your image from ECR  [create complete]  [19.6s]
  - An IAM role to control permissions for the containers in your service         [create complete]  [22.1s]
  - An App Runner service to run and manage your containers                       [create complete]  [212.5s]
✔ Deployed service nginx.
Recommended follow-up action:
    You can access your service at https://{{xxxxx}}.awsapprunner.com over the internet. <======== App RunnerのURL
➜  first-app git:(master) ✗
  1. ローカルでイメージをビルド
  2. ECRにPush
  3. AppRunner サービス起動

作成リソース

  • ccessRole AWS::IAM::Role CREATE_COMPLETE
  • InstanceRole AWS::IAM::Role CREATE_COMPLETE
  • Service AWS::AppRunner::Service

同様に以下でサービスapacheもデプロイできます。

$ copilot svc deploy --name nginx --env test

ジョブを追加

ファイルを作成

.docker/php/Dockerfie

FROM php:7.4-cli
COPY ./src/php /usr/src/myapp
WORKDIR /usr/src/myapp
CMD [ "php", "./script.php" ]

src/php/script.php

<?php
var_dump('hello world');

copilot job init

➜  first-app git:(master) ✗ copilot job init
Job name: php-job <=============================================== job名を指定
Dockerfile: Enter custom path for your Dockerfile
Dockerfile: .docker/php/Dockerfile <============================== job用Dockerfileを指定
Schedule type: Rate
Rate: 2m <======================================================== jobのスケジュールを指定
✔ Wrote the manifest for job php-job at copilot/php-job/manifest.yml
Your manifest contains configurations like your container size and job schedule (@every 2m).

✔ Created ECR repositories for job php-job..
  • ECRリポジトリを作成(first-app/php-job)
  • ローカルにcopilot/php-job/manifest.ymlを作成

copilot/php-job/manifest.yml

name: php-job
type: Scheduled Job

on:
  schedule: "@every 2m"

image:
  # Docker build arguments. For additional overrides: https://aws.github.io/copilot-cli/docs/manifest/scheduled-job/#image-build
  build: .docker/php/Dockerfile
  build:
    context: .
    dockerfile: .docker/php/Dockerfile

cpu: 256       # Number of CPU units for the task.
memory: 512    # Amount of memory in MiB used by the task.

Jobをデプロイ

$ copilot job deploy --name php-job --env test

# ...
✔ Proposing infrastructure changes for stack first-app-test-php-job
- Creating the infrastructure for stack first-app-test-php-job              [create complete]  [160.9s]
  - Update your environment's shared resources                              [create complete]  [0.0s]
  - An IAM Role for the Fargate agent to make AWS API calls on your behalf  [create complete]  [25.6s]
  - A CloudWatch log group to hold your service logs                        [create complete]  [2.5s]
  - A CloudWatch event rule to trigger the job's state machine              [create complete]  [61.0s]
  - A state machine to invoke your job and handle retry and timeout logic   [create complete]  [9.2s]
  - An ECS task definition to group your containers and run them on ECS     [create complete]  [6.1s]
  - An IAM role to control permissions for the containers in your tasks     [create complete]  [25.6s]
✔ Deployed php-job.

ジョブのインエターネット接続

  • ジョブはパブリックサブネットに作成する
  • パブリックサブネットにInternet Gatewayが作成される
  • パブリックサブネットにInternet Gatewayへのルートが設定される
VPC_Management_Console

サービスデプロイ結果

Copilot_Hello_World

ジョブデプロイ結果

CloudWatch_Management_Console

作成スタック

Banners_and_Alerts_と_CloudFormation_-_スタック_app-infrastructure-roles

Addon

Service の Manifestとデフォルトでは統合されていない任意の AWS サービスを Copilot CLI では Addon という形で追加できます。Addon の例として Service から読み書きをする必要がある DyanmoDB テーブルや S3 バケット、RDS Aurora Serverless クラスターなどが考えられます。

https://aws.github.io/copilot-cli/ja/docs/developing/additional-aws-resources/

Addonはサービスだけでなくジョブでも使用できます。
copilot/{job/service}/addons/ディレクトリにCloudFormation(CFN)テンプレート(.yml)を配置します。

通常 addons/ ディレクトリ以下の各ファイルは独立した Addon を表し AWS CloudFormation (CFN) テンプレート と解釈されます。

https://aws.github.io/copilot-cli/ja/docs/developing/additional-aws-resources/

addonsに配置したCFNテンプレートには、パラメーターとしてApp、Env、Nameが渡されます。
テンプレートにのarametersにApp、Env、Nameを定義する必要があります。

Addon テンプレートには任意の有効な CloudFormation テンプレートを用いることができます。しかしデフォルトでは Copilot は App, Env そして Name パラメーターを渡すため、必要であれば Conditions セクション または Mappings セクション でリソースのプロパティをカスタマイズできます。

https://aws.github.io/copilot-cli/ja/docs/developing/additional-aws-resources/

 Parameters:
  App:
    Type: String
    Description: Your application's name.
  Env:
    Type: String
    Description: The environment name your service, job, or workflow is being deployed to.
  Name:
    Type: String
    Description: The name of the service, job, or workflow being deployed.

Role

copilot app init

AdministrationRole

AdministrationRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Ref AdminRoleName
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service: cloudformation.amazonaws.com
            Action:
              - sts:AssumeRole
      Path: /
      Policies:
        - PolicyName: AssumeRole-AWSCloudFormationStackSetExecutionRole
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - sts:AssumeRole
                Resource:
                  - !Sub 'arn:${AWS::Partition}:iam::*:role/${AdminRoleName}'

ExecutionRole

ExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Ref ExecutionRoleName
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              AWS: !GetAtt AdministrationRole.Arn
            Action:
              - sts:AssumeRole
      Path: /
      Policies:
      - PolicyName: ExecutionRolePolicy
        PolicyDocument:
          Version: "2012-10-17"
          Statement:
              - Sid: StackSetRequiredPermissions
                Effect: Allow
                Action:
                  - cloudformation:*
                  - s3:*
                  - sns:*
                Resource: "*"
              - Sid: ManageKMSKeys
                Effect: Allow
                Action:
                  - kms:*
                Resource: "*"
              - Sid: ManageECRRepos
                Effect: Allow
                Action:
                  - ecr:DescribeImageScanFindings
                  - ecr:GetLifecyclePolicyPreview
                  - ecr:CreateRepository
                  - ecr:GetDownloadUrlForLayer
                  - ecr:GetAuthorizationToken
                  - ecr:ListTagsForResource
                  - ecr:ListImages
                  - ecr:DeleteLifecyclePolicy
                  - ecr:DeleteRepository
                  - ecr:SetRepositoryPolicy
                  - ecr:BatchGetImage
                  - ecr:DescribeImages
                  - ecr:DescribeRepositories
                  - ecr:BatchCheckLayerAvailability
                  - ecr:GetRepositoryPolicy
                  - ecr:GetLifecyclePolicy
                  - ecr:TagResource
                Resource: "*"

DNSDelegationRole

 DNSDelegationRole:
    Type: AWS::IAM::Role
    Condition: DelegateDNS
    Properties:
      RoleName: !Ref DNSDelegationRoleName
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              AWS:  !Sub arn:${AWS::Partition}:iam::${AWS::AccountId}:root
            Action:
              - sts:AssumeRole
          - Effect: Allow
            Principal:
              AWS:
                - !Sub arn:${AWS::Partition}:iam::876424506091:root
            Action:
              - sts:AssumeRole
      Path: /
      Policies:
      - PolicyName: DNSDelegationPolicy
        PolicyDocument:
          Version: "2012-10-17"
          Statement:
              - Sid: HostedZoneReadRecords
                Effect: Allow
                Action:
                  - route53:Get*
                  - route53:List*
                Resource: "*"
              - Sid: HostedZoneUpdate
                Effect: Allow
                Action:
                  - route53:ChangeResourceRecordSets
                Resource:
                  - !Sub arn:${AWS::Partition}:route53:::hostedzone/${AppHostedZone}
                  - !Sub arn:${AWS::Partition}:route53:::hostedzone/${AppDomainHostedZoneID}

copilot env init

CloudformationExecutionRole

  CloudformationExecutionRole:
    Metadata:
      'aws:copilot:description': 'An IAM Role for AWS CloudFormation to manage resources'
    DeletionPolicy: Retain
    Type: AWS::IAM::Role
    DependsOn: VPC
    Properties:
      RoleName: !Sub ${AWS::StackName}-CFNExecutionRole
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Effect: Allow
          Principal:
            Service:
            - 'cloudformation.amazonaws.com'
            - 'lambda.amazonaws.com'
          Action: sts:AssumeRole
      Path: /
      Policies:
        - PolicyName: executeCfn
          # This policy is more permissive than the managed PowerUserAccess
          # since it allows arbitrary role creation, which is needed for the
          # ECS task role specified by the customers.
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
            -
              Effect: Allow
              NotAction:
                - 'organizations:*'
                - 'account:*'
              Resource: '*'
            -
              Effect: Allow
              Action:
                - 'organizations:DescribeOrganization'
                - 'account:ListRegions'
              Resource: '*'

EnvironmentManagerRole

  EnvironmentManagerRole:
 Metadata:
   'aws:copilot:description': 'An IAM Role to describe resources in your environment'
 DeletionPolicy: Retain
 Type: AWS::IAM::Role
 DependsOn: CloudformationExecutionRole
 Properties:
   RoleName: !Sub ${AWS::StackName}-EnvManagerRole
   AssumeRolePolicyDocument:
     Version: '2012-10-17'
     Statement:
     - Effect: Allow
       Principal:
         AWS: !Sub ${ToolsAccountPrincipalARN}
       Action: sts:AssumeRole
   Path: /
   Policies:
   - PolicyName: root
     PolicyDocument:
       Version: '2012-10-17'
       Statement:
       - Sid: CloudwatchLogs
         Effect: Allow
         Action: [
           "logs:GetLogRecord",
           "logs:GetQueryResults",
           "logs:StartQuery",
           "logs:GetLogEvents",
           "logs:DescribeLogStreams",
           "logs:StopQuery",
           "logs:TestMetricFilter",
           "logs:FilterLogEvents",
           "logs:GetLogGroupFields",
           "logs:GetLogDelivery"
         ]
         Resource: "*"
       - Sid: Cloudwatch
         Effect: Allow
         Action: [
           "cloudwatch:DescribeAlarms"
         ]
         Resource: "*"
       - Sid: ECS
         Effect: Allow
         Action: [
           "ecs:ListAttributes",
           "ecs:ListTasks",
           "ecs:DescribeServices",
           "ecs:DescribeTaskSets",
           "ecs:ListContainerInstances",
           "ecs:DescribeContainerInstances",
           "ecs:DescribeTasks",
           "ecs:DescribeClusters",
           "ecs:UpdateService",
           "ecs:PutAttributes",
           "ecs:StartTelemetrySession",
           "ecs:StartTask",
           "ecs:StopTask",
           "ecs:ListServices",
           "ecs:ListTaskDefinitionFamilies",
           "ecs:DescribeTaskDefinition",
           "ecs:ListTaskDefinitions",
           "ecs:ListClusters",
           "ecs:RunTask"
         ]
         Resource: "*"
       - Sid: ExecuteCommand
         Effect: Allow
         Action: [
           "ecs:ExecuteCommand"
         ]
         Resource: "*"
         Condition:
           StringEquals:
             'aws:ResourceTag/copilot-application': !Sub '${AppName}'
             'aws:ResourceTag/copilot-environment': !Sub '${EnvironmentName}' 
       - Sid: CloudFormation
         Effect: Allow
         Action: [
           "cloudformation:CancelUpdateStack",
           "cloudformation:CreateChangeSet",
           "cloudformation:CreateStack",
           "cloudformation:DeleteChangeSet",
           "cloudformation:DeleteStack",
           "cloudformation:Describe*",
           "cloudformation:DetectStackDrift",
           "cloudformation:DetectStackResourceDrift",
           "cloudformation:ExecuteChangeSet",
           "cloudformation:GetTemplate",
           "cloudformation:GetTemplateSummary",
           "cloudformation:UpdateStack",
           "cloudformation:UpdateTerminationProtection"
         ]
         Resource: "*"
       - Sid: GetAndPassCopilotRoles
         Effect: Allow
         Action: [
           "iam:GetRole",
           "iam:PassRole"
         ]
         Resource: "*"
         Condition:
           StringEquals:
             'iam:ResourceTag/copilot-application': !Sub '${AppName}'
             'iam:ResourceTag/copilot-environment': !Sub '${EnvironmentName}'
       - Sid: ECR
         Effect: Allow
         Action: [
           "ecr:BatchGetImage",
           "ecr:BatchCheckLayerAvailability",
           "ecr:CompleteLayerUpload",
           "ecr:DescribeImages",
           "ecr:DescribeRepositories",
           "ecr:GetDownloadUrlForLayer",
           "ecr:InitiateLayerUpload",
           "ecr:ListImages",
           "ecr:ListTagsForResource",
           "ecr:PutImage",
           "ecr:UploadLayerPart",
           "ecr:GetAuthorizationToken"
         ]
         Resource: "*"
       - Sid: ResourceGroups
         Effect: Allow
         Action: [
           "resource-groups:GetGroup",
           "resource-groups:GetGroupQuery",
           "resource-groups:GetTags",
           "resource-groups:ListGroupResources",
           "resource-groups:ListGroups",
           "resource-groups:SearchResources"
         ]
         Resource: "*"
       - Sid: SSM
         Effect: Allow
         Action: [
           "ssm:DeleteParameter",
           "ssm:DeleteParameters",
           "ssm:GetParameter",
           "ssm:GetParameters",
           "ssm:GetParametersByPath"
         ]
         Resource: "*"
       - Sid: SSMSecret
         Effect: Allow
         Action: [
           "ssm:PutParameter",
           "ssm:AddTagsToResource"
         ]
         Resource:
           - !Sub 'arn:${AWS::Partition}:ssm:${AWS::Region}:${AWS::AccountId}:parameter/copilot/${AppName}/${EnvironmentName}/secrets/*'
       - Sid: ELBv2
         Effect: Allow
         Action: [
           "elasticloadbalancing:DescribeLoadBalancerAttributes",
           "elasticloadbalancing:DescribeSSLPolicies",
           "elasticloadbalancing:DescribeLoadBalancers",
           "elasticloadbalancing:DescribeTargetGroupAttributes",
           "elasticloadbalancing:DescribeListeners",
           "elasticloadbalancing:DescribeTags",
           "elasticloadbalancing:DescribeTargetHealth",
           "elasticloadbalancing:DescribeTargetGroups",
           "elasticloadbalancing:DescribeRules"
         ]
         Resource: "*"
       - Sid: BuiltArtifactAccess
         Effect: Allow
         Action: [
           "s3:ListBucketByTags",
           "s3:GetLifecycleConfiguration",
           "s3:GetBucketTagging",
           "s3:GetInventoryConfiguration",
           "s3:GetObjectVersionTagging",
           "s3:ListBucketVersions",
           "s3:GetBucketLogging",
           "s3:ListBucket",
           "s3:GetAccelerateConfiguration",
           "s3:GetBucketPolicy",
           "s3:GetObjectVersionTorrent",
           "s3:GetObjectAcl",
           "s3:GetEncryptionConfiguration",
           "s3:GetBucketRequestPayment",
           "s3:GetObjectVersionAcl",
           "s3:GetObjectTagging",
           "s3:GetMetricsConfiguration",
           "s3:HeadBucket",
           "s3:GetBucketPublicAccessBlock",
           "s3:GetBucketPolicyStatus",
           "s3:ListBucketMultipartUploads",
           "s3:GetBucketWebsite",
           "s3:ListJobs",
           "s3:GetBucketVersioning",
           "s3:GetBucketAcl",
           "s3:GetBucketNotification",
           "s3:GetReplicationConfiguration",
           "s3:ListMultipartUploadParts",
           "s3:GetObject",
           "s3:GetObjectTorrent",
           "s3:GetAccountPublicAccessBlock",
           "s3:ListAllMyBuckets",
           "s3:DescribeJob",
           "s3:GetBucketCORS",
           "s3:GetAnalyticsConfiguration",
           "s3:GetObjectVersionForReplication",
           "s3:GetBucketLocation",
           "s3:GetObjectVersion",
           "kms:Decrypt"
         ]
         Resource: "*"
       - Sid: EC2
         Effect: Allow
         Action: [
           "ec2:DescribeSubnets",
           "ec2:DescribeSecurityGroups",
           "ec2:DescribeNetworkInterfaces"
         ]
         Resource: "*"
       - Sid: AppRunner
         Effect: Allow
         Action: [
           "apprunner:DescribeService",
           "apprunner:ListOperations",
           "apprunner:ListServices",
           "apprunner:PauseService",
           "apprunner:ResumeService",
           "apprunner:StartDeployment"
         ]
         Resource: "*"
       - Sid: Tags
         Effect: Allow
         Action: [
           "tag:GetResources"
         ]
         Resource: "*"
       - Sid: ApplicationAutoscaling
         Effect: Allow
         Action: [
           "application-autoscaling:DescribeScalingPolicies"
         ]
         Resource: "*"
       - Sid: DeleteRoles
         Effect: Allow
         Action: [
           "iam:DeleteRole",
           "iam:ListRolePolicies",
           "iam:DeleteRolePolicy"
         ]
         Resource:
           - !GetAtt CloudformationExecutionRole.Arn
           - !Sub "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/${AWS::StackName}-EnvManagerRole"
       - Sid: DeleteEnvStack
         Effect: Allow
         Action:
           - 'cloudformation:DescribeStacks'
           - 'cloudformation:DeleteStack'
         Resource:
           - !Sub 'arn:${AWS::Partition}:cloudformation:${AWS::Region}:${AWS::AccountId}:stack/${AWS::StackName}/*'

CustomResourceRole

  CustomResourceRole:
    Type: AWS::IAM::Role
    Condition: DelegateDNS
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          -
            Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
            Action:
              - sts:AssumeRole
      Path: /
      Policies:
        - PolicyName: "DNSandACMAccess"
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - "acm:ListCertificates"
                  - "acm:RequestCertificate"
                  - "acm:DescribeCertificate"
                  - "acm:GetCertificate"
                  - "acm:DeleteCertificate"
                  - "acm:AddTagsToCertificate"
                  - "sts:AssumeRole"
                  - "logs:*"
                  - "route53:ChangeResourceRecordSets"
                  - "route53:Get*"
                  - "route53:Describe*"
                  - "route53:ListResourceRecordSets"
                  - "route53:ListHostedZonesByName"
                Resource:
                  - "*"

copilot job init

EnvControllerRole

EnvControllerRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
            Action:
              - sts:AssumeRole
      Path: /
      Policies:
        - PolicyName: "EnvControllerStackUpdate"
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - cloudformation:DescribeStacks
                  - cloudformation:UpdateStack
                Resource: !Sub 'arn:${AWS::Partition}:cloudformation:${AWS::Region}:${AWS::AccountId}:stack/${AppName}-${EnvName}/*'
                Condition:
                  StringEquals:
                    'cloudformation:ResourceTag/copilot-application': !Sub '${AppName}'
                    'cloudformation:ResourceTag/copilot-environment': !Sub '${EnvName}'
        - PolicyName: "EnvControllerRolePass"
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - iam:PassRole
                Resource: !Sub 'arn:${AWS::Partition}:iam::${AWS::AccountId}:role/${AppName}-${EnvName}-CFNExecutionRole'
                Condition:
                  StringEquals:
                    'iam:ResourceTag/copilot-application': !Sub '${AppName}'
                    'iam:ResourceTag/copilot-environment': !Sub '${EnvName}'
      ManagedPolicyArns:
        - !Sub arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole

ExecutionRole

  ExecutionRole:
    Metadata:
      'aws:copilot:description': 'An IAM Role for the Fargate agent to make AWS API calls on your behalf'
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Statement:
          - Effect: Allow
            Principal:
              Service: ecs-tasks.amazonaws.com
            Action: 'sts:AssumeRole'
      Policies:
        - PolicyName: !Join ['', [!Ref AppName, '-', !Ref EnvName, '-', !Ref WorkloadName, SecretsPolicy]]
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: 'Allow'
                Action:
                  - 'ssm:GetParameters'
                Resource:
                  - !Sub 'arn:${AWS::Partition}:ssm:${AWS::Region}:${AWS::AccountId}:parameter/*'
                Condition:
                  StringEquals:
                    'ssm:ResourceTag/copilot-application': !Sub '${AppName}'
                    'ssm:ResourceTag/copilot-environment': !Sub '${EnvName}'
              - Effect: 'Allow'
                Action:
                  - 'secretsmanager:GetSecretValue'
                Resource:
                  - !Sub 'arn:${AWS::Partition}:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:*'
                Condition:
                  StringEquals:
                    'secretsmanager:ResourceTag/copilot-application': !Sub '${AppName}'
                    'secretsmanager:ResourceTag/copilot-environment': !Sub '${EnvName}'
              - Effect: 'Allow'
                Action:
                  - 'kms:Decrypt'
                Resource:
                  - !Sub 'arn:${AWS::Partition}:kms:${AWS::Region}:${AWS::AccountId}:key/*'
      ManagedPolicyArns:
        - !Sub 'arn:${AWS::Partition}:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy'

TaskRole


TaskRole:
   Metadata:
     'aws:copilot:description': 'An IAM role to control permissions for the containers in your tasks'
    Type: AWS::IAM::Role
   Properties:
     ManagedPolicyArns:
       - Fn::GetAtt: [AddonsStack, Outputs.SqsAccessPolicyArn]
     AssumeRolePolicyDocument:
       Statement:
         - Effect: Allow
           Principal:
             Service: ecs-tasks.amazonaws.com
           Action: 'sts:AssumeRole'
     Policies:
       - PolicyName: 'DenyIAMExceptTaggedRoles'
         PolicyDocument:
           Version: '2012-10-17'
           Statement:
             - Effect: 'Deny'
               Action: 'iam:*'
               Resource: '*'
             - Effect: 'Allow'
               Action: 'sts:AssumeRole'
               Resource:
                 - !Sub 'arn:${AWS::Partition}:iam::${AWS::AccountId}:role/*'
               Condition:
                 StringEquals:
                   'iam:ResourceTag/copilot-application': !Sub '${AppName}'
                   'iam:ResourceTag/copilot-environment': !Sub '${EnvName}'
 Rule:
   Metadata:
     'aws:copilot:description': "A CloudWatch event rule to trigger the job's state machine"
   Type: AWS::Events::Rule
   Properties:
     ScheduleExpression: !Ref Schedule
     State: ENABLED
     Targets:
       - Arn: !Ref StateMachine
         Id: statemachine
         RoleArn: !GetAtt RuleRole.Arn

RuleRole

RuleRole:
   Type: AWS::IAM::Role
   Properties:
     AssumeRolePolicyDocument:
       Statement:
         - Effect: Allow
           Principal:
             Service: events.amazonaws.com
           Action: sts:AssumeRole
     Policies:
       - PolicyName: EventRulePolicy
         PolicyDocument:
           Statement:
             - Effect: Allow
               Action: states:StartExecution
               Resource: !Ref StateMachine
 StateMachine:
   Metadata:
     'aws:copilot:description': 'A state machine to invoke your job and handle retry and timeout logic'
   Type: AWS::StepFunctions::StateMachine
   Properties:
     StateMachineName: !Sub '${AppName}-${EnvName}-${WorkloadName}'
     RoleArn: !GetAtt StateMachineRole.Arn
     LoggingConfiguration:
       Destinations:
         - CloudWatchLogsLogGroup:
             LogGroupArn: !GetAtt LogGroup.Arn
       IncludeExecutionData: True
       Level: ALL
     DefinitionSubstitutions:
       ContainerName: !Ref WorkloadName
       Cluster:
         Fn::ImportValue: !Sub '${AppName}-${EnvName}-ClusterId'
       TaskDefinition: !Ref TaskDefinition
       Partition: !Ref AWS::Partition
       Subnets:
         Fn::Join:
           - '","'
           - Fn::Split:
               - ','
               - Fn::ImportValue: !Sub '${AppName}-${EnvName}-PublicSubnets'
       AssignPublicIp: ENABLED
       SecurityGroups:
         Fn::Join:
           - '","'
           - - Fn::ImportValue: !Sub "${AppName}-${EnvName}-EnvironmentSecurityGroup"
     DefinitionString: "{\n  \"Version\": \"1.0\",\n  \"Comment\": \"Run AWS Fargate task\",\n  \"StartAt\": \"Run Fargate Task\",\n  \"States\": {\n    \"Run Fargate Task\": {\n      \"Type\": \"Task\",\n      \"Resource\": \"arn:${Partition}:states:::ecs:runTask.sync\",\n      \"Parameters\": {\n        \"LaunchType\": \"FARGATE\",\n        \"PlatformVersion\": \"LATEST\",\n        \"Cluster\": \"${Cluster}\",\n        \"TaskDefinition\": \"${TaskDefinition}\",\n        \"PropagateTags\": \"TASK_DEFINITION\",\n        \"Group.$\": \"$$.Execution.Name\",\n        \"NetworkConfiguration\": {\n          \"AwsvpcConfiguration\": {\n            \"Subnets\": [\"${Subnets}\"],\n            \"AssignPublicIp\": \"${AssignPublicIp}\",\n            \"SecurityGroups\": [\"${SecurityGroups}\"]\n          }\n        }\n      },\n      \"End\": true\n    }\n  }\n}  

StateMachineRole

  StateMachineRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service: states.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: StateMachine
          PolicyDocument:
            Statement:
              - Effect: Allow
                Action: iam:PassRole
                Resource:
                  - !GetAtt ExecutionRole.Arn
                  - !GetAtt TaskRole.Arn
              - Effect: Allow
                Action: ecs:RunTask
                Resource: !Ref TaskDefinition
                Condition:
                  ArnEquals:
                    'ecs:cluster':
                      Fn::Sub:
                        - arn:${AWS::Partition}:ecs:${AWS::Region}:${AWS::AccountId}:cluster/${ClusterID}
                        - ClusterID:
                            Fn::ImportValue: !Sub '${AppName}-${EnvName}-ClusterId'
              - Effect: Allow
                Action:
                  - ecs:StopTask
                  - ecs:DescribeTasks
                Resource: "*"
                Condition:
                  ArnEquals:
                    'ecs:cluster':
                      Fn::Sub:
                        - arn:${AWS::Partition}:ecs:${AWS::Region}:${AWS::AccountId}:cluster/${ClusterID}
                        - ClusterID:
                            Fn::ImportValue: !Sub '${AppName}-${EnvName}-ClusterId'
              - Effect: Allow
                Action:
                  - logs:CreateLogDelivery
                  - logs:GetLogDelivery
                  - logs:UpdateLogDelivery
                  - logs:DeleteLogDelivery
                  - logs:ListLogDeliveries
                  - logs:PutResourcePolicy
                  - logs:DescribeResourcePolicies
                  - logs:DescribeLogGroups
                Resource: "*" # CWL doesn't support resource-level permissions
              - Effect: Allow
                Action:
                  - events:PutTargets
                  - events:PutRule
                  - events:DescribeRule
                Resource: !Sub arn:${AWS::Partition}:events:${AWS::Region}:${AWS::AccountId}:rule/StepFunctionsGetEventsForECSTaskRule
  AddonsStack:
    Metadata:
      'aws:copilot:description': 'An Addons CloudFormation Stack for your additional AWS resources'
    Type: AWS::CloudFormation::Stack # Needed for #1848
    DependsOn: EnvControllerAction
    Condition: HasAddons
    Properties:
      Parameters:
        App: !Ref AppName
        Env: !Ref EnvName
        Name: !Ref WorkloadName
      TemplateURL: !Ref AddonsTemplateURL

ログ

  • ロググループは/copilot/{{アプリケーション}}-{{環境}}-{{Job}}/
  • 失効の期間は1ヶ月

LoadBalancer

LoadBalancerタイプのサービスはap-northeast-1だとエラーが発生しました。

The following resource(s) failed to create: [PublicLoadBalancer].

原因としては以下の2つ(どちらかが発生)が考えられます。

(1)

The following resource(s) failed to create: [EnvControllerAction]. Rollback requested by user.

(2)

Creating the infrastructure for stack hello-world-test-hello-world                   [rollback complete]         [135.5s]
  The following resource(s) failed to create: [EnvControllerAction]. Rol
  lback requested by user.
  - Service discovery for your services to communicate within the VPC                  [delete complete]           [1.9s]
  - Update your environment's shared resources                                         [update rollback complete]  [31.1s]
    The following resource(s) failed to create: [PublicLoadBalancer].
    - A security group for your load balancer allowing HTTP and HTTPS traffic          [delete complete]           [2.0s]
    - An Application Load Balancer to distribute public traffic to your services       [delete complete]           [1.1s]
      The following Availability Zones ap-northeast-1b cannot be associated
      with a load balancer. Please try a different Availability Zone. (Servi
      ce: AmazonElasticLoadBalancing; Status Code: 400; Error Code: Validati
      onError; Request ID: ed795715-8ea8-49bd-b37c-2d20d7f7eee6; Proxy: null
      )

regionus-east-1に変更すると成功しました。

対策

  1. $ copilot initではdeployをしない(以下アプリケーション名がhello-worldとして進める)
  2. $ copilot env init --name test --region us-east-1 --app hello-world
  3. $ copilot svc deploy

アプリケーションを削除

  • $ copilot app deleteで一括して削除できるかもしれないがService/Job -> Environments -> Applicationの順で削除するのが安全
  • ↑の手順を踏めばcopilot app deleteで削除できない場合も削除できることがよくある
// Service/Jobを削除
$ copilot svc delete -n {{service name}}
$ copilot job delete -n {{job name}}
// Environmentsを削除
$ copilot env delete -n {{env name}}
// Applicationを削除
$ copilot app delete -n {{app name}}

以下でCloudFormationスタック、VPC、ECRなどが削除されます。

$ copilot app delete --name first-app

備考

本編ではcopilot initを使用する例を書いていますが、理解を深めるためにアプリケーションを作成 -> 環境を構築 -> サービスを追加 -> サービスをビルド&デプロイという流れについても記載します。

  1. アプリケーションを作成$ copilot app init
  2. 環境を構築:$ copilot env init
  3. サービスを追加:$ copilot svc init
  4. サービスをビルド&デプロイ:$ copilot svc deploy

アプリケーションを作成

$ copilot app init

Use existing application: No
Application name: first-app <====================== アプリケーション名を指定
✔ Created the infrastructure to manage services and jobs under application first-app..

✔ The directory copilot will hold service manifests for application first-app.

Recommended follow-up action:
    Run `copilot init` to add a new service or job to your application.

環境を構築

$ copilot env init --name test --app first-app --profile default \
  --override-vpc-cidr 10.10.0.0/16 \
  --override-public-cidrs 10.10.0.0/24,10.10.1.0/24 \
  --override-private-cidrs 10.10.2.0/24,10.10.3.0/24

サービスを追加

$ copilot svc init --app first-app

Note: It's best to run this command in the root of your workspace.
Service type: Request-Driven Web Service <=============================== サービスタイプを指定
Service name: nginx <==================================================== サービス名を指定
Dockerfile: Enter custom path for your Dockerfile <====================== Dockerfileを指定
Dockerfile: .docker/nginx/Dockerfile
✔ Manifest file for service nginx already exists at copilot/nginx/manifest.yml, skipping writing it.
Your manifest contains configurations like your container size and port (:80).

✔ Created ECR repositories for service nginx..

Recommended follow-up actions:
  - Update your manifest copilot/nginx/manifest.yml to change the defaults.
  - Run `copilot svc deploy --name nginx --env test` to deploy your service to a test environment.

サービスをビルド&デプロイ

copilot svc deploy --name nginx --app first-app --env test
Environment test is already on the latest version v1.6.1, skip upgrade.
[+] Building 0.4s (7/7) FINISHED
 => [internal] load build definition from Dockerfile                                                                                                                          0.1s
 => => transferring dockerfile: 36B                                                                                                                                           0.1s
 => [internal] load .dockerignore                                                                                                                                             0.0s
 => => transferring context: 2B                                                                                                                                               0.0s
 => [internal] load metadata for docker.io/library/nginx:latest                                                                                                               0.0s
 => [internal] load build context                                                                                                                                             0.0s
 => => transferring context: 94B                                                                                                                                              0.0s
 => [1/2] FROM docker.io/library/nginx:latest                                                                                                                                 0.0s
 => CACHED [2/2] COPY ./src/nginx /usr/share/nginx/html                                                                                                                       0.0s
 => exporting to image                                                                                                                                                        0.0s
 => => exporting layers                                                                                                                                                       0.0s
 => => writing image sha256:fe53993295dcd8a0342b8910c1125a386266b7b80523b07467987f0eb31e1a0d                                                                                  0.0s
 => => naming to xxxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/first-app/nginx                                                                                            0.0s

Use 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them
Login Succeeded
Using default tag: latest
The push refers to repository [xxxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/first-app/nginx]
e18564a26aa4: Pushed
feb57d363211: Pushed
98c84706d0f7: Pushed
4311f0ea1a86: Pushed
6d049f642241: Pushed
3158f7304641: Pushed
fd95118eade9: Pushed
latest: digest: sha256:0a3bdb8a81ecd4798ed72b10984ecd7522efbb0421f882680ee931cc76a6419a size: 1777
✔ Proposing infrastructure changes for stack first-app-test-nginx
- Creating the infrastructure for stack first-app-test-nginx                      [create complete]  [356.0s]
  - An IAM Role for App Runner to use on your behalf to pull your image from ECR  [create complete]  [22.6s]
  - An IAM role to control permissions for the containers in your service         [create complete]  [26.2s]
  - An App Runner service to run and manage your containers                       [create complete]  [319.4s]
✔ Deployed service nginx.
Recommended follow-up action:
    You can access your service at https://q6tymh4v8n.ap-northeast-1.awsapprunner.com over the internet.