GeekFactory

int128.hatenablog.com

terraform-aws-modules/acm/aws でTLS証明書を取得する

terraform-aws-modules/acm/aws を使うとACMの証明書を簡単に取得できます.コンソール作業が一切必要ないのがうれしい.

例えば,ドメイン foo.example.com のRoute53 Hosted Zoneが存在する前提で *.foo.example.comTLS証明書を取得するには,以下のコードを追加します.

module "acm" {
  source = "terraform-aws-modules/acm/aws"

  domain_name = "*.foo.example.com"
  zone_id     = data.aws_route53_zone.service.zone_id
}

data "aws_route53_zone" "service" {
  name = "foo.example.com"
}

Terraformを実行すると以下のリソースが作成されます.

  # module.acm.aws_acm_certificate.this[0] will be created
  + resource "aws_acm_certificate" "this" {
      + arn                       = (known after apply)
      + domain_name               = "*.foo.example.com"
      + domain_validation_options = (known after apply)
      + id                        = (known after apply)
      + subject_alternative_names = []
      + validation_emails         = (known after apply)
      + validation_method         = "DNS"
    }

  # module.acm.aws_acm_certificate_validation.this[0] will be created
  + resource "aws_acm_certificate_validation" "this" {
      + certificate_arn         = (known after apply)
      + id                      = (known after apply)
      + validation_record_fqdns = (known after apply)
    }

  # module.acm.aws_route53_record.validation[0] will be created
  + resource "aws_route53_record" "validation" {
      + allow_overwrite = true
      + fqdn            = (known after apply)
      + id              = (known after apply)
      + name            = (known after apply)
      + records         = (known after apply)
      + ttl             = 60
      + type            = (known after apply)
      + zone_id         = "YOUR_ZONE_ID"
    }

...

module.acm.aws_acm_certificate.this[0]: Creation complete after 7s [id=arn:aws:acm:us-east-1:ID:certificate/ID]
module.acm.aws_route53_record.validation[0]: Creating...
module.acm.aws_route53_record.validation[0]: Still creating... [10s elapsed]
module.acm.aws_route53_record.validation[0]: Still creating... [20s elapsed]
module.acm.aws_route53_record.validation[0]: Still creating... [30s elapsed]
module.acm.aws_route53_record.validation[0]: Creation complete after 38s [id=ZONE_ID.foo.example.com._CNAME]
module.acm.aws_acm_certificate_validation.this[0]: Creating...
module.acm.aws_acm_certificate_validation.this[0]: Still creating... [10s elapsed]
module.acm.aws_acm_certificate_validation.this[0]: Still creating... [20s elapsed]
module.acm.aws_acm_certificate_validation.this[0]: Still creating... [30s elapsed]
module.acm.aws_acm_certificate_validation.this[0]: Still creating... [40s elapsed]
module.acm.aws_acm_certificate_validation.this[0]: Still creating... [50s elapsed]
module.acm.aws_acm_certificate_validation.this[0]: Still creating... [1m0s elapsed]
module.acm.aws_acm_certificate_validation.this[0]: Creation complete after 1m10s [id=2019-09-19 02:12:32 +0000 UTC]

モジュールの詳細は terraform-aws-modules/acm/aws を参照してください.

Kubernetes上のGitLab Runnerでビルドキャッシュを利用する

GitLab CI/CDではビルドキャッシュがサポートされています.本稿では,KubernetesにデプロイしているGitLab Runnerでビルドキャッシュを利用する方法を紹介します.

ここでは以下を利用している前提とします.

  • AWS
  • Kubernetes 1.13 (EKS)
  • kube2iam
  • Terraform 0.12
  • Helmfile v0.80 or later

以下の流れでビルドキャッシュを構成していきます.

  1. ビルドキャッシュ用のS3バケットを作成する.
  2. GitLab RunnerのIAMロールを作成する.
  3. GitLab Runnerが生成するworkerのIAMロールを作成する.
  4. GitLab Runnerをデプロイする.
  5. ジョブを設定する.

1. ビルドキャッシュ用のS3バケットを作成する

