Amazon ECSで始める、Webサービス入門

本日(2022年10月8日)、[JAWS Days 2022](https://jaws-ug.doorkeeper.jp/events/140008)が行われた。この際、コンテナサービスのハンズオンに参加していたのだが、必要なリソース群をAWS CloudFormationでデプロイしようとして、とても時間を食ってしまっていたが、無事にハンズオンの範囲まで作成が完了したので報告する。

なお、元の資料は[ここ](https://shigeru-oda.github.io/jawsdays2022-container-handson/docs/#0)に講師が出しているので、参照せよ。

## ECS用のロールの作成

ハンズオンの通り。ただし、別にecs.amazonaws.comサービスが引き受け可能な、AmazonEC2ContainerServiceRoleポリシーをアタッチしたロールも作成しておくこと。これをやらないと、コンテナのマネージができないようだ。

## VPCテンプレート

今回は、3AZである前提で作成している。4AZ以上だったり、2AZしかない場合は、適宜テンプレートを修正せよ。

```yaml
AWSTemplateFormatVersion: "2010-09-09"
Description: VPC for ECS Hands-on
Parameters:
  VpcCIDR:
    Type: String
    Description: VPC CIDR address
    Default: 10.0.0.0/16
  EnableInterfaceEndpoint:
    Type: String
    Description: Enable Interface endpoint or not
    AllowedValues:
      - true
      - false
    Default: false
  TagName:
    Type: String
    Description: Tag name
    Default: ''
Conditions:
  HasTag:
    Fn::Not:
      - !Equals
        - !Ref TagName
        - ''
  EnabledInterfaceEndpoint: !Equals
    - !Ref EnableInterfaceEndpoint
    - true
Resources:
  Vpc:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !Ref VpcCIDR
      EnableDnsHostnames: true
      EnableDnsSupport: true
      Tags:
        -
          Key: Name
          Value: !Sub "${AWS::StackName}-Vpc"
        -
          Key: CostTag
          Value:
            Fn::If:
              - HasTag
              - !Ref TagName
              - !Ref AWS::StackName
  IPv6Block:
    Type: AWS::EC2::VPCCidrBlock
    Properties:
      VpcId: !Ref Vpc
      AmazonProvidedIpv6CidrBlock: true
  InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        -
          Key: Name
          Value: !Sub "${AWS::StackName}-InternetGateway"
        -
          Key: CostTag
          Value:
            Fn::If:
              - HasTag
              - !Ref TagName
              - !Ref AWS::StackName
  InternetGatewayAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref Vpc
      InternetGatewayId: !Ref InternetGateway
  EgressOnlyGateway:
    Type: AWS::EC2::EgressOnlyInternetGateway
    Properties:
      VpcId: !Ref Vpc
  PublicSubnet1:
    Type: AWS::EC2::Subnet
    DependsOn: IPv6Block
    Properties:
      VpcId: !Ref Vpc
      AvailabilityZone:
        Fn::Select:
          - 0
          - !GetAZs ''
      CidrBlock:
        Fn::Select:
          - 0
          -
            Fn::Cidr:
              - !GetAtt Vpc.CidrBlock
              - 6
              - 13
      Ipv6CidrBlock:
        Fn::Select:
          - 0
          -
            Fn::Cidr:
              -
                Fn::Select:
                  - 0
                  - !GetAtt Vpc.Ipv6CidrBlocks
              - 6
              - 64
      MapPublicIpOnLaunch: true
      PrivateDnsNameOptionsOnLaunch:
        EnableResourceNameDnsAAAARecord : true
        EnableResourceNameDnsARecord: true
      Tags:
        -
          Key: Name
          Value: !Sub "${AWS::StackName}-PublicSubnet1"
        -
          Key: CostTag
          Value:
            Fn::If:
              - HasTag
              - !Ref TagName
              - !Ref AWS::StackName
        -
          Key: SubnetType
          Value: Public
  PrivateSubnet1:
    Type: AWS::EC2::Subnet
    DependsOn: IPv6Block
    Properties:
      VpcId: !Ref Vpc
      AvailabilityZone:
        Fn::Select:
          - 0
          - !GetAZs ''
      CidrBlock:
        Fn::Select:
          - 1
          -
            Fn::Cidr:
              - !GetAtt Vpc.CidrBlock
              - 6
              - 13
      Ipv6CidrBlock:
        Fn::Select:
          - 1
          -
            Fn::Cidr:
              -
                Fn::Select:
                  - 0
                  - !GetAtt Vpc.Ipv6CidrBlocks
              - 6
              - 64
      AssignIpv6AddressOnCreation: true
      PrivateDnsNameOptionsOnLaunch:
        EnableResourceNameDnsAAAARecord : true
        EnableResourceNameDnsARecord: true
      Tags:
        -
          Key: Name
          Value: !Sub "${AWS::StackName}-PrivateSubnet1"
        -
          Key: CostTag
          Value:
            Fn::If:
              - HasTag
              - !Ref TagName
              - !Ref AWS::StackName
        -
          Key: SubnetType
          Value: Private
  PublicSubnet2:
    Type: AWS::EC2::Subnet
    DependsOn: IPv6Block
    Properties:
      VpcId: !Ref Vpc
      AvailabilityZone:
        Fn::Select:
          - 1
          - !GetAZs ''
      CidrBlock:
        Fn::Select:
          - 2
          -
            Fn::Cidr:
              - !GetAtt Vpc.CidrBlock
              - 6
              - 13
      Ipv6CidrBlock:
        Fn::Select:
          - 2
          -
            Fn::Cidr:
              -
                Fn::Select:
                  - 0
                  - !GetAtt Vpc.Ipv6CidrBlocks
              - 6
              - 64
      MapPublicIpOnLaunch: true
      PrivateDnsNameOptionsOnLaunch:
        EnableResourceNameDnsAAAARecord : true
        EnableResourceNameDnsARecord: true
      Tags:
        -
          Key: Name
          Value: !Sub "${AWS::StackName}-PublicSubnet2"
        -
          Key: CostTag
          Value:
            Fn::If:
              - HasTag
              - !Ref TagName
              - !Ref AWS::StackName
        -
          Key: SubnetType
          Value: Public
  PrivateSubnet2:
    Type: AWS::EC2::Subnet
    DependsOn: IPv6Block
    Properties:
      VpcId: !Ref Vpc
      AvailabilityZone:
        Fn::Select:
          - 1
          - !GetAZs ''
      CidrBlock:
        Fn::Select:
          - 3
          -
            Fn::Cidr:
              - !GetAtt Vpc.CidrBlock
              - 6
              - 13
      Ipv6CidrBlock:
        Fn::Select:
          - 3
          -
            Fn::Cidr:
              -
                Fn::Select:
                  - 0
                  - !GetAtt Vpc.Ipv6CidrBlocks
              - 6
              - 64
      AssignIpv6AddressOnCreation: true
      PrivateDnsNameOptionsOnLaunch:
        EnableResourceNameDnsAAAARecord : true
        EnableResourceNameDnsARecord: true
      Tags:
        -
          Key: Name
          Value: !Sub "${AWS::StackName}-PrivateSubnet2"
        -
          Key: CostTag
          Value:
            Fn::If:
              - HasTag
              - !Ref TagName
              - !Ref AWS::StackName
        -
          Key: SubnetType
          Value: Private
  PublicSubnet3:
    Type: AWS::EC2::Subnet
    DependsOn: IPv6Block
    Properties:
      VpcId: !Ref Vpc
      AvailabilityZone:
        Fn::Select:
          - 2
          - !GetAZs ''
      CidrBlock:
        Fn::Select:
          - 4
          -
            Fn::Cidr:
              - !GetAtt Vpc.CidrBlock
              - 6
              - 13
      Ipv6CidrBlock:
        Fn::Select:
          - 4
          -
            Fn::Cidr:
              -
                Fn::Select:
                  - 0
                  - !GetAtt Vpc.Ipv6CidrBlocks
              - 6
              - 64
      MapPublicIpOnLaunch: true
      PrivateDnsNameOptionsOnLaunch:
        EnableResourceNameDnsAAAARecord : true
        EnableResourceNameDnsARecord: true
      Tags:
        -
          Key: Name
          Value: !Sub "${AWS::StackName}-PublicSubnet3"
        -
          Key: CostTag
          Value:
            Fn::If:
              - HasTag
              - !Ref TagName
              - !Ref AWS::StackName
        -
          Key: SubnetType
          Value: Public
  PrivateSubnet3:
    Type: AWS::EC2::Subnet
    DependsOn: IPv6Block
    Properties:
      VpcId: !Ref Vpc
      AvailabilityZone:
        Fn::Select:
          - 2
          - !GetAZs ''
      CidrBlock:
        Fn::Select:
          - 5
          -
            Fn::Cidr:
              - !GetAtt Vpc.CidrBlock
              - 6
              - 13
      Ipv6CidrBlock:
        Fn::Select:
          - 5
          -
            Fn::Cidr:
              -
                Fn::Select:
                  - 0
                  - !GetAtt Vpc.Ipv6CidrBlocks
              - 6
              - 64
      AssignIpv6AddressOnCreation: true
      PrivateDnsNameOptionsOnLaunch:
        EnableResourceNameDnsAAAARecord : true
        EnableResourceNameDnsARecord: true
      Tags:
        -
          Key: Name
          Value: !Sub "${AWS::StackName}-PrivateSubnet3"
        -
          Key: CostTag
          Value:
            Fn::If:
              - HasTag
              - !Ref TagName
              - !Ref AWS::StackName
        -
          Key: SubnetType
          Value: Private
  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    DependsOn: IPv6Block
    Properties:
      VpcId: !Ref Vpc
      Tags:
        -
          Key: Name
          Value: !Sub "${AWS::StackName}-PublicRouteTable"
        -
          Key: CostTag
          Value:
            Fn::If:
              - HasTag
              - !Ref TagName
              - !Ref AWS::StackName
  PublicRouteV4:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PublicRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway
  PublicRouteV6:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PublicRouteTable
      DestinationIpv6CidrBlock: '::/0'
      GatewayId: !Ref InternetGateway
  PublicSubnet1RouteTableAssoc:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PublicRouteTable
      SubnetId: !Ref PublicSubnet1
  PublicSubnet2RouteTableAssoc:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PublicRouteTable
      SubnetId: !Ref PublicSubnet2
  PublicSubnet3RouteTableAssoc:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PublicRouteTable
      SubnetId: !Ref PublicSubnet3
  PrivateRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref Vpc
      Tags:
        -
          Key: Name
          Value: !Sub "${AWS::StackName}-PrivateRouteTable"
        -
          Key: CostTag
          Value:
            Fn::If:
              - HasTag
              - !Ref TagName
              - !Ref AWS::StackName
  PrivateEgressRoute:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PrivateRouteTable
      DestinationIpv6CidrBlock: '::/0'
      EgressOnlyInternetGatewayId: !Ref EgressOnlyGateway
  S3Endpoint:
    Type: AWS::EC2::VPCEndpoint
    Properties:
      ServiceName: !Sub "com.amazonaws.${AWS::Region}.s3"
      VpcId: !Ref Vpc
      VpcEndpointType: Gateway
      RouteTableIds:
        - !Ref PrivateRouteTable
  PrivateSubnet1RouteTableAssoc:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PrivateRouteTable
      SubnetId: !Ref PrivateSubnet1
  PrivateSubnet2RouteTableAssoc:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PrivateRouteTable
      SubnetId: !Ref PrivateSubnet2
  PrivateSubnet3RouteTableAssoc:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PrivateRouteTable
      SubnetId: !Ref PrivateSubnet3
  FrontSecGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Front security group
      VpcId: !Ref Vpc
      SecurityGroupIngress:
        -
          Description: Ingress IPv4 HTTP
          IpProtocol: tcp
          CidrIp: '0.0.0.0/0'
          FromPort: 80
          ToPort: 80
        -
          Description: Ingress IPv6 HTTP
          IpProtocol: tcp
          CidrIpv6: '::/0'
          FromPort: 80
          ToPort: 80
      Tags:
        -
          Key: Name
          Value: !Sub "${AWS::StackName}-FrontSecGroup"
        -
          Key: CostTag
          Value:
            Fn::If:
              - HasTag
              - !Ref TagName
              - !Ref AWS::StackName
  BackSecGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Backend security group
      VpcId: !Ref Vpc
      SecurityGroupIngress:
        -
          Description: Ingress IPv4 HTTP
          IpProtocol: tcp
          SourceSecurityGroupId: !Ref FrontSecGroup
          FromPort: 80
          ToPort: 80
      Tags:
        -
          Key: Name
          Value: !Sub "${AWS::StackName}-BackSecGroup"
        -
          Key: CostTag
          Value:
            Fn::If:
              - HasTag
              - !Ref TagName
              - !Ref AWS::StackName
  VpcEndpointSecGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: VPC endpoint security group
      VpcId: !Ref Vpc
      SecurityGroupIngress:
        -
          Description: Ingress IPv4 HTTPS
          IpProtocol: tcp
          CidrIp: '0.0.0.0/0'
          FromPort: 443
          ToPort: 443
      Tags:
        -
          Key: Name
          Value: !Sub "${AWS::StackName}-VpcEndpointSecGroup"
        -
          Key: CostTag
          Value:
            Fn::If:
              - HasTag
              - !Ref TagName
              - !Ref AWS::StackName
  DkrVpcEndpoint:
    Type: AWS::EC2::VPCEndpoint
    Condition: EnabledInterfaceEndpoint
    Properties:
      ServiceName: com.amazonaws.ap-northeast-1.ecr.dkr
      VpcId: !Ref Vpc
      VpcEndpointType: Interface
      PrivateDnsEnabled: true
      SecurityGroupIds:
        - !Ref VpcEndpointSecGroup
      SubnetIds:
        - !Ref PrivateSubnet1
        - !Ref PrivateSubnet2
        - !Ref PrivateSubnet3
  EcrApiVpcEndpoint:
    Type: AWS::EC2::VPCEndpoint
    Condition: EnabledInterfaceEndpoint
    Properties:
      ServiceName: com.amazonaws.ap-northeast-1.ecr.api
      VpcId: !Ref Vpc
      VpcEndpointType: Interface
      PrivateDnsEnabled: true
      SecurityGroupIds:
        - !Ref VpcEndpointSecGroup
      SubnetIds:
        - !Ref PrivateSubnet1
        - !Ref PrivateSubnet2
        - !Ref PrivateSubnet3
  LogsVpcEndpoint:
    Type: AWS::EC2::VPCEndpoint
    Condition: EnabledInterfaceEndpoint
    Properties:
      ServiceName: com.amazonaws.ap-northeast-1.logs
      VpcId: !Ref Vpc
      VpcEndpointType: Interface
      PrivateDnsEnabled: true
      SecurityGroupIds:
        - !Ref VpcEndpointSecGroup
      SubnetIds:
        - !Ref PrivateSubnet1
        - !Ref PrivateSubnet2
        - !Ref PrivateSubnet3
```

パラメータは、以下の3つをとる。

* VpcCIDR - VPCのCIDR。デフォルトは10.0.0.0/16で、ハンズオンの通りである
* EnableInterfaceEndpoint - インタフェースVPCエンドポイントを有効にするか。デフォルトはfalseで、デプロイしない。インタフェースVPCエンドポイントは有料なので、それだけシャットダウンするための仕組み。サービス稼働時にはtrueにすること
* TagName - コストタグ名。デフォルトではスタック名をそのまま用いる。VPCエンドポイントにはタグをつけられないので、後から手作業でつけること([こちらのIssue](https://github.com/aws-cloudformation/cloudformation-coverage-roadmap/issues/196)も参照せよ)

ハンズオンとの相違点としては、

* 2AZではなく3AZでデプロイしている
* インタフェースVPCエンドポイントに関しては専用のセキュリティグループに切り分け、バックエンドセキュリティグループに不要な443番ポートへのアクセスを除外している

## ECRテンプレート

```yaml
AWSTemplateFormatVersion: "2010-09-09"
Description: Amazon ECR
Parameters:
  TagName:
    Type: String
    Description: Tag name
    Default: ''
Conditions:
  HasTag:
    Fn::Not:
      - !Equals
        - !Ref TagName
        - ''
Resources:
  Repository:
    Type: AWS::ECR::Repository
    Properties:
      Tags:
        -
          Key: Name
          Value: !Sub "${AWS::StackName}-Repository"
        -
          Key: CostTag
          Value:
            Fn::If:
              - HasTag
              - !Ref TagName
              - !Ref AWS::StackName
```

パラメータは、TagNameの1つのみをとる。説明はVPCスタックと同様。

URLはデプロイ後に確認すること。

## コンテナのpush

ハンズオンの通りだが、URLが異なるのでその点に注意せよ。

## ECSテンプレート

```yaml
AWSTemplateFormatVersion: "2010-09-09"
Description: ECR Hands On Main
Parameters:
  ImageName:
    Type: String
    Description: Image name
  Vpc:
    Type: AWS::EC2::VPC::Id
    Description: VPC ID
  PublicSubnets:
    Type: List<AWS::EC2::Subnet::Id>
    Description: Public subnet IDs
  FrontSecGroup:
    Type: AWS::EC2::SecurityGroup::Id
    Description: Front security group ID
  PrivateSubnets:
    Type: List<AWS::EC2::Subnet::Id>
    Description: Private subnet IDs
  BackSecGroup:
    Type: AWS::EC2::SecurityGroup::Id
    Description: Back security group ID
  TagName:
    Type: String
    Description: Tag name
    Default: ''
Conditions:
  HasTag:
    Fn::Not:
      - !Equals
        - !Ref TagName
        - ''
Resources:
  Alb:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties:
      IpAddressType: dualstack
      Subnets: !Ref PublicSubnets
      SecurityGroups:
        - !Ref FrontSecGroup
      Type: application
      Tags:
        -
          Key: Name
          Value: !Sub "${AWS::StackName}-Alb"
        -
          Key: CostTag
          Value:
            Fn::If:
              - HasTag
              - !Ref TagName
              - !Ref AWS::StackName
  TargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      HealthCheckPath: /index.php
      HealthCheckProtocol: HTTP
      HealthCheckPort: traffic-port
      Protocol: HTTP
      Port: 80
      TargetType: ip
      VpcId: !Ref Vpc
      Tags:
        -
          Key: Name
          Value: !Sub "${AWS::StackName}-TargetGroup"
        -
          Key: CostTag
          Value:
            Fn::If:
              - HasTag
              - !Ref TagName
              - !Ref AWS::StackName
  AlbListener:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      DefaultActions:
        -
          Type: forward
          TargetGroupArn: !Ref TargetGroup
      LoadBalancerArn: !Ref Alb
      Port: 80
      Protocol: HTTP
  EcsCluster:
    Type: AWS::ECS::Cluster
    Properties:
      CapacityProviders:
        - FARGATE
      Tags:
        -
          Key: Name
          Value: !Sub "${AWS::StackName}-EcsCluster"
        -
          Key: CostTag
          Value:
            Fn::If:
              - HasTag
              - !Ref TagName
              - !Ref AWS::StackName
  LogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      RetentionInDays: 14
      Tags:
        -
          Key: Name
          Value: !Sub "${AWS::StackName}-LogGroup"
        -
          Key: CostTag
          Value:
            Fn::If:
              - HasTag
              - !Ref TagName
              - !Ref AWS::StackName
  EcsTaskDef:
    Type: AWS::ECS::TaskDefinition
    Properties:
      ExecutionRoleArn:
        !Sub "arn:aws:iam::${AWS::AccountId}:role/ecsTaskExecutionRole"
      NetworkMode: awsvpc
      RequiresCompatibilities:
        - FARGATE
      RuntimePlatform:
        CpuArchitecture: X86_64
        OperatingSystemFamily: LINUX
      Cpu: '256'
      Memory: '512'
      ContainerDefinitions:
        -
          Name: Web
          Image: !Ref ImageName
          PortMappings:
            -
              ContainerPort: 80
              HostPort: 80
              Protocol: tcp
          Essential: true
          LogConfiguration:
            LogDriver: awslogs
            Options:
              awslogs-create-group: 'true'
              awslogs-group: !Ref LogGroup
              awslogs-region: !Ref AWS::Region
              awslogs-stream-prefix: hands-on
      Tags:
        -
          Key: Name
          Value: !Sub "${AWS::StackName}-EcsTaskDef"
        -
          Key: CostTag
          Value:
            Fn::If:
              - HasTag
              - !Ref TagName
              - !Ref AWS::StackName
  EcsService:
    Type: AWS::ECS::Service
    DependsOn: AlbListener
    Properties:
      Cluster: !Ref EcsCluster
      TaskDefinition: !Ref EcsTaskDef
      DeploymentController:
        Type: CODE_DEPLOY
      DesiredCount: 2
      LaunchType: FARGATE
      PlatformVersion: LATEST
      NetworkConfiguration:
        AwsvpcConfiguration:
          SecurityGroups:
            - !Ref BackSecGroup
          Subnets: !Ref PrivateSubnets
      LoadBalancers:
        -
          ContainerName: Web
          ContainerPort: 80
          TargetGroupArn: !Ref TargetGroup
      Tags:
        -
          Key: Name
          Value: !Sub "${AWS::StackName}-EcsService"
        -
          Key: CostTag
          Value:
            Fn::If:
              - HasTag
              - !Ref TagName
              - !Ref AWS::StackName
```

パラメータは、以下の7つをとる。

* ImageName - 必須。イメージの名前。さっきpushしたDockerイメージのURLを指定する
* Vpc - VPCのID。最初に作ったVPCのIDを指定する
* PublicSubnets - 必須。パブリックサブネットのID群を指定する。CLIで指定する場合はカンマ区切りで指定する
* FrontSecGroup - 必須。フロントのセキュリティグループID(ハンズオンのパブリックサブネットのセキュリティグループ)
* PrivateSubnets - 必須。プライベートサブネットのID群を指定する。CLIで指定する場合はカンマ区切りで指定する
* BackSecGroup - 必須。バックエンドのセキュリティグループID(ハンズオンのプライベートサブネットのセキュリティグループ)
* TagName - コストタグ名。デフォルトではスタック名をそのまま用いる

デプロイの順序は、VPCスタック→ECRスタック→ECRイメージ→ECSスタックの順である。ただし、VPCスタックとECRスタック→ECRイメージは順不同である。

解体は、ECSスタック削除→ECRイメージ削除→VPCスタックのVPCエンドポイント無効化で、費用発生を止めることができる。

## CodeCommitレポジトリの作成

ここからは、資料で説明されている後半戦。

```yaml
AWSTemplateFormatVersion: "2010-09-09"
Description: AWS CodeCommit
Parameters:
  RepositoryName:
    Type: String
    Description: Repository name
  TagName:
    Type: String
    Description: Tag name
    Default: ''
Conditions:
  HasTag:
    Fn::Not:
      - !Equals
        - !Ref TagName
        - ''
Resources:
  Repository:
    Type: AWS::CodeCommit::Repository
    Properties:
      RepositoryName: !Ref RepositoryName
      RepositoryDescription: ECS Hands-on repository
      Tags:
        -
          Key: Name
          Value: !Sub "${AWS::StackName}-Repository"
        -
          Key: CostTag
          Value:
            Fn::If:
              - HasTag
              - !Ref TagName
              - !Ref AWS::StackName
```

パラメータは2つ。

* RepositoryName - 必須。レポジトリの名前を指定する。ハンズオンの通りにするなら、ContainerHandsOnと指定する
* TagName - コストタグ名。デフォルトではスタック名をそのまま用いる

## CodeCommitを触るのにGitクレデンシャルを用意せずにアクセスする方法

方法は以下の通り。

1. `pip install git-remote-codecommit`する。場合によってはpip3になったり、sudoが必要だったり、管理者権限が必要になるかもしれない
1. `git clone codecommit://[レポジトリ名]`する。プロファイルを名前付きにする場合は`codecommit://[プロファイル名]@[レポジトリ名]`する。リージョンが異なる場合、スキームが`codecommit::[リージョン名]`になる

以上の情報は[AWS公式](https://docs.aws.amazon.com/ja_jp/codecommit/latest/userguide/setting-up-git-remote-codecommit.html)が出している。