CDKでAWS Batch環境の作成 AWS Batch編

## 概要

CDKを使ってAWS環境を作成するときの方法についてまとめていこうと思います。
[前回](https://qiita.com/dmikita/items/40a5ce0df136a2e0e390)の続きです。
定義にはS3,DynamoDB,RDSの部分もありますがそのあたりは次回以降に・・・


## 準備

* 前回の転記部分もあります。詳細は前回を参照
* [AWS Summit](https://resources.awscloud.com/aws-summit-online-japan-2020-on-demand-self-paced-hands-on-85234)のCDKハンズオンを見て準備をしましょう。
* こちらでも簡単に準備手順を
    * AWS CLIのインストール 
    * AWS Configureの設定
        * アカウントはCDKで作成するリソースにアクセスできる権限を付与しましょう。(お試しでやる場合はAdministrator権限の方がいいかもしれません)
    * node.jsのインストール
    * `npm install -g aws-cdk` のコマンドでcdkをインストール

### プロジェクトの準備

* TypeScriptでの例となります。
1. 空のディレクトリを用意する。
1. ディレクトリに移動して以下のコマンドを実行する
    * `cdk init sample-app --language typescript`
1. 初期ディレクトリなどが作成されるのを待ちます。


## ライブラリのインストール
* 必要に応じて npm コマンドでライブラリをインストールします。以下のようなコマンドです。
    * `npm install @aws-cdk/aws-ecs`
    * `npm install @aws-cdk/aws-batch`
    * `npm install @aws-cdk/aws-ec2`
    * `npm install @aws-cdk/aws-iam`
    * `npm install @aws-cdk/aws-ecr`
* __注意点__
    * ライブラリのバージョンが一致していないとコンパイルエラーやdeploy時にエラーになるので `package.json` のバージョンを合わせて、  `package-lock.json` と `node_modules` 配下を削除して `npm install` を実施しましょう。

## コンパイルの準備
* ライブラリインストール後にプロジェクトを作成したディレクトリに移動して以下のコマンドを実行します。windowsの場合はライブラリのインストール時には停止する必要があるので注意を
    * `npm run watch`

## 各ソースの説明
* ./lib/cdk-stack.ts
    * 各定義を作成する場所です。基本的にこのファイルを修正します

### CDKの定義

* VPCなどを指定する部分はIDを指定します。

* ソース全体

<details><summary>長いので折りたたんでます</summary><div>

```typescript
import * as cdk from '@aws-cdk/core';
import * as batch from '@aws-cdk/aws-batch';
import * as dynamodb from '@aws-cdk/aws-dynamodb';
import * as rds from '@aws-cdk/aws-rds';
import * as s3 from '@aws-cdk/aws-s3';
import * as ec2 from '@aws-cdk/aws-ec2';
import * as iam from '@aws-cdk/aws-iam';
import * as ecs from '@aws-cdk/aws-ecs';
import * as ssm from '@aws-cdk/aws-ssm';
import * as ecr from '@aws-cdk/aws-ecr';
import { ContainerImage, EcrImage } from '@aws-cdk/aws-ecs';
import { Repository } from '@aws-cdk/aws-ecr';
import { ServerlessCluster, SubnetGroup } from '@aws-cdk/aws-rds';
import { Effect } from '@aws-cdk/aws-iam';


export class CdkStack extends cdk.Stack {

  constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {

    super(scope, id, props);
    

    const vpc: ec2.IVpc = ec2.Vpc.fromLookup(this, 'VPC', {
      vpcId: 'vpc-0890be0a4389aa3b3',
    });
    const selectSubnets: ec2.SelectedSubnets = vpc.selectSubnets({
      subnets: [
        ec2.Subnet.fromSubnetAttributes(this, 'Subnet', {
          subnetId: 'subnet-0f3d599ff3a439e8b',
          availabilityZone: 'ap-northeast-1a',
          routeTableId: 'rtb-0b3f9acea163735d2',
        }),
      ]
    });


    // 既存のセキュリティーグループを取得
    const securityGroup: ec2.ISecurityGroup = ec2.SecurityGroup.fromSecurityGroupId(
      this, 'SecurityGroup', 'sg-011b45aab947a7b11',
    );


    // バッチのロール
    const batchRole: iam.IRole = new iam.Role(this, 'BatchRole', {
      roleName: 'TestBatchRole',
      assumedBy: new iam.CompositePrincipal(
        new iam.ServicePrincipal('batch.amazonaws.com'),
      ),
      path:'/service-role/',
      managedPolicies: [
        iam.ManagedPolicy.fromManagedPolicyArn(
          this,
          'AWSBatchServiceRole',
          'arn:aws:iam::aws:policy/service-role/AWSBatchServiceRole',
        ),
      ],
    });

    // インスタンスのロール S3とDynamoDBへのアクセスを追加
    const instanceRole: iam.IRole = new iam.Role(this, 'InstanceRole', {
      roleName: 'TestInstanceRole',
      assumedBy: new iam.CompositePrincipal(
        new iam.ServicePrincipal('ec2.amazonaws.com'),
      ),

      managedPolicies: [
        iam.ManagedPolicy.fromManagedPolicyArn(
          this,
          'AmazonEC2ContainerServiceforEC2Role',
          'arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role',
        ),
        iam.ManagedPolicy.fromManagedPolicyArn(this,'AwsS3FullAccess','arn:aws:iam::aws:policy/AmazonS3FullAccess'),
        iam.ManagedPolicy.fromManagedPolicyArn(this,'AwsDynamoFullAccess','arn:aws:iam::aws:policy/AmazonDynamoDBFullAccess')
      ],
    });

    // Job用ロールを作成
    const jobRole: iam.IRole = new iam.Role(this, 'JobRole', {
      roleName: 'TestJobRole',
      assumedBy: new iam.CompositePrincipal(
        new iam.ServicePrincipal('ecs-tasks.amazonaws.com'),
      ),
      managedPolicies: [
        iam.ManagedPolicy.fromManagedPolicyArn(
          this,
          'AmazonECSTaskExecutionRolePolicy',
          'arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy',
        ),
      ],
    });

    // インスタンスプロフィールを作成
    const instanceProfile: iam.CfnInstanceProfile = new iam.CfnInstanceProfile(this, 'InstanceProfile', {
      instanceProfileName: 'Testprofile',
      roles: [instanceRole.roleName],
     path:'/'
    });

    // コンピューティング環境を作成
    const computeEnvironment: batch.ComputeEnvironment = new batch.ComputeEnvironment(this, 'BatchCompute', {
      computeEnvironmentName: 'TestComputeEnvironment',
      computeResources: {
        type: batch.ComputeResourceType.ON_DEMAND,
        instanceRole: instanceProfile.attrArn,
        vpc: vpc,
        vpcSubnets: selectSubnets,
        securityGroups: [securityGroup],
      },
      serviceRole:batchRole
    });
    const repository = Repository.fromRepositoryName(this,'pandastestRepo','pandastest2')
    const ecscontainerImage =  ecs.ContainerImage.fromEcrRepository( repository,'latest')

    // jobのQueを作成
    new batch.JobQueue(this, 'JobQueue', {
      jobQueueName: 'jobQueque',
      computeEnvironments: [{
          computeEnvironment: computeEnvironment,
          order: 1,
      }],
    });

    const jobDefinec = new batch.JobDefinition(this, 'JobDefinition', {
      jobDefinitionName: 'pandasTest',
      container: {
        command: ['1','3','A'],  // コンテナ内で実行されるコマンド
        environment: {'RDS_PASS': 'password','TEST':'ABC'},
        image: ecscontainerImage,
        jobRole: jobRole,
        vcpus: 1,
        memoryLimitMiB: 32,
      },
    });


    //  RDS周り
    const selectRdsSubnets: ec2.SelectedSubnets = vpc.selectSubnets({
      subnets: [
        ec2.Subnet.fromSubnetAttributes(this, 'Subnet1', {
          subnetId: 'subnet-0f3d599ff3a439e8b',
          availabilityZone: 'ap-northeast-1a',
          routeTableId: 'rtb-0b3f9acea163735d2',
        }),
        ec2.Subnet.fromSubnetAttributes(this, 'Subnet2', {
          subnetId: 'subnet-08a1d1ea639bb8876',
          availabilityZone: 'ap-northeast-1a',
          routeTableId: 'rtb-0b3f9acea163735d2',
        }),
      ]
    });

    const subnetGroup=new rds.SubnetGroup(this,'subnetgroup',{
      vpc:vpc,
      description:'for rds subnetGroup',
      subnetGroupName:'sdfsubnetgroup',
      vpcSubnets:selectRdsSubnets
    })



    
    const dbname='testdb';
    const masterUsername='root';
    const masterPassword='password';

    const rdsCluster = new rds.CfnDBCluster(this, 'DBCluster', { 
      engine: 'aurora-mysql',
      dbClusterIdentifier: `main-cluster`,
      engineMode: 'serverless',
      engineVersion: '5.7.mysql_aurora.2.07.1',
      enableHttpEndpoint: false,
      databaseName: dbname,
      masterUsername: masterUsername,
      masterUserPassword: masterPassword,
      backupRetentionPeriod:  1 ,
      dbSubnetGroupName:subnetGroup.subnetGroupName,
      scalingConfiguration: {
        autoPause: true,
        maxCapacity:  2 ,
        minCapacity: 1,
        secondsUntilAutoPause:  600 ,
      },
      deletionProtection:  false ,
    });

    // S3
    const dataBucket = new s3.Bucket(this, 'pf-bucket-data', {
      bucketName: `bucket-data-${this.region}-${this.account}`,
      blockPublicAccess:s3.BlockPublicAccess.BLOCK_ALL
  });
  // S3 BucketPolicy
  const dataBucketPolicy = new s3.BucketPolicy(this, 'pf-bucket-data-policy', {
      bucket : dataBucket,
  });

const s3dataStatement = new iam.PolicyStatement({
  effect: Effect.ALLOW,
      actions:['s3:ListBucket','s3:PutObject','s3:ListBucket'],
      resources:[
        "arn:aws:s3:::" + dataBucket.bucketName,
        "arn:aws:s3:::" + dataBucket.bucketName + "/*"
      ],
      principals:[new iam.AccountPrincipal(this.account)]
  })
    const s3Statement = new iam.PolicyStatement({
      effect: Effect.ALLOW,
          actions:['s3:ListBucket','s3:PutObject','s3:ListBucket'],
          resources:[
            "arn:aws:s3:::" + dataBucket.bucketName,
            "arn:aws:s3:::" + dataBucket.bucketName + "/*"
          ]
      })

      const s3Statementssm = new iam.PolicyStatement({
        effect: Effect.ALLOW,
            actions:['ssm:DescribeParameters','ssm:GetParameter'],
            resources:['*']
        })
  

  dataBucketPolicy.document.addStatements(s3dataStatement)
  
  

  const sdfDynamoDB=new dynamodb.Table(this,'sdfCountTable',{
    partitionKey: {
      name: "sequence_key",
      type: dynamodb.AttributeType.STRING,
    },
    tableName:'sequence_table_cdk',
    billingMode:dynamodb.BillingMode.PROVISIONED,
    removalPolicy: cdk.RemovalPolicy.DESTROY,
    readCapacity:2,
    writeCapacity:2,
    

  })


    const dynamoDbStatement = new iam.PolicyStatement({
      effect: Effect.ALLOW,
      actions:[
        "dynamodb:BatchGetItem",
        "dynamodb:BatchWriteItem",
        "dynamodb:PutItem",
        "dynamodb:ListTables",
        "dynamodb:GetItem",
        "dynamodb:Scan",
        "dynamodb:Query",
        "dynamodb:UpdateItem",
        "dynamodb:UpdateTable",
        "dynamodb:GetRecords"
      ],resources:[sdfDynamoDB.tableArn]
    })
    
      const s3DynamoDBPolicy= new iam.ManagedPolicy(this,'s3Dynamo',{
        managedPolicyName:'s3dynamodbpolicy',
        statements:[s3Statement,dynamoDbStatement],
        description:'for s3Dynamo',
      })

  }
}



```
</div></details>

### AWS Batchの部分

* AWS Batchの部分だけピックアップします
* job定義に設定するロールの設定、コンピューティング環境に設定するインスタンスプロフィールは以下のように作成します。
    * 必要に応じて権限を追加したりしてください。
    

```typescript
    // バッチのロール
    const batchRole: iam.IRole = new iam.Role(this, 'BatchRole', {
      roleName: 'TestBatchRole',
      assumedBy: new iam.CompositePrincipal(
        new iam.ServicePrincipal('batch.amazonaws.com'),
      ),
      path:'/service-role/',
      managedPolicies: [
        iam.ManagedPolicy.fromManagedPolicyArn(
          this,
          'AWSBatchServiceRole',
          'arn:aws:iam::aws:policy/service-role/AWSBatchServiceRole',
        ),
      ],
    });

    // インスタンスのロール S3とDynamoDBへのアクセスを追加
    const instanceRole: iam.IRole = new iam.Role(this, 'InstanceRole', {
      roleName: 'TestInstanceRole',
      assumedBy: new iam.CompositePrincipal(
        new iam.ServicePrincipal('ec2.amazonaws.com'),
      ),

      managedPolicies: [
        iam.ManagedPolicy.fromManagedPolicyArn(
          this,
          'AmazonEC2ContainerServiceforEC2Role',
          'arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role',
        ),
        iam.ManagedPolicy.fromManagedPolicyArn(this,'AwsS3FullAccess','arn:aws:iam::aws:policy/AmazonS3FullAccess'),
        iam.ManagedPolicy.fromManagedPolicyArn(this,'AwsDynamoFullAccess','arn:aws:iam::aws:policy/AmazonDynamoDBFullAccess')
      ],
    });

    // Job用ロールを作成
    const jobRole: iam.IRole = new iam.Role(this, 'JobRole', {
      roleName: 'TestJobRole',
      assumedBy: new iam.CompositePrincipal(
        new iam.ServicePrincipal('ecs-tasks.amazonaws.com'),
      ),
      managedPolicies: [
        iam.ManagedPolicy.fromManagedPolicyArn(
          this,
          'AmazonECSTaskExecutionRolePolicy',
          'arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy',
        ),
      ],
    });

    // インスタンスプロフィールを作成
    const instanceProfile: iam.CfnInstanceProfile = new iam.CfnInstanceProfile(this, 'InstanceProfile', {
      instanceProfileName: 'Testprofile',
      roles: [instanceRole.roleName],
     path:'/'
    });
```
* コンピューティング環境の設定
    * インスタンスのロールやサブネットなどをこちらで設定します。

```typescript
    // コンピューティング環境を作成
    const computeEnvironment: batch.ComputeEnvironment = new batch.ComputeEnvironment(this, 'BatchCompute', {
      computeEnvironmentName: 'TestComputeEnvironment',
      computeResources: {
        type: batch.ComputeResourceType.ON_DEMAND,
        instanceRole: instanceProfile.attrArn,
        vpc: vpc,
        vpcSubnets: selectSubnets,
        securityGroups: [securityGroup],
      },
      serviceRole:batchRole
    });
```
* リポジトリの指定
    * 新規でリポジトリを作るところも行う場合は new で作成します。今回はすでにあるリポジトリを使います。

```typescript
    const repository = Repository.fromRepositoryName(this,'pandastestRepo','pandastest2')
    const ecscontainerImage =  ecs.ContainerImage.fromEcrRepository( repository,'latest')
```
* JobのキューとJob定義
    * ジョブ定義の時に使用するCPUやメモリなどを指定します、

```typescript
    // jobのQueを作成
    new batch.JobQueue(this, 'JobQueue', {
      jobQueueName: 'jobQueque',
      computeEnvironments: [{
          computeEnvironment: computeEnvironment,
          order: 1,
      }],
    });

    const jobDefinec = new batch.JobDefinition(this, 'JobDefinition', {
      jobDefinitionName: 'pandasTest',
      container: {
        command: ['1','3','A'],  // コンテナ内で実行されるコマンド
        environment: {'RDS_PASS': 'password','TEST':'ABC'},
        image: ecscontainerImage,
        jobRole: jobRole,
        vcpus: 1,
        memoryLimitMiB: 32,
      },
    });
```