GitLab Runnerはデフォルトではローカルにビルドキャッシュを保存しますが,GitLab Runnerを使い捨てにする場合はビルドキャッシュが消えてしまいます. S3バケットを利用すると複数のGitLab Runnerでビルドキャッシュを共有できます.

以下のようにTerraformでS3バケットを作成します.

# S3 bucket for cache of GitLab Runner
# https://gitlab.com/gitlab-org/gitlab-runner/blob/master/docs/configuration/autoscale.md#distributed-runners-caching
resource "aws_s3_bucket" "gitlab_runner_cache" {
  bucket = "${var.cluster_name}-gitlab-runner-cache"
  acl    = "private"
}

2. GitLab RunnerのIAMロールを作成する

GitLab Runnerはジョブの最初にビルドキャッシュを取得します.また,ジョブの最後にビルドキャッシュを保存します.そこで,ビルドキャッシュ用のS3バケットを読み書きできるIAMロールを作成し,GitLab Runnerにアタッチします.正確には,GitLab Runnerが生成するworkerではなく,GitLab Runnerの本体にIAMロールをアタッチする必要があります.

以下のようにTerraformでIAMロールとIAMポリシーを作成します.

# IAM role for GitLab Runner
resource "aws_iam_role" "gitlab_runner" {
  name               = "${var.cluster_name}-gitlab-runner"
  assume_role_policy = data.aws_iam_policy_document.kube2iam_assume_role.json
}

resource "aws_iam_role_policy" "gitlab_runner" {
  role   = aws_iam_role.gitlab_runner.name
  policy = <<EOF
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:PutObject",
                "s3:GetObject",
                "s3:ListBucket",
                "s3:DeleteObject"
            ],
            "Resource": [
                "${aws_s3_bucket.gitlab_runner_cache.arn}/*",
                "${aws_s3_bucket.gitlab_runner_cache.arn}"
            ]
        }
    ]
}
EOF
}

data "aws_iam_policy_document" "kube2iam_assume_role" {
  statement {
    actions = ["sts:AssumeRole"]
    principals {
      type        = "Service"
      identifiers = ["ec2.amazonaws.com"]
    }
    principals {
      type        = "AWS"
      identifiers = [module.eks.worker_iam_role_arn]
    }
  }
}

3. GitLab Runnerが生成するworkerのIAMロールを作成する

GitLab Runnerはジョブを実行する時に新しいPodを生成します.ジョブで指定したイメージやスクリプトはPod内で実行されます.

以下のようにTerraformでIAMロールを作成します.

# IAM role for building app on GitLab Runner
resource "aws_iam_role" "gitlab_runner_app" {
  name               = "${var.cluster_name}-gitlab-runner-app"
  assume_role_policy = data.aws_iam_policy_document.kube2iam_assume_role.json
}

ここでは特に権限を割り当てていませんが,将来的に権限が必要になった場合はこのIAMロールに権限を割り当てます. 例えば,ECRにイメージを保存する必要が出てきた場合は以下のIAMポリシーをアタッチします.

resource "aws_iam_role_policy_attachment" "gitlab_runner_app_ecr" {
  role       = aws_iam_role.gitlab_runner_app.name
  policy_arn = "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryPowerUser"
}

4. GitLab Runnerをデプロイする

GitLab RunnerにはHelm Chartが用意されています.

以下のようにHelmfileでChartをデプロイします.

releases:
  - name: gitlab-runner-app
    namespace: gitlab
    chart: gitlab/gitlab-runner
    values:
      - gitlabUrl: {{ .Environment.Values.gitlabURL }}
        runnerRegistrationToken: {{ .Environment.Values.gitlabRunnerRegistrationToken }}
        rbac:
          create: true
        podAnnotations:
          # IAM role for GitLab Runner manager pod
          iam.amazonaws.com/role: CLUSTER-gitlab-runner
        runners:
          tags: "app"
          cache:
            # Distributed runners caching
            # https://gitlab.com/gitlab-org/gitlab-runner/blob/master/docs/configuration/advanced-configuration.md#the-runnerscaches3-section
            cacheType: s3
            cacheShared: true
            s3ServerAddress: s3.amazonaws.com
            s3BucketName: {{ .Environment.Values.cacheBucketName }}
            s3BucketLocation: {{ .Environment.Values.cacheBucketRegion }}
          podAnnotations:
            # IAM role for building apps
            iam.amazonaws.com/role: CLUSTER-gitlab-runner-app

