Terraform で書く CodeBuild -> ECR -> ECS with Vapor その1

`Terraform v0.12.1`

Swift製 WebFrameWork である Vapor をECS へのデプロイするところまでをTerraformの練習として書きました。
解説記事というよりは個人的な忘備録、メモになります。

Terraform が初めての場合は Terraform 公式のチュートリアルにまず取り掛かる事をお勧めします。
Terraform はバージョンによる差異も大きく、そしてどんどん良いものになっているため、これから始めるのであれば最新版を使うことをお勧めします。

[Introduction to Terraform](https://www.terraform.io/intro/index.html "Introduction to Terraform")


### アプリケーションの作成

まずECS上で動かすためのアプリケーションが必要です。
Vapor のテンプレートをアプリケーションとして使用します。
今回は以下のような Dockerfile を作成し、この Dockerfile により作られるイメージをECS で動かすことを目的とします。

https://github.com/O-Junpei/vapor-docker-sample

```dockerfile:Dockerfile
FROM ubuntu:16.04

RUN apt-get update && apt-get install -y \
  git \
  vim \
  wget \
  curl
RUN /bin/bash -c "$(wget -qO- https://apt.vapor.sh)"
RUN apt-get install -y \
  swift \
  vapor

RUN vapor new Hello --template=web
WORKDIR /Hello
RUN vapor build

EXPOSE 80

CMD ["swift", "run", "Run", "--hostname", "0.0.0.0", "--port", "80"]
```


### Terraform の設定

次に Terraform から AWS にアクセスできることを確認します。


```tf
########################
## Credential Infos
########################
variable "access_key" {
  description = "AWS access key"
}

variable "secret_key" {
  description = "AWS secret access key"
}

variable "region" {
  description = "AWS region to host your network"
}

provider "aws" {
  access_key = "${var.access_key}"
  secret_key = "${var.secret_key}"
  region     = "${var.region}"
}

```


### ECRの作成

CodeBuild で作成したイメージを格納する ECR を作成します。

```tf
########################
## ECR
########################
# ECS Repository
resource "aws_ecr_repository" "repository" {
  name = "my-repository"
}

# Repositry Policy
# Permit pull image
resource "aws_ecr_repository_policy" "repository_policy" {
  repository = "${aws_ecr_repository.repository.name}"

  policy = <<EOF
  {
    "Version": "2008-10-17",
    "Statement": [
      {
        "Sid": "new statement",
        "Effect": "Allow",
        "Principal": "*",
        "Action": [
          "ecr:BatchCheckLayerAvailability",
          "ecr:BatchGetImage",
          "ecr:GetDownloadUrlForLayer"
        ]
      }
    ]
  }
EOF
}
```

### CodeBuildProjectの作成

CodeBuildProject を作成します。

```tf
########################
## CodeBuild
########################
data "aws_iam_policy_document" "codebuild_assume_role_policy_document" {
  statement {
    sid = "CodebuildExecution"
    actions = ["sts:AssumeRole"]

    principals {
      type = "Service"
      identifiers = ["codebuild.amazonaws.com"]
    }
  }
}

resource "aws_iam_role" "codebuild_role" {
  name = "my-codebuild"
  assume_role_policy = "${data.aws_iam_policy_document.codebuild_assume_role_policy_document.json}"
}

data "aws_iam_policy_document" "codebuild_policy_document" {
  statement {
    sid = "Logging"
    actions = [
      "logs:CreateLogGroup",
      "logs:CreateLogStream",
      "logs:PutLogEvents",
    ]
    resources = ["*"]
  }

  statement {
    sid = "PushECR"
    actions = [
      "ecr:GetAuthorizationToken",
      "ecr:BatchCheckLayerAvailability",
      "ecr:GetDownloadUrlForLayer",
      "ecr:GetRepositoryPolicy",
      "ecr:DescribeRepositories",
      "ecr:ListImages",
      "ecr:DescribeImages",
      "ecr:BatchGetImage",
      "ecr:InitiateLayerUpload",
      "ecr:UploadLayerPart",
      "ecr:CompleteLayerUpload",
      "ecr:PutImage",
    ]
    resources = ["*"]
  }
}

resource "aws_iam_role_policy" "codebuild_role_policy" {
  role = "${aws_iam_role.codebuild_role.name}"
  policy = "${data.aws_iam_policy_document.codebuild_policy_document.json}"
}

# templete などを別ファイルに書き出すことをおすすめです。
data "template_file" "buildspec_template_file" {
  template = <<EOF
version: 0.2

phases:
  pre_build:
    commands:
      - echo pre_build
      - echo Logging in to Amazon ECR...
      - $(aws ecr get-login --no-include-email --region $AWS_DEFAULT_REGION)

  build:
    commands:
      - echo build
      - echo build docker image
      - docker build -t vapor:latest .

  post_build:
    commands:
      - echo post_build
      - docker tag vapor:latest ${aws_ecr_repository.repository.repository_url}:latest
      - docker push ${aws_ecr_repository.repository.repository_url}:latest
EOF
}

resource "aws_codebuild_project" "codebuild_project" {
  name          = "build-vapor-image"
  description   = "build vapor image"
  build_timeout = "30"
  service_role  = "${aws_iam_role.codebuild_role.arn}"

  artifacts {
    type = "NO_ARTIFACTS"
  }

  cache {
    type  = "LOCAL"
    modes = ["LOCAL_DOCKER_LAYER_CACHE", "LOCAL_SOURCE_CACHE"]
  }

  environment {
    compute_type                = "BUILD_GENERAL1_SMALL"
    image                       = "aws/codebuild/docker:18.09.0"
    type                        = "LINUX_CONTAINER"
    privileged_mode             = true
    image_pull_credentials_type = "CODEBUILD"
  }

  source {
    type = "GITHUB"
    # プライベートリポジトリを使う場合はOAuthToken を設定する
    # パラメーターストアに保存し、取ってくるのが簡単
    # auth {
    #   type     = "OAUTH"
    #   resource = "${aws_ssm_parameter.github_oauth_token.value}"
    # }
    location  = "https://github.com/O-Junpei/vapor-docker-sample.git"
    buildspec = "${data.template_file.buildspec_template_file.rendered}"
  }
}
```

その2に続きます。