CDKを使ってBatch環境の構築とECRへプッシュを行う

# 概要
Dockerイメージの作成、ECRリポジトリの作成・プッシュ、Batch環境(コンピューティング環境、ジョブキュー、ジョブ定義)の構築をCDKで行います。
コンピューティング環境の作成も行いますが、コンピューティング環境はCDKを使っても後からパラメータを変更することができず、パラメータを変えるには一度デストロイする必要があります。
そのためジョブキューやジョブ定義とは別で作成しておくことをお勧めします。


# 前提
CDKをデプロイするためのロールや環境は準備してあることを前提とした記事です。

# 用意しておくもの
- Dockerfile
    - 使用したいDockerfileをプロジェクトディレクトリから見て `./Dockerfile` に配置してください

## 既存のコンピューティング環境を使用したい場合
- コンピューティング環境
    - コンピューティング環境ARN

## コンピューティング環境の作成から行いたい場合
- VPC
    - VPC ID
- サブネット
    - サブネットID
    - アベイラビリティゾーン
    - ルートテーブルID
- セキュリティーグループ
    - セキュリティグループID

# 環境
- Typescript 4.1.2
- CDK 1.75.0

# CDKを書いていく
今回はサンプルとしてジョブ定義を利用してジョブを送信すると日付が表示されるバッチを作ります。
以降のコードはCDKの lib/cdk-stack.ts のconstructor()内に記述します。
最低限の設定しか行わないため、必要に合わせてパラメータを追加・変更してください。
[サンプルコード](https://github.com/ishizakit/sample-cdk-aws-batch)

## 1. コンピューティング環境の用意

### A. 既存のコンピューティング環境を使用したい場合

#### コンピューティング環境の取得

```typescript
const computeEnvironment: batch.ComputeEnvironment = batch.ComputeEnvironment.fromComputeEnvironmentArn(
  this, 'BatchCompute', 'arn:aws:batch:ap-northeast-1:0123456789:compute-environment/ExampleComputeEnvironment'
);
```

### B. 新規のコンピューティング環境を作成したい場合

#### ネットワーク情報の取得

コンピューティング環境の作成に使用するネットワーク周りの情報を取得します。

##### VPCの取得

```typescript
const vpc: ec2.IVpc = ec2.Vpc.fromLookup(this, 'VPC', {
  vpcId: 'vpc-0123456789abcdef',
});
```

##### サブネットの取得

```typescript
const selectSubnets: ec2.SelectedSubnets = vpc.selectSubnets({
  subnets: [
    ec2.Subnet.fromSubnetAttributes(this, 'Subnet', {
      subnetId: 'subnet-0123456789abcdef',
      availabilityZone: 'ap-northeast-1a',
      routeTableId: 'rtb-0123456789abcdef',
    }),
  ],
});
```

##### セキュリティグループの取得

```typescript
const securityGroup: ec2.ISecurityGroup = ec2.SecurityGroup.fromSecurityGroupId(
  this, 'SecurityGroup', 'sg-0123456789abcdef',
);
```

#### ロールの作成

バッチ実行に必要な各ロールを作成します。

##### Batch実行用のロール作成

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

##### インスタンス用のロール作成

```typescript
const instanceRole: iam.IRole = new iam.Role(this, 'InstanceRole', {
  roleName: 'ExampleInstanceRole',
  assumedBy: new iam.CompositePrincipal(
    new iam.ServicePrincipal('ec2.amazonaws.com'),
  ),
  managedPolicies: [
    iam.ManagedPolicy.fromManagedPolicyArn(
      this,
      'AmazonEC2ContainerServiceforEC2Role',
      'arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role',
    ),
  ],
});
```

##### インスタンスプロフィールを作成

```typescript
const instanceProfile: iam.CfnInstanceProfile = new iam.CfnInstanceProfile(this, 'InstanceProfile', {
  instanceProfileName: 'Example',
  roles: [instanceRole.roleName],
});
```

#### コンピューティング環境を作成

```typescript
const computeEnvironment: batch.ComputeEnvironment = new batch.ComputeEnvironment(this, 'BatchCompute', {
  computeEnvironmentName: 'ExampleComputeEnvironment',
  computeResources: {
    type: batch.ComputeResourceType.ON_DEMAND,
    instanceRole: instanceProfile.attrArn,
    vpc: vpc,
    vpcSubnets: selectSubnets,
    securityGroups: [securityGroup],
  },
  serviceRole: batchRole,
});
```

## 2. イメージの用意

### ビルドとプッシュ

ECR上にイメージをプッシュします。
指定したリポジトリがECRに存在していなければ、作成されるため事前にリポジトリ作成をしておく必要はありません。
またDockerfileを指定するだけでイメージのプッシュまでできるため、事前のビルドは不要です。

```typescript
const tag = '適当なタグ名'
const imageAsset: cdk.DockerImageAssetLocation = this.synthesizer.addDockerImageAsset({
sourceHash: tag,
  directoryName: `${__dirname}/../`,
  repositoryName: 'example',
})
```

## 3. ジョブキュー・ジョブロールの用意

### ジョブ実行用のロール作成

```typescript
const jobRole: iam.IRole = new iam.Role(this, 'JobRole', {
  roleName: 'ExampleJobRole',
  assumedBy: new iam.CompositePrincipal(
    new iam.ServicePrincipal('ecs-tasks.amazonaws.com'),
  ),
});
```

### ジョブキューを作成

```typescript
new batch.JobQueue(this, 'JobQueue', {
  jobQueueName: 'ExampleJobQueue',
  computeEnvironments: [{
      computeEnvironment: computeEnvironment,
      order: 1,
  }],
});
```

### ジョブ定義を作成

`date`コマンドを実行するジョブ定義を作成します。

```typescript
new batch.JobDefinition(this, 'JobDefinition', {
  jobDefinitionName: 'ExampleJobDefinition',
  container: {
    command: ['date'],  // コンテナ内で実行されるコマンド
    environment: {'TZ': 'Asia/Tokyo'},
    image: image,
    jobRole: jobRole,
    vcpus: 1,
    memoryLimitMiB: 100,
  },
});
```

# 環境が正しく作られたか確かめる

## ジョブを送信する

以下のコマンドでジョブキューにジョブを送信してみましょう。
AWSコンソールから送信することも可能です。

```bash
aws batch submit-job \
    --job-name "test" \
    --job-queue "arn:aws:batch:ap-northeast-1:0123456789:job-queue/ExampleJobQueue" \
    --job-definition "arn:aws:batch:ap-northeast-1:0123456789:job-definition/ExampleJobDefinition"
```

## ジョブが完了したことを確認する

AWSコンソールからジョブがsucceededにあることを確認しましょう。
ジョブを選択して、View logsから実行日時が出力されていることを確認できれば正常に環境構築ができています。
<img width="1151" alt="スクリーンショット 2020-06-08 15.33.01.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/297238/f7db394d-3daf-f823-d190-1e124cf36f14.png">