environments:
  default:
    values:
      - gitlabURL: https://gitlab.example.com
        gitlabRunnerRegistrationToken: YOUR_TOKEN
        cacheBucketName: CLUSTER-gitlab-runner-cache
        cacheBucketRegion: ap-northeast-1

ポイントは以下です.

  • podAnnotations でGitLab Runnerの本体に割り当てるIAMロールを指定します.
  • runners.podAnnotations でGitLab Runnerが生成するworkerに割り当てるIAMロールを指定します.
  • runners.cache.cacheType には s3 を指定します.これを忘れるとS3バケットが有効になりません.
  • runners.cache.cacheShared はtrueにします.これを忘れるとビルドキャッシュが別のGitLab Runnerに引き継がれません.

GitLab Runnerでビルドキャッシュを設定する方法は以下のドキュメントが参考になります.

5. ジョブを設定する

GitLabのリポジトリ.gitlab-ci.yml を作成します.WebIDEでファイルを作成すると,ビルドジョブの動作をすぐに確認できるので便利です.

build:
  cache:
    paths:
      - .terraform

ジョブのログに以下が出力されていればビルドキャッシュが有効になっています.

Checking cache for default...
Downloading cache.zip from https://NAME-gitlab-runner-cache.s3-ap-northeast-1.amazonaws.com/project/NUMBER/default 
Successfully extracted cache

...


Creating cache default...
.terraform: found 140 matching files
Downloading cache.zip from https://NAME-gitlab-runner-cache.s3-ap-northeast-1.amazonaws.com/project/NUMBER/default 
Created cache

トラブルシューティング

GitLab Runnerの本体がS3バケットにアクセスできない場合は,ジョブのログに以下が出力されます.

Checking cache for NAME...
No URL provided, cache will not be downloaded from shared cache server. Instead a local version of cache will be extracted. 

...

Creating cache NAME...
No URL provided, cache will be not uploaded to shared cache server. Cache will be stored only locally. 

また,GitLab RunnerのPodのログに以下が出力されます.

error while generating S3 pre-signed URL: No IAM roles attached to this EC2 service

Podに適切なIAMロールが割り当てられているか確認しましょう.GitLab Runnerが生成するworkerにS3アクセスのIAMポリシーを割り当てている場合もこれらログが出力されます.

GitLab CI/CDで特定のファイルが変更された場合にのみジョブを実行する

GitLab 11.4から only:changes という記法がサポートされました.これにより,特定のファイルが変更された場合のみジョブを実行できます.

https://docs.gitlab.com/ee/ci/yaml/#onlychangesexceptchanges

例えば,以下のように記述すると,ブランチをpushした時に terraform/ フォルダのファイルが変更されている場合にのみジョブが実行されます.

stages:
  - build

terraform_plan:
  stage: build
  only:
    changes:
      - terraform/**/*
      - .gitlab-ci.yml
  image:
    name: hashicorp/terraform:0.12.8
    entrypoint:
      - /usr/bin/env
  script:
    - cd terraform/
    - terraform --version
    - terraform init
    - terraform validate
    - terraform plan -out=plan.tfplan
  artifacts:
    name: plan
    paths:
      - terraform/plan.tfplan
  tags:
    - terraform

only はブランチ名と組み合わせることも可能です.以下のように記述すると,先ほどの条件に加えて,masterブランチに対してのみジョブが実行されます.

  only:
    changes:
      - terraform/**/*
      - .gitlab-ci.yml
    refs:
      - master

.gitlab-ci.yml を変更した場合は何らかの副作用がある場合が多いので,上記のように .gitlab-ci.yml を変更した場合もジョブが実行されるようにしておくとよいでしょう.