My Note

自己理解のためのブログ

GitHub ActionsでSPA(CloudFront + S3)にVueアプリをデプロイする

はじめに

GitHub Actionsで vueアプリをビルドしてS3にデプロイする

GIthub Actons

OIDCプロバイダを作成

AWSGitHub ActionsのOIDCプロバイダを作成する方法については以下に記載しています。 yhidetoshi.hatenablog.com

AWSの設定

IAMロールに付与する権限は以下の通り。CloudFrontとS3の設定については省略します。

  • S3にアップロードするために必要なIAMポリシー
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "s3:PutObject",
                "s3:DeleteObject",
                "s3:ListBucket",
                "s3:PutObjectAcl"
            ],
            "Resource": "*"
        }
    ]
}
  • CloudFrontのキャッシュ削除に必要なポリシー
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "cloudfront:CreateInvalidation",
                "cloudfront:GetInvalidation",
                "cloudfront:GetDistribution",
                "cloudfront:GetDistributionConfig",
                "cloudfront:GetStreamingDistribution",
                "cloudfront:ListDistributions",
                "cloudfront:ListInvalidations",
                "cloudfront:ListStreamingDistributions"
            ],
            "Effect": "Allow",
            "Resource": "*"
        }
    ]
}

GitHub Actionsのコード

用意した Vue.jsのコードは以下のサイトを参考に用意しました。

Vue-RouterでSPAを作る - Qiita

ディレクトリ構成は以下の通り

❯ tree -L 1
.
├── README.md
├── build
├── config
├── dist
├── index.html
├── node_modules
├── package-lock.json
├── package.json
├── src
└── static

1 vue.jsをビルドする

yarn run build
  • package.jsonの "scripts" の抜粋
"scripts": {
    "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
    "start": "npm run dev",
    "build": "node build/build.js"
  },

2 S3にアップロードする

aws s3 sync --exact-timestamps --delete ./dist s3://${{ env.AWS_BUCKET_NAME }}/

3 CloudFrontのキャッシュを削除する

aws cloudfront create-invalidation --distribution-id ${{ env.CF_DISTRIBUTION_ID }} --paths "/*"

利用するgithub-actionsのライブラリ

name: Deploy SPA
on:
  push:
    branches:
      - main
    paths:
      - ./**
      - .github/workflows/deploy.yaml

env:
  NODE_VERSION: '16'
  AWS_REGION: "ap-northeast-1"
  IAM_ROLE_ARN: ${{ secrets.IAM_ROLE_ARN }}
  AWS_BUCKET_NAME: "INPUT_BUCKET_NAME"
  CF_DISTRIBUTION_ID: "INPUT_CF_DISTRIBUTION_ID"

permissions:
  id-token: write
  contents: read

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v2

    - uses: actions/setup-node@v2
      with:
        node-version: ${{ env.NODE_VERSION }}
        cache: 'yarn'
        cache-dependency-path: ./package-lock.json
    - run: yarn install && yarn run build

    - name: Configure AWS Credentials
      uses: aws-actions/configure-aws-credentials@v1
      with:
        aws-region: ${{ env.AWS_REGION }}
        role-to-assume: ${{ env.IAM_ROLE_ARN}}
  
    - name: Setup aws cli
      uses: unfor19/install-aws-cli-action@v1
      with:
        version: 2
        verbose: false
        arch: amd64

    - name: Upload files to S3 and Clear CF cache
      run: |
        aws s3 sync --exact-timestamps --delete ./dist s3://${{ env.AWS_BUCKET_NAME }}/
        aws cloudfront create-invalidation --distribution-id ${{ env.CF_DISTRIBUTION_ID }} --paths "/*"

GitHub Actonsで利用するOIDCプロバイダをTerraform(AWS IAM)で作成する

はじめに

前回の記事で Github AcrtionsにOIDCでAWS認証してECRにコンテナイメージを登録しました。 前回は手動でAWSコンソールでポチポチ作成したので今回は OIDCをプロバイダをTerraformで作成しました。 terraform以外の部分は前回記載済みなので省略します。

yhidetoshi.hatenablog.com

Terraform

├── dev
│   ├── iam.tf
└── modules
    ├── iam_oidc
    │   ├── main.tf
    │   └── variables.tf

引数に "Thumbprint" をセットする必要があるので、hashicorp/tls プロバイダを利用しました。

https://registry.terraform.io/providers/hashicorp/tls/latest/docs/data-sources/tls_certificate

url - (Required) The URL of the website to get the certificates from.

が必要になりますが、URLは下記のサイトに記載されています。

Configuring OpenID Connect in Amazon Web Services - GitHub Docs

For the provider URL: Use https://token.actions.githubusercontent.com

  • modules/iam_oidc/iam.tf
resource "aws_iam_openid_connect_provider" "github_actions" {
  url = var.openid_url

  client_id_list = [
    var.openid_client_id,
  ]
  thumbprint_list = [data.tls_certificate.github_actions.certificates[0].sha1_fingerprint]
}

data "tls_certificate" "github_actions" {
  url = "https://token.actions.githubusercontent.com"
}
resource "aws_iam_role" "github_actions" {
  name               = var.iam_role_name
  assume_role_policy = data.aws_iam_policy_document.github_actions_role.json
}

data "aws_iam_policy_document" "github_actions_role" {
  statement {
    actions = ["sts:AssumeRoleWithWebIdentity"]
    principals {
      type        = "Federated"
      identifiers = ["arn:aws:iam::${data.aws_caller_identity.this.account_id}:oidc-provider/token.actions.githubusercontent.com"]
    }
    condition {
      test     = "StringLike"
      variable = "token.actions.githubusercontent.com:sub"
      values   = ["repo:${var.github_account}/${var.github_repo}:*"]
    }
  }
}

resource "aws_iam_role_policy" "ecr_push" {
  name   = var.iam_role_policy_name
  role   = aws_iam_role.github_actions.id
  policy = data.aws_iam_policy_document.ecr_push.json
}

data "aws_iam_policy_document" "ecr_push" {
  statement {
    sid = "ecrPush"
    actions = [
      "ecr:GetAuthorizationToken",
      "ecr:PutImage",
      "ecr:InitiateLayerUpload",
      "ecr:UploadLayerPart",
      "ecr:CompleteLayerUpload",
      "ecr:BatchCheckLayerAvailability"
    ]
    resources = ["*"]
  }
}

data "aws_caller_identity" "this" {}
  • modules/iam_oidc/variables.tf
variable "openid_url" {
  type        = string
  description = "open id url"
}

variable "openid_client_id" {
  type        = string
  description = "open id client id"
}

variable "iam_role_name" {
  type        = string
  description = "aws iam role name"
}

variable "iam_role_policy_name" {
  type        = string
  description = "aws iam role policy name"
}

variable "github_account" {
  type        = string
  description = "github account name"
}

variable "github_repo" {
  type        = string
  description = "github repo name"
}
  • dev/iam.tf
module "openid_connect_github" {
  source               = "../modules/iam_oidc"
  openid_url           = "https://token.actions.githubusercontent.com"
  openid_client_id     = "sts.amazonaws.com"
  iam_role_name        = "github-actions"
  iam_role_policy_name = "ecr-push"
  github_account       = "${GITHUB_ACCOUNT_NAME}"
  github_repo          = "${REPO_NAME}"
}

これで生成されたIAMロールのARNをgithub-actionsで設定すれば利用できます。

GitHub ActionsでOIDCを利用してECRにコンテナイメージを登録する

はじめに

Github Actionsで AWSのアークセスキーとシークレットキーを設定しなくてもOIDC ( OpenID Connect) で認証できるようになりました。 鍵情報をセットしなくて済み鍵を管理する必要もないのでこちらの方が楽で安心ですね!

docs.github.com

OpenID Connect (OIDC) allows your GitHub Actions workflows to access resources in Amazon Web Services (AWS), without needing to store the AWS credentials as long-lived GitHub secrets.

OIDCプロバイダーを作成

Configuring OpenID Connect in Amazon Web Services - GitHub Docs

  • IDプロバイダを作成するために必要な情報(上記のサイトで確認)
For the provider URL: Use https://token.actions.githubusercontent.com
For the "Audience": Use sts.amazonaws.com

f:id:yhidetoshi:20220122142747p:plain

f:id:yhidetoshi:20220122143344p:plain

  • 信頼関係
{
  "Version": "2008-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::${AWS_ACCOUNT}:oidc-provider/token.actions.githubusercontent.com"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringLike": {
          "token.actions.githubusercontent.com:sub": "repo:${GITHUB_ACCOUNT_NAME}/${GITHUB_REPO_NAME}:*"
        }
      }
    }
  ]
}
  • Roleに付与するポリシー
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "AllowPush",
            "Effect": "Allow",
            "Action": [
                "ecr:GetAuthorizationToken",
                "ecr:PutImage",
                "ecr:InitiateLayerUpload",
                "ecr:UploadLayerPart",
                "ecr:CompleteLayerUpload",
                "ecr:BatchCheckLayerAvailability"
            ],
            "Resource": "*"
        }
    ]
}

Github Actionsを設定

リポジトリのSecretsに設定しました。(アカウント番号が含まれるため)

env:
  AWS_ACCOUNT: ${{ secrets.AWS_ACCOUNT }}
  IAM_ROLE_ARN: ${{ secrets.IAM_ROLE_ARN }}

f:id:yhidetoshi:20220122145303p:plain

.github
└── workflows
    └── main.yaml
├── Dockerfile
  • .github/workflows/main.yaml
name: Registry docker image to ECR
on:
  push:
    branches:
      - main
    paths:
      - ./**
      - .github/workflows/main.yaml

env:
  REPO_NAME: product-a
  ECR_REGION: ap-northeast-1
  AWS_ACCOUNT: ${{ secrets.AWS_ACCOUNT }}
  IAM_ROLE_ARN: ${{ secrets.IAM_ROLE_ARN }}

permissions:
  id-token: write
  contents: read

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v2

      - name: Configure AWS Credentials
        uses: aws-actions/configure-aws-credentials@v1
        with:
          aws-region: ${{ env.ECR_REGION }}
          role-to-assume: ${{ env.IAM_ROLE_ARN}}

      - name: Login to ECR
        uses: docker/login-action@v1
        env:
          ECR_REGISTRY: ${{ env.AWS_ACCOUNT }}.dkr.ecr.${{ env.ECR_REGION }}.amazonaws.com
        with:
          registry: ${{ env.ECR_REGISTRY }}

      - name: Docker build and push to ECR
        uses: docker/build-push-action@v2
        env:
          ECR_REPOSITORY: ${{ env.AWS_ACCOUNT }}.dkr.ecr.${{ env.ECR_REGION }}.amazonaws.com/${{ env.REPO_NAME }}
        with:
          push: true
          tags: |
            ${{ env.ECR_REPOSITORY }}:latest
            ${{ env.ECR_REPOSITORY }}:${{ github.sha }}

f:id:yhidetoshi:20220122153120p:plain

terraformのコードをGitHub Actionsで構文チェックする

はじめに

terraformのコードをgithub-actionsでコードチェック(fmt, validate)する設定を書きました。 ワークフローは github/workflowsyamlファイルをセットすれば実行されます。

  • 今回の実行内容
    • mainブランチに対して "pull request" を作成したときに実行
    • 連携は "terraform-cloud"
    • terraform { fmt | init | validate } を実行
    • fmtの結果をPRのCommentに出力
    • 複数ディレクトリに対して実行

ソースコード

GitHub Actionsワークフローの構文については以下を参照。

GitHub Actionsのワークフロー構文 - GitHub Docs

■ 利用する "Terraform" の GitHub Actions

github.com

以前までは以下を利用していましたが、サポートが終了していたので変更しました。

github.com

(新) hashicorp/setup-terraform(旧) hashicorp/terraform-github-actions の違い

"hashicorp/terraform-github-actions" は step毎に hashicorp/terraform-github-actions@master を指定して "with" で "subcommand" 等を指定して実行していました。

     - name: Validate
       uses: hashicorp/terraform-github-actions@master
       with:
         tf_actions_version: ${{ env.TF_VERSION }}
         tf_actions_subcommand: validate
         tf_actions_cli_credentials_token: ${{ env.TF_ACTIONS_CLI_CREDENTIALS_TOKEN }}

"hashicorp/setup-terraform" は最初に terraformの実行環境をセットアップしてから実行コマンドを定義していく形に変更になっています。 こっちの方が直感的にわかりやすくて個人的に好きですね。

■ 利用する "checkout" の GItHub Actions

github.com

   steps:
   - name: Setup Terraform
     uses: hashicorp/setup-terraform@v1
     with:
       terraform_version: ${{ env.TF_VERSION }}
   - name: Configure TF Cloud
     uses: hashicorp/setup-terraform@v1
     with:
       cli_config_credentials_hostname: ${{ env.TF_HOSTNAME }}
       cli_config_credentials_token: ${{ env.TF_API_TOKEN }}  

    # terraform fmt
    - name: Terraform fmt
      id: fmt
      working-directory: ${{ matrix.workdir }}
      run: terraform fmt
      # run: terraform fmt -recursive -check
      continue-on-error: true

    # terraform init
    - name: Terraform init
      working-directory: ${{ matrix.workdir }}
      run: terraform init

■ PRにコメント投稿するために利用する GItHub Actions

github.com

  • terraform fmtの結果を環境変数にセットする
    • steps.${id}.outputs.stdout: stepで id を指定すると利用できる
    • "```" は markdown記法のコードで出力する
    • ${process.env.FMT} で内容を出力する
- uses: actions/github-script@v4
  if: ${{ contains(steps.fmt.outputs.stdout, '.tf') }}
  env:
    FMT: "```terraform\n${{steps.fmt.outputs.stdout}}```"
  with:
    github-token: ${{ secrets.GITHUB_TOKEN }}
    script: |
      const output = `<details><summary>terraform fmt:</summary>\n\n${process.env.FMT}\n\n</details>`;
      github.issues.createComment({
        issue_number: context.issue.number,
        owner: context.repo.owner,
        repo: context.repo.repo,
        body: output
      })

github actionを並列に実行する

今回は workdirの 3ディレクトリをチェックするので、 jobs.<job_id>.strategy.matrix で単一のジョブの定義内の変数の置き換えを行い複数のジョブを作成する。

    strategy:
      fail-fast: false # default=true
      matrix:
        workdir: [
          "./terraform/aws",
          "./terraform/org_a",
          "./terraform/org_b"
        ]
  • jobs.<job_id>.strategy.matrix を使った結果

f:id:yhidetoshi:20220119140607p:plain

■ terraform fmtの結果に応じてコメントを入れる

  • .tf が含まれる場合にコメントを書き込む処理を実施する
    - uses: actions/github-script@v4
      if: ${{ contains(steps.fmt.outputs.stdout, '.tf') }}

f:id:yhidetoshi:20220119142207p:plain

ソースコード全体

  • .github/workflows/terraform.yaml
name: Terraform
on:
  pull_request:
    branches:
      - main
    paths:
      - "terraform/**.tf"
      - ".github/workflows/terraform.yaml"

env:
  TF_HOSTNAME: "app.terraform.io"
  TF_API_TOKEN: ${{ secrets.TF_API_TOKEN }}
  TF_VERSION: 1.0.11

jobs:
  terraform_check:
    runs-on: ubuntu-latest
    continue-on-error: true
    strategy:
      fail-fast: false # default=true
      matrix:
        workdir: [
          "./terraform/aws",
          "./terraform/org_a",
          "./terraform/org_b"
        ]
    steps:
    - name: Setup Terraform
      uses: hashicorp/setup-terraform@v1
      with:
        terraform_version: ${{ env.TF_VERSION }}
    - name: Configure TF Cloud
      uses: hashicorp/setup-terraform@v1
      with:
        cli_config_credentials_hostname: ${{ env.TF_HOSTNAME }}
        cli_config_credentials_token: ${{ env.TF_API_TOKEN }}
    
    # git checkout   
    - name: Checkout
      uses: actions/checkout@v2

    # terraform fmt
    - name: Terraform fmt
      id: fmt
      working-directory: ${{ matrix.workdir }}
      run: terraform fmt
      # run: terraform fmt -recursive -check
      continue-on-error: true

    # terraform init
    - name: Terraform init
      working-directory: ${{ matrix.workdir }}
      run: terraform init

    # terraform validate
    - name: Terraform validate
      id: validate
      working-directory: ${{ matrix.workdir }}
      run: terraform validate

    # post fmt results
    - uses: actions/github-script@v4
      if: ${{ contains(steps.fmt.outputs.stdout, '.tf') }}
      env:
        FMT: "```terraform\n${{steps.fmt.outputs.stdout}}```"
      with:
        github-token: ${{ secrets.GITHUB_TOKEN }}
        script: |
          const output = `<details><summary>terraform fmt:</summary>\n\n${process.env.FMT}\n\n</details>`;

          github.issues.createComment({
            issue_number: context.issue.number,
            owner: context.repo.owner,
            repo: context.repo.repo,
            body: output
          })

TerraformでMackerelのAWSインテグレーションを設定する

はじめに

前回の記事でMackerelの設定をterraformで行ったので、今回は "AWSインテグレーション" を設定します。 今回もTerraformの実行は 前回の記事と同様に terraform-cloudを利用していますので同様に設定します。

yhidetoshi.hatenablog.com

設定

  • AWSインテグレーションの方法は下記の公式記事にまとめられています。

mackerel.io

Terraformの構成

今回追加した部分は terraform/aws/awsリソース作成用のワークスペースを用意しました。 awsインテグレーションに必要となるIAMリソースを他の "org" からも参照するという想定で "org_a" 内で作らず workspaceを独立する形にしています。

├── README.md
└── terraform
    ├── aws
    │   ├── iam-aws-integration.tf
    │   ├── locals.tf
    │   ├── outputs.tf
    │   ├── providers.tf
    │   └── variables.tf
    ├── org_a
    │   ├── alerts.tf
    │   ├── integrations.tf
    │   ├── monitors.tf
    │   ├── notifications.tf
    │   ├── providers.tf
    │   ├── roles.tf
    │   ├── services.tf
    │   └── variables.tf
    └── org_b
        ├── providers.tf
        └── variables.tf

IAMロールの作成

  • terraform/aws/providers.tf
    • terraform-cloudを利用しない場合は backend "local" {}
terraform {
  required_version = ">=1.0"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 3.0"
    }
  }
  backend "remote" {
    hostname     = "app.terraform.io"
    organization = "my-org"

    workspaces {
      name = "mackerel-aws"
    }
  }
}

provider "aws" {
  region = "ap-northeast-1"
}
  • terraform/aws/variables.tf
    • この鍵情報は terraform-cloudの Variables にSensitiveで設定します。
variable "AWS_ACCESS_KEY" {
  type = string
}

variable "AWS_SECRET_KEY" {
  type = string
}
  • terraform/aws/iam-aws-integration.tf
    • AWSインテグレーションに必要な権限を付与する必要があるので下記のサイトを参考に適宜付与する
    • "identifiers" で指定しているアカウント番号は 217452466226 をセットするように指定されています。
resource "aws_iam_role" "mackerel_integration" {
  name               = "mackerel-aws-integration"
  assume_role_policy = data.aws_iam_policy_document.mackerel_integration_role.json
}

data "aws_iam_policy_document" "mackerel_integration_role" {
  statement {
    actions = ["sts:AssumeRole"]
    principals {
      type        = "AWS"
      identifiers = ["arn:aws:iam::217452466226:root"]
    }
    condition {
      test     = "StringEquals"
      variable = "sts:ExternalId"
      values   = [local.aws_integration_iam_role_external_id_org_a]
    }
  }
}

resource "aws_iam_role_policy" "mackerel_integration" {
  name   = "mackerel-integration-policy"
  role   = aws_iam_role.mackerel_integration.id
  policy = data.aws_iam_policy_document.mackerel_integration.json
}

// ref: https://mackerel.io/ja/docs/entry/integrations/aws
data "aws_iam_policy_document" "mackerel_integration" {
  statement {
    actions = [
      "lambda:GetFunctionConfiguration",
      "lambda:List*",
      "budgets:ViewBudget",
      "cloudwatch:Get*",
      "cloudwatch:List*",
      "s3:ListAllMyBuckets",
      "s3:GetBucketLocation",
      "s3:GetBucketLogging",
      "s3:GetBucketTagging",
      "s3:GetEncryptionConfiguration",
      "s3:GetMetricsConfiguration"
    ]
    resources = ["*"]
  }
}

このtfファイルで作成されるIAMロールは以下の通りです。

  • 権限
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "",
            "Effect": "Allow",
            "Action": [
                "s3:ListAllMyBuckets",
                "s3:GetMetricsConfiguration",
                "s3:GetEncryptionConfiguration",
                "s3:GetBucketTagging",
                "s3:GetBucketLogging",
                "s3:GetBucketLocation",
                "lambda:List*",
                "lambda:GetFunctionConfiguration",
                "cloudwatch:List*",
                "cloudwatch:Get*",
                "budgets:ViewBudget"
            ],
            "Resource": "*"
        }
    ]
}
  • 信頼関係
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "",
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::217452466226:root"
      },
      "Action": "sts:AssumeRole",
      "Condition": {
        "StringEquals": {
          "sts:ExternalId": "${SET_IAM_ROLE_EXTERNAL_ID_FROM_MACKEREL_CONSOLE}"
        }
      }
    }
  ]
}
  • IAMロールに設定する external-id は Mackerelのコンソール画面で確認。

f:id:yhidetoshi:20220114112629p:plain

  • terraform/aws/locals.tf
    • Mackerelコンソールから確認した "external-id" をセット
locals {
  aws_integration_iam_role_external_id_org_a = "SET_IAM_ROLE_EXTERNAL_ID_FROM_MACKEREL_CONSOLE"
}
  • terraform/aws/outputs.tf
    • aws_integration_iam_role_arn は "org_a" で "iam-role-arn" が必要なので参照させる
    • aws_integration_iam_role_external_id_org_a は "org_a" で "external-id" が必要なので参照させる
output "aws_integration_iam_role_arn" {
  value = aws_iam_role.mackerel_integration.arn
}

output "aws_integration_iam_role_external_id_org_a" {
  value     = local.aws_integration_iam_role_external_id_org_a
  sensitive = true
}

Mackerelのインテグレーション設定

  • terraform/org_a/providers.tf
    • terraform-cloudを利用しない場合は backend "local" {}
terraform {
  required_version = ">=1.0"
  required_providers {
    mackerel = {
      source  = "mackerelio-labs/mackerel"
      version = "~> 0.0.8"
    }
  }
  backend "remote" {
    hostname     = "app.terraform.io"
    organization = "my-org"
    workspaces {
      name = "mackerel-org-a"
    }
  }
}

data "terraform_remote_state" "aws" {
  backend = "remote"

  config = {
    organization = "my-org"
    workspaces = {
      name = "mackerel-aws"
    }
  }
}
  • terraform/org_a/variables.tf
variable "MACKEREL_API_KEY" {
  type = string
}
resource "mackerel_aws_integration" "us_east_1" {
  name          = "us-east-1"
  memo          = "monitor for us-east-1"
  key           = ""
  secret_key    = ""
  role_arn      = data.terraform_remote_state.aws.outputs.aws_integration_iam_role_arn
  external_id   = data.terraform_remote_state.aws.outputs.aws_integration_iam_role_external_id_org_a
  region        = "us-east-1"
  included_tags = "" # ex) = "Name:hoge,Environment:dev"
  excluded_tags = ""

  ec2 {
    enable               = false
    role                 = "${mackerel_service.dev.name}: ${mackerel_role.web.name}"
    excluded_metrics     = []
    retire_automatically = true
  }

  lambda {
    enable = true
    role   = "${mackerel_service.aws.name}: ${mackerel_role.lambda.name}"
  }

  s3 {
    enable = false
    role   = "${mackerel_service.aws.name}: ${mackerel_role.s3.name}"
  }

  billing {
    enable = true
    role   = "${mackerel_service.aws.name}: ${mackerel_role.billing.name}"
  }
}


resource "mackerel_aws_integration" "ap_northeast_1" {
  name          = "ap-northeast-1"
  memo          = "monitor for ap-northeast-1"
  role_arn      = data.terraform_remote_state.aws.outputs.aws_integration_iam_role_arn
  external_id   = data.terraform_remote_state.aws.outputs.aws_integration_iam_role_external_id_org_a
  region        = "ap-northeast-1"
  included_tags = "" # ex) = "Name:hoge,Environment:dev"
  excluded_tags = ""

  ec2 {
    enable               = false
    role                 = "${mackerel_service.dev.name}: ${mackerel_role.web.name}"
    excluded_metrics     = []
    retire_automatically = true
  }
}

■ 結果

f:id:yhidetoshi:20220114121643p:plain

f:id:yhidetoshi:20220114120206p:plain

おわり

本記事では、TerraformでMackerelのAWSインテグレーションの設定を行いました。 AWSインテグレーションをすると意図せず多くのリソースを追加してしまい想定より多くの料金がかかる場合があるので注意が必要です。 タグで絞るなど工夫できます。今回は Mackerelのアンバサダプランを利用させていただいているので少額の課金も気にせず検証することができました。(感謝!)

また、以前にAWSインテグレーションにBillingの機能がなかった場合に、MackerelのサービスメトリクスにPostして監視する記事を書いたので よければ参照ください。

yhidetoshi.hatenablog.com

Terraform ( terraform-cloud )でMackerelをコード管理する

はじめに

2022年の一本目の記事になります。Mackerelに関する記事は最近書けていなかったので久々の投稿になります。 以前の記事でMackerel監視設定の管理をServerlessで自動化しました。

yhidetoshi.hatenablog.com

当時は Mackerel公式からterraformプロバイダーが提供されていませんでしたが、昨年リリースされたので 監視設定に限らずMackerelの設定をコード化できるので試してみました。

mackerel.io

環境

  • terraform-clolud(無料プラン)
  • GitHub
  • Mackerel
  • Terraform: v1.0.11

f:id:yhidetoshi:20220111145022p:plain

やること

  • terraform-cloudの設定
  • terraformの構成
  • serviceを作成
  • terraform import (既存のservice)
  • roleを作成
  • monitorを作成
  • notificationを作成
  • alertを作成

terraformのコードと構成

terraform-cloudの設定

  • GitHubとterraform-cloudの連携
  • terraform-cloudの organizationとworkspaceの作成

の詳細な説明については省略します。

terraform-cloud の "workspace" の設定する部分を一部抜粋しました。

Variables

  • 環境変数として、mackerelのAPI-KEYを設定しています。Mackerelに設定を反映するときに必要になります。

f:id:yhidetoshi:20220111151038p:plain

Setting (General Settings)

  • 実行環境を "Remote" にして Terraformのバージョンを設定。

f:id:yhidetoshi:20220111151207p:plain

Setting (Version Control)

  • fileが変更されたら実行するトリガーのパスを設定。

f:id:yhidetoshi:20220111151258p:plain

terraform-registry

利用する Mackerelのterraform registryとGitHubは下記。

terraformの構成

  • 構成は複数のOrganizationを運用するという設定で org_a, org_bのようにorg毎にworkspaceを分離
  • 役割毎にtfファイルを分離

ディレクトリ構造

└── terraform
    ├── org_a
    │   ├── alerts.tf
    │   ├── monitors.tf
    │   ├── notifications.tf
    │   ├── providers.tf
    │   ├── roles.tf
    │   ├── services.tf
    │   └── variables.tf
    └── org_b
        ├── providers.tf
        └── variables.tf
terraform {
  required_version = ">=1.0"
  required_providers {
    mackerel = {
      source  = "mackerelio-labs/mackerel"
      version = "~> 0.0.8"
    }
  }
  backend "remote" {
    hostname     = "app.terraform.io"
    organization = "my-org"
    workspaces {
      name = "mackerel-org-a"
    }
  }
}
  • terraform/org_a/variables.tf
variable "MACKEREL_API_KEY" {
  type = string
}
  • ローカルmoduleは hashicorp/mackerel のプロバイダーがなくエラーになるため利用していません。
> tree .
 ├── modules
    │   └── service
    │       ├── main.tf
    │       └── variables.tf
   └── org_a
        ├── providers.tf
        ├── services.tf
        └── variables.tf


> terraform init

│ Error: Failed to query available provider packages
│
│ Could not retrieve the list of available versions for provider hashicorp/mackerel: provider registry
│ registry.terraform.io does not have a provider named registry.terraform.io/hashicorp/mackerel


> terraform providers
Providers required by configuration:
.
├── provider[registry.terraform.io/mackerelio-labs/mackerel] ~> 0.0.8
└── module.service_name_org_a
    └── provider[registry.terraform.io/hashicorp/mackerel]

serviceを作成

resource "mackerel_service" "aws" {
  name = "AWS"
  memo = "aws billing"
}

resource "mackerel_service" "dev" {
  name = "dev"

■ 確認

❯ mkr services | jq '.[].name' -r | grep -e dev -e AWS
AWS
dev

→ "dev" のサービスが作成されています。

terraform import

新規でmackerelを利用する場合は困らないですが、すでに運用を始めていると既存の設定を terraformに取り込む必要があるので terraform import を利用します。

  • terraform importするために開発端末からmackerelに対してAPIを発行するので環境変数をセットします。
export MACKEREL_API_KEY=XXXX

すでに作成してあるサービスの "AWS" とimportします。

resource "mackerel_service" "aws" {
  name = "AWS"
  memo = "aws billing"
}
  • importする
❯ terraform import mackerel_service.aws AWS


Acquiring state lock. This may take a few moments...

Import successful!

The resources that were imported are shown above. These resources are now in
your Terraform state and will henceforth be managed by Terraform.

Releasing state lock. This may take a few moments...
  • stateファイルを確認
❯ terraform state list mackerel_service.aws
mackerel_service.aws

→ statefileに追加されています。

roleを作成

resource "mackerel_role" "web" {
  name    = "web"
  service = mackerel_service.dev.name
  depends_on = [
    mackerel_service.dev
  ]
}

resource "mackerel_role" "db" {
  name    = "db"
  service = mackerel_service.dev.name
  depends_on = [
    mackerel_service.dev
  ]
}

f:id:yhidetoshi:20220112133255p:plain

→ "dev" のサービスに "web" と "db" のロールが作成されています。

monitorを作成

resource "mackerel_monitor" "cpu_high" {
  name    = "VM CPU %"
  is_mute = false

  host_metric {
    metric   = "cpu%"
    operator = ">"
    warning  = 80
    critical = 90
    duration = 3

    scopes = ["dev:web"]
  }
}

作成されたmonitor

f:id:yhidetoshi:20220114110313p:plain

notificationを作成

resource "mackerel_channel" "email" {
  name = "Email"

  email {
    emails = ["sample1@example.com", "sample2@example.com"]
    events = ["alert", "alertGroup"]
  }
}

resource "mackerel_channel" "slack" {
  name = "Slack"

  slack {
    url                 = "https://hooks.slack.com/services/xxx/yyy/zzz"
    enabled_graph_image = true
    events              = ["alert", "alertGroup"]
    mentions = {
      critical = "@here"
    }
  }
}

Emailのchannel

f:id:yhidetoshi:20220114104823p:plain

Slackのchannel

f:id:yhidetoshi:20220114110154p:plain

alertsを作成

resource "mackerel_alert_group_setting" "admin" {
  name = "admin"
  memo = "For Admin members"
  service_scopes = [
    mackerel_service.dev.name,
  ]
  depends_on = [
    mackerel_service.dev
  ]
}

おわり

Mackerelの公式からTerraformのregistryがリリースされたので利用して設定を管理してみました。 これを使えばコードベースでMackerelの管理ができそうですね。

ソースコードは以下のGitHubに配置しています。

github.com

"terraform state mv" の備忘録

はじめに

今回はTerraformを利用していると必要となってくる stateファイルを変更する場合の備忘録になります。

環境

  • Terraformのバージョン
    • Terraform: v1.0.1
  • リモートのTerraform環境

state mv を実行する

今回は以下の箇所を変更します。

resource "aws_ecs_task_definition" "service" を "services" に変更する

resource "aws_ecs_task_definition" "service" 
↓↓↓
resource "aws_ecs_task_definition" "services" 

変更する前に設定を確認する。

  • $ terraform state list
$ terraform state list module.ecs_fargate.aws_ecs_task_definition.service
module.ecs_fargate.aws_ecs_task_definition.service["systemA"]
module.ecs_fargate.aws_ecs_task_definition.service["systemB"]

作業手順は以下の通り。

  1. "remote" のtfstateファイルを取得する
  2. "backend" を "remote" から "local" に変更する
  3. "backend"を変更したので -reconfigure する
  4. "local" のtfstateファイルを state mv する
  5. "local" のtfstateファイルを指定して terraforn plan して変更が無い事を確認する
  6. "backend" を "local" から "remote" に変更する
  7. "backend" を変更したので -reconfigure する
  8. "remote" のtfstateファイルに反映する
  9. 不要になった "local" のstateファイルを削除する
  10. 変更したソースコードをpushする

1. "remote" のtfstateファイルを取得する

$ terraform state pull > temp.tfstate

2. "backend" を "remote" から "local" に変更する

  • providers.tf (変更前)
terraform {
  required_version = ">=1.0"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 3.0"
    }
  }
  backend "remote" {
    hostname     = "app.terraform.io"
    organization = "myorg"

    workspaces {
      name = "aws-dev"
    }
  }
}
  • providers.tf (変更後)
terraform {
  required_version = ">=1.0"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 3.0"
    }
  }
  backend "local" {
    
  }
}

3. "backend"を変更したので -reconfigure する

  • -reconfigure のオプション

-reconfigure: Reconfigure the backend, ignoring any saved configuration.

$ terraform init -reconfigure
Initializing modules...

Initializing the backend...

Successfully configured the backend "local"! Terraform will automatically
use this backend unless the backend configuration changes.

→ local に切り替わった事を確認

4. "local" のtfstateファイルを state mv する

  • -state で stateファイルを指定する
  • $ terraform state mv -state=${STATE_FILE} <変更前> <変更後>
$ terraform state mv -state=tmp.tfstate module.ecs_fargate.aws_ecs_task_definition.service module.ecs_fargate.aws_ecs_task_definition.services

Move "module.ecs_fargate.aws_ecs_task_definition.service" to "module.ecs_fargate.aws_ecs_task_definition.services"
Successfully moved 1 object(s).

5."local" のtfstateファイルを指定して terraforn plan して変更が無い事を確認する

$ aws-vault exec my-dev -- terraform plan -state=temp.tfstate

> No changes. Your infrastructure matches the configuration.

*) aws-vaultを利用しているためこのコマンドになります。

6. "backend" を "local" から "remote" に変更する

最初に変更した providers.tfの設定を元に戻す

  • providers.tf (変更前)
terraform {
  required_version = ">=1.0"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 3.0"
    }
  }
  backend "local" {
    
  }
}
  • providers.tf (変更後)
terraform {
  required_version = ">=1.0"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 3.0"
    }
  }
  backend "remote" {
    hostname     = "app.terraform.io"
    organization = "myorg"

    workspaces {
      name = "aws-dev"
    }
  }
}

7. "backend" を変更したので -reconfigure する

❯ terraform init -reconfigure
Initializing modules...

Initializing the backend...

Successfully configured the backend "remote"! Terraform will automatically
use this backend unless the backend configuration changes.

→ remote に切り替わった事を確認

8. "remote" のtfstateファイルに反映する

  • $ terraform state push ${STATE_FILE}
$ terraform state push temp.tfstate
Acquiring state lock. This may take a few moments...
Releasing state lock. This may take a few moments...

"remote" のstate情報を確認

  • $ terraform state list
$ terraform state list module.ecs_fargate.aws_ecs_task_definition.services
module.ecs_fargate.aws_ecs_task_definition.services["systemA"]
module.ecs_fargate.aws_ecs_task_definition.services["systemB"]

remote stateファイルが aws_ecs_task_definition.service.aws_ecs_task_definition.services に変更できた事が確認できました。

9. 不要になった "local" のstateファイルを削除する

rm -f temp.tfstate*

10. 変更したソースコードをpushする 変更したコードをpushして差分を取り込む