kakakakakku blog

Weekly Tech Blog: Keep on Learning!

Fargate でも使える!FireLens の init プロセスを活用して設定ファイルを S3 から読み込む

Amazon ECS で FireLens (Fluent Bit) を使ってログをルーティングするときに Fluent Bit の「設定ファイル」を書いて挙動をカスタマイズしたくなる場面は多くある.そして FireLens は設定ファイルタイプ config-file-type として s3file をサポートしているけど,ECS タスクを AWS Fargate で動かす場合は file を使う必要がある💨

AWS Fargate でホストされるタスクは、file 設定ファイルタイプのみをサポートします。
FireLens 設定を使用するタスク定義の作成 - Amazon ECS

1. 独自コンテナイメージを作る file

config-file-typefile を使う場合,FireLens のコンテナイメージをベースに extra.conf など Fluent Bit の設定ファイルを COPY コマンドで追加した「独自コンテナイメージ」を作る必要がある.

Dockerfile サンプル

以下に Dockerfile のサンプルを載せる.

FROM public.ecr.aws/aws-observability/aws-for-fluent-bit:2.28.5
COPY ./extra.conf /fluent-bit/etc/extra.conf

extra.conf サンプル

以下に Fluent Bit の設定ファイル extra.conf のサンプルを載せる.今回は Apache HTTP Server (httpd) のログを Amazon CloudWatch Logs と Amazon Kinesis Data Firehose にログをルーティングするようにした.

[OUTPUT]
    Name cloudwatch_logs
    Match *
    region ap-northeast-1
    log_key log
    log_group_name /sandbox/httpd
    log_stream_prefix httpd/
    auto_create_group true

[OUTPUT]
    Name firehose
    Match *
    region ap-northeast-1
    delivery_stream sandbox-log-stream

Amazon ECR に独自コンテナイメージを登録する

そして,Dockerfile をビルドして Amazon ECR に独自コンテナイメージを登録する.今回コンテナイメージ名は firelens-extra:1.0.0 にした.

$ docker build -t firelens-extra .
$ docker tag firelens-extra:latest 000000000000.dkr.ecr.ap-northeast-1.amazonaws.com/firelens-extra:1.0.0
$ docker push 000000000000.dkr.ecr.ap-northeast-1.amazonaws.com/firelens-extra:1.0.0

Amazon ECS タスク定義

以下に Amazon ECS タスク定義のサンプルを載せる.

Apache HTTP Server (httpd) のログを FireLens (Fluent Bit) 経由でルーティングしている.firelensConfigurationoptions で独自コンテナイメージに追加した設定ファイル /fluent-bit/etc/extra.conf を指定している.ちなみに log-routerlogConfiguration では,FireLens 自体のログを Amazon CloudWatch Logs に保存している.FireLens の設定ファイルを間違えたときなどの調査に使える.

{
  "containerDefinitions": [
    {
      "name": "httpd",
      "image": "public.ecr.aws/docker/library/httpd:2.4",
      "essential": true,
      "logConfiguration": {
        "logDriver": "awsfirelens"
      }
    },
    {
      "name": "log-router",
      "image": "000000000000.dkr.ecr.ap-northeast-1.amazonaws.com/firelens-extra:1.0.0",
      "essential": true,
      "firelensConfiguration": {
        "type": "fluentbit",
        "options": {
          "config-file-type": "file",
          "config-file-value": "/fluent-bit/etc/extra.conf"
        }
      },
      "logConfiguration": {
        "logDriver": "awslogs",
        "options": {
          "awslogs-group": "firelens-container",
          "awslogs-region": "ap-northeast-1",
          "awslogs-create-group": "true",
          "awslogs-stream-prefix": "firelens"
        }
      }
    }
  ]
}

アーキテクチャ図を書くならこんな感じ🎨

また FireLens の概要や独自コンテナイメージを作る詳細な手順などは「AWS コンテナ設計・構築 [本格] 入門」の Chapter 3 と 5 にも載っている.あわせて読んでみると良いのではないでしょうか📕

2. FireLens の init プロセスを活用する

さて,ここから本題❗️

FireLens には :init-latest など init というキーワードを含んだイメージタグがあって,これを使うと FireLens を起動するときに Amazon S3 から Fluent Bit の設定ファイルを自動的に読み込めるようになっている❗️この init プロセスを活用すると,独自コンテナイメージを作らなくて良くなる👏

\( 'ω')/ うおおおおお!最高だ〜

  • :init-latest
  • :init-2.28.5
  • :init-arm64-2.28.5
  • etc

gallery.ecr.aws

init プロセスに関して

「init プロセス」に関する詳細な情報は GitHub に載っていた📝

  • Init process for Fluent Bit on ECS, multi-config support
  • FireLens Example: Multiple Config support - using the Fluent Bit image with init tag

github.com

github.com

Amazon ECS タスク定義

以下に init プロセスを活用する Amazon ECS タスク定義のサンプルを載せる.

まず,独自コンテナイメージを作らなくて良くなるため,log-routerimagepublic.ecr.aws/aws-observability/aws-for-fluent-bit:init-2.28.5 など公開イメージで OK になる👌 そして,読み込ませる Fluent Bit の設定ファイル extra.conflog-routerenvironment に Amazon S3 オブジェクトの ARN として設定する.nameaws_fluent_bit_init_s3 プレフィックスを使って aws_fluent_bit_init_s3_${連番} のようにする必要がある.

もちろん複数の設定ファイルを読み込ませることもできる❗️

{
  "containerDefinitions": [
    {
      "name": "httpd",
      "image": "public.ecr.aws/docker/library/httpd:2.4",
      "essential": true,
      "logConfiguration": {
        "logDriver": "awsfirelens"
      }
    },
    {
      "name": "log-router",
      "image": "public.ecr.aws/aws-observability/aws-for-fluent-bit:init-2.28.5",
      "essential": true,
      "firelensConfiguration": {
        "type": "fluentbit"
      },
      "environment": [
        {
          "name": "aws_fluent_bit_init_s3_1",
          "value": "arn:aws:s3:::kakakakakku-firelens-configs/extra.conf"
        }
      ],
      "logConfiguration": {
        "logDriver": "awslogs",
        "options": {
          "awslogs-group": "firelens-container",
          "awslogs-region": "ap-northeast-1",
          "awslogs-create-group": "true",
          "awslogs-stream-prefix": "firelens"
        }
      }
    }
  ]
}

アーキテクチャ図を書くならこんな感じ🎨

init プロセスのメリットとデメリット

あくまで個人的な考えだけど,init プロセスを活用すれば独自コンテナイメージを作らなくて良く,運用面で楽になるメリットがあると思う.extra.conf を更新するときも Amazon S3 にアップロードして,ECS タスクを再デプロイすれば OK👌逆に言えば,init プロセスだと FireLens コンテナを起動するときに毎回 Amazon S3 から設定ファイルを取得するため,起動時間への影響と s3:GetObject のコストは多少デメリットに感じる.

よって,必ずしも init プロセスを使うべきという話ではなく,メリットとデメリットを比較して決める必要がありそう❗️

github.com

まとめ

FireLens (Fluent Bit) の設定ファイルをカスタマイズするときに init プロセスを使えば独自コンテナイメージを作らなくても良くなるよー❗️という紹介記事を書いた.

参考になれば〜

おまけ : aws-for-fluent-bit の stable バージョンを確認する

AWS Systems Manager Parameter Store のパブリックパラメータを使えば aws-for-fluent-bit の stable バージョンを確認できる💡

$ aws ssm get-parameters \
    --names /aws/service/aws-for-fluent-bit/stable \
    --region ap-northeast-1
{
    "Parameters": [
        {
            "Name": "/aws/service/aws-for-fluent-bit/stable",
            "Type": "String",
            "Value": "906394416424.dkr.ecr.ap-northeast-1.amazonaws.com/aws-for-fluent-bit:2.28.5",
            "Version": 13,
            "LastModifiedDate": "2023-04-15T03:50:41.141000+09:00",
            "ARN": "arn:aws:ssm:ap-northeast-1::parameter/aws/service/aws-for-fluent-bit/stable",
            "DataType": "text"
        }
    ],
    "InvalidParameters": []
}

Terraform Cloud x AWS に入門できる「AWS Modernization Workshop with HashiCorp Terraform Cloud」

AWS Modernization Workshop「AWS Modernization Workshop with HashiCorp Terraform Cloud」を実施した❗️Terraform Cloud を使って AWS を操作する流れを一通り学べるので,特に Terraform や Terraform Cloud 入門者におすすめ.例えば "今までは AWS CloudFormation をよく使ってたけど今度 Terraform を使う予定があるから試しておこうかなぁ〜" っていうときにピッタリ👏

実施をしながら詰まったところや工夫したところなども含めてワークショップを紹介しようと思う💡

hashicorp-terraform.awsworkshop.io

構成

ワークショップは大きく以下の構成になっている.手順書に所要時間は "1.5時間 ~ 2.5時間" と書いてあって,うまく進めば終わると思うけど,僕自身は調査もしながらワークショップを吟味していて,実際には4時間ほどだった🕛

  • Introduction
  • Self Guided Setup
  • Terraform Cloud
  • Deploying Infrastructure
  • Sentinel
  • Cleanup

🟣 Self Guided Setup

手順には AWS Cloud9 をバージニアリージョン(us-east-1)に構築すると書いてあるけど,全く使わなくて謎だった💧よって,ここは何もせず skip して良いかと👌

🟣 Terraform Cloud

まず,Terraform Cloud の初期セットアップを行う.アカウントを作って,Organization kakakakakku を作って,Default Project の中に GitHub と連携した Workspace tfc-guide-example を作る.連携するリポジトリは hashicorp/tfc-guide-example にある(フォークして使う).

github.com

GitHub と連携した Workspace を作るときに,手順書だと GitHub.com (Custom) を選択すると書いてある.ただし現在は選択肢が増えていて「GitHub App」を選択してリポジトリに対する権限を付与する必要がある❗️詳しくは以下のドキュメント Configuration-Free GitHub Usage に載っている.

developer.hashicorp.com

🟣 Deploying Infrastructure

次に Terraform コードを実行する準備をする.今回は Terraform で Amazon EC2 インスタンスを1台構築するため,以下の Terraform variable を設定する.

  • instance_type: t2.micro
  • instance_name: Provisioned by Terraform

次に AWS を操作するためのアクセスキー(AWS_ACCESS_KEY_IDAWS_SECRET_ACCESS_KEY)を設定する.でも個人的には Terraform Cloud にアクセスキーを設定したくなく,今回は IAM ID プロバイダから「IAM Role」を取得できるようにした.詳しくは以下の記事にまとめてある❗️

  • TFC_AWS_PROVIDER_AUTH: true
  • TFC_AWS_RUN_ROLE_ARN: arn:aws:iam::xxx:role/xxx

kakakakakku.hatenablog.com

準備ができたら Terraform Cloud から planapply をして Amazon EC2 インスタンスを北カリフォルニアリージョン (us-west-1) に構築する.

\( 'ω')/ 構築できたぞ!やったー!

またプルリクエストを使って Amazon EC2 インスタンス名(Name タグ)を変更して,planapply をする流れも体験できる.

\( 'ω')/ 変更できたぞ!やったー!

🟣 Sentinel

今度は Terraform で Policy as code を実現できる「Sentinel」を使って Terraform コードにポリシーを適用する.

www.hashicorp.com

手順書には "30日間フリートライアル" を使って Terraform のプランを上げると書いてあるけど,Free プランのままでも 1 Policy set5 Policies までは使えそうで,手順上少し面倒なところはあるけど結果的に問題なくワークショップは進められた✌️

www.hashicorp.com

今回は Terraform のバージョン 1.1.0 以上であることを soft-mandatory レベルで強制するという内容で,Sentinel コードは hashicorp/learn-terraform-enforce-policies リポジトリにある.

import "tfplan"
import "version"

main = rule {
  version.new(tfplan.terraform_version).greater_than("1.1.0")
}

github.com

docs.hashicorp.com

手順書では Policy sets と GitHub を連携するように書いてあるけど,Free プランだと The organization has reached the limit of 0 versioned policy sets. というエラーで設定できなかった.今回は少し面倒だけど Terraform Cloud の画面で PoliciesPolicy sets を設定した.

そして,ポリシーを紐付けて同じく plan をすると期待通りに Sentinel policies passed になった❗️

\( 'ω')/ Sentinel 便利!

後半では Terraform Cloud の「Cost Estimation」機能と Sentinel を組み合わせて,見積もりコストが $100 より小さくなるようにポリシーを適用する.今回は Free プランを使ってるので,作成した PoliciesPolicy sets を一度消して,また Terraform Cloud の画面で設定し直した.

import "tfrun"
import "decimal"

delta_monthly_cost = decimal.new(tfrun.cost_estimate.delta_monthly_cost)

main = rule {
    delta_monthly_cost.less_than(100)
}

そして,ポリシーを紐付けて同じく plan をすると期待通りに Sentinel policies passed になった❗️

\( 'ω')/ コストを意識しながら実行できるのはイイ!

後半で使ったリポジトリを以下にまとめておく.

github.com

github.com

🟣 Cleanup

不要なリソースが残らないように Destruction and Deletion でお掃除をして終了❗️

まとめ

AWS Modernization Workshop「AWS Modernization Workshop with HashiCorp Terraform Cloud」は Terraform Cloud x AWS に入門できる良いワークショップだった❗️

もし興味があったら実施してみると良いのではないでしょうか✌️

関連リンク

手順書自体は以下の GitHub で管理されていた.

github.com

Terraform Cloud で IAM Role から一時的なアクセスキーを取得する

Terraform Cloud で AWS を操作するときにアクセスキー(AWS_ACCESS_KEY_IDAWS_SECRET_ACCESS_KEY)を使いたくなく,Terraform Cloud の「Dynamic Provider Credentials」を使って OpenID Connect (OIDC) と連携する仕組みを構築したので,ポイントをまとめておく❗️

\( 'ω')/ アクセスキー撲滅だ〜

参考になる記事

Terraform のドキュメントと AWS Partner Network (APN) Blog の記事は特に参考になった💡

developer.hashicorp.com

aws.amazon.com

Step.1 IAM ID プロバイダを構築する

まず,Terraform Cloud 用に「IAM ID プロバイダ」を構築する.コンソールだと以下のように設定する.サムプリントはコンソールから取得できる👌

  • プロバイダのタイプ: OpenID Connect
  • プロバイダの URL: https://app.terraform.io
  • 対象者: aws.workload.identity

もし AWS CloudFormation で構築する場合は以下のテンプレートを参考にしてもらえればと❗️

AWSTemplateFormatVersion: 2010-09-09

Parameters:
  Thumbprint:
    Type: String
    Default: '9e99a48a9960b14926bb7f3b02e22da2b0ab7280'

Resources:
  TerraformCloudOidcProvider:
    Type: AWS::IAM::OIDCProvider
    Properties:
      Url: https://app.terraform.io
      ClientIdList:
        - aws.workload.identity
      ThumbprintList:
        - !Ref Thumbprint

docs.aws.amazon.com

構築できた👏

Step.2 IAM Role を構築する

次に IAM ID プロバイダと連携する「IAM Role」を構築する.ポリシーは Terraform Cloud に必要な権限に抑えつつ,信頼関係は以下の値を埋めつつ設定する.

  • <AccountId>: AWS アカウント ID
  • <Organization>: Terraform Cloud の Organization 名
  • <Project>: Terraform Cloud の Project 名
  • <Workspace>: Terraform Cloud の Workspace 名
  • <RunPhase>: plan もしくは apply(基本的には * で良いと思う)

特に app.terraform.io:sub に設定する ID に関しては範囲を狭めるために * を避けて <Organization><Project><Workspace> を設定する.<RunPhase>planapply の権限を分割して管理する場合を除いて,基本的には * で良いと思う.きめ細かく ID を指定できるのは助かる💡

{
    "Version": "2008-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Federated": "arn:aws:iam::<AccountId>:oidc-provider/app.terraform.io"
            },
            "Action": "sts:AssumeRoleWithWebIdentity",
            "Condition": {
                "StringEquals": {
                    "app.terraform.io:aud": "aws.workload.identity"
                },
                "StringLike": {
                    "app.terraform.io:sub": "organization:<Organization>:project:<Project>:workspace:<Workspace>:run_phase:<RunPhase>"
                }
            }
        }
    ]
}

もし AWS CloudFormation で構築する場合は以下のテンプレートを参考にしてもらえればと❗️ポリシーはサンプルとして AmazonEC2FullAccess を設定してある.

AWSTemplateFormatVersion: 2010-09-09

Resources:
  TerraformCloudRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: terraform-cloud-role
      AssumeRolePolicyDocument:
        Statement:
          - Effect: Allow
            Action: sts:AssumeRoleWithWebIdentity
            Principal:
              Federated: !Sub arn:aws:iam::${AWS::AccountId}:oidc-provider/app.terraform.io
            Condition:
              StringEquals:
                app.terraform.io:aud: aws.workload.identity
              StringLike:
                app.terraform.io:sub: organization:<Organization>:project:<Project>:workspace:<Workspace>:run_phase:<RunPhase>
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AmazonEC2FullAccess

docs.aws.amazon.com

Step.3 Terraform Cloud に環境変数を設定する

最後は Terraform Cloud の Workspace で環境変数 (Environment variable) を2つ設定する.

  • TFC_AWS_PROVIDER_AUTH: true
  • TFC_AWS_RUN_ROLE_ARN: IAM Role の ARN(例: arn:aws:iam::<AccountId>:role/terraform-cloud-role

あとは Terraform Cloud で planapply を実行すれば OK❗️

\( 'ω')/ 簡単便利〜

関連記事

www.hashicorp.com

www.hashicorp.com

Amplify Hosting を CloudFormation と GitHub の Personal Access Tokens でデプロイする

AWS Amplify Hosting に GitHub からフロントエンドアプリケーションをデプロイする構成を AWS CloudFormation で構築する機会があった.AWS Amplify Hosting はコンソールだと比較的簡単にデプロイできるけど,今回は AWS CloudFormation と GitHub の Personal Access Tokens (PAT) を組み合わせてデプロイしたため,検証時に使ったテンプレートを汎化してまとめておく❗️

ドキュメントとしては以下を読んでおくと良いと思う💡AWS Amplify Hosting と GitHub Apps を連携する手順がまとまっている📝

docs.aws.amazon.com

GitHub Apps (AWS Amplify)

まず最初に AWS Amplify の GitHub Apps をセットアップする.例えば「東京リージョン」なら https://github.com/apps/aws-amplify-ap-northeast-1/installations/new にアクセスして,Permissions を与えておく.リポジトリを選択する設定では,基本的には All repositories ではなく Only select repositories で対象リポジトリを個別に選択するのが良いと思う👌

Personal Access Tokens (PAT)

次に AWS CloudFormation で使う GitHub の Personal Access Tokens (PAT) を取得する.現時点だとトークンは classicFine-grained (Beta) の2種類があるけど,今回はドキュメントに載っている手順に沿って classic を使う.重要なのは Select scopes でトークンに指定するスコープで admin:repo_hook を許可しておくところ❗️

ちなみに GitHub ドキュメントだと 可能な限り、personal access tokens (classic) ではなく fine-grained personal access token を使用することをお勧めします。 と書いてある.

docs.github.com

AWS CloudFormation テンプレート

最後は AWS CloudFormation を使って AWS Amplify Hosting アプリケーションをデプロイする.ポイントは AccessToken に取得した GitHub の Personal Access Tokens (PAT) を設定するところ❗️しかし,テンプレートに直接 Personal Access Tokens (PAT) を設定するのは避けるべきで,今回は以下の3種類のテンプレートを準備してみた❗️

  • AWS Secrets Manager
  • AWS Systems Manager Parameter Store
  • Parameters (NoEcho)

docs.aws.amazon.com

1. AWS Secrets Manager

AWS Secrets Manager を使うと Personal Access Tokens (PAT) の管理を外部化できて,暗号化もできて,選択肢の中では1番良いと思う✌️今回は GitHubPat というシークレットを登録した.

AWSTemplateFormatVersion: 2010-09-09

Resources:
  AmplifyApp:
    Type: AWS::Amplify::App
    Properties:
      Name: sandbox-app-secrets-manager
      Repository: https://github.com/kakakakakku/xxxxx
      AutoBranchCreationConfig:
        EnableAutoBranchCreation: true
        EnableAutoBuild: true
      EnableBranchAutoDeletion: true
      AccessToken: '{{resolve:secretsmanager:GitHubPat:SecretString:GitHubPat}}'
  MasterBranch:
    Type: AWS::Amplify::Branch
    Properties:
      AppId: !GetAtt AmplifyApp.AppId
      BranchName: master
      EnableAutoBuild: true

2. AWS Systems Manager Parameter Store

AWS Systems Manager Parameter Store を使っても Personal Access Tokens (PAT) の管理を外部化できるけど,SecureString のパラメータを埋め込めるサービスは一部しかなく制約がある.よって,今回は StringGitHubPat というパラメータを登録した.

docs.aws.amazon.com

AWSTemplateFormatVersion: 2010-09-09

Resources:
  AmplifyApp:
    Type: AWS::Amplify::App
    Properties:
      Name: sandbox-app-secrets-manager
      Repository: https://github.com/kakakakakku/xxxxx
      AutoBranchCreationConfig:
        EnableAutoBranchCreation: true
        EnableAutoBuild: true
      EnableBranchAutoDeletion: true
      AccessToken: '{{resolve:ssm:GitHubPat}}'
  MasterBranch:
    Type: AWS::Amplify::Branch
    Properties:
      AppId: !GetAtt AmplifyApp.AppId
      BranchName: master
      EnableAutoBuild: true

3. Parameters (NoEcho)

選択肢の中で一番簡単に実現できるのは AWS CloudFormation の Parameters で,NoEcho を設定することでパラメータを ***** としてマスキングできる(あくまで表示上).

AWSTemplateFormatVersion: 2010-09-09

Parameters:
  GitHubPat:
    Type: String
    Default: ''
    NoEcho: true

Resources:
  AmplifyApp:
    Type: AWS::Amplify::App
    Properties:
      Name: sandbox-app-parameters-noecho
      Repository: https://github.com/kakakakakku/xxxxx
      AutoBranchCreationConfig:
        EnableAutoBranchCreation: true
        EnableAutoBuild: true
      EnableBranchAutoDeletion: true
      AccessToken: !Ref GitHubPat
  MasterBranch:
    Type: AWS::Amplify::Branch
    Properties:
      AppId: !GetAtt AmplifyApp.AppId
      BranchName: master
      EnableAutoBuild: true

まとめ

コンソールだと比較的簡単にデプロイできるのに AWS CloudFormation 化をしようとすると悩んだりすることはよくある❗️今回は CloudFormation と GitHub の Personal Access Tokens で AWS Amplify Hosting アプリケーションをデプロイするテンプレートを3種類準備してみた.誰かの参考になれば〜💡

Mailtrap を使って受信メール確認を自動テストに組み込む

メール配信プラットフォーム「Mailtrap」を使うと,開発中にメールをテストしたり,実際にメールを配信したり,メール関連の悩みを幅広く解決できる❗️ちょうどメール関連のテスト戦略を設計していて,気になっていた Mailtrap の Email Testing を試してみた✌️

mailtrap.io

Mailtrap を本格的に使うなら Business Plan 以上にすると良さそう.1ヶ月に 50,000 件もメールを受信できるし,テストに使える専用メールアドレスも割り当てられる.しかし Free Plan でも1ヶ月に 100 件はメールを受信できるし(Inbox に残せるのは 50 件まで),Mailtrap API も使えるし,検証目的なら十分そう👌

mailtrap.io

\( 'ω')/ Mailtrap ならメールテストを自動テストにも組み込めるぞ〜

Project と Inbox 📩

まず,Mailtrap の画面で Project(プロジェクト)と Inbox(受信ボックス)を作る.今回は検証用に Sandbox ProjectSandbox Inbox という名前にした.

Project と Inbox

curl でメールを送信する 📩

次に Inbox に載っているサンプルコードを使って Mailtrap の SMTP 経由でメールを送信する.最初から準備されてるサンプルコードは Ruby on Rails / Django / Laravel など,計23種類ほどある(ちょっと選択肢は古めの感じもあるけど...).今回は一番お手軽に curl コマンドを使ってメールを送信することにした❗️

  • Command Line
    • cURL
  • Ruby
    • Ruby on Rails
    • Ruby (net/smtp)
  • Python
    • smtplib
    • Django
    • Flask-Mail
  • etc

ちなみに Mailtrap の Business Plan 以上だと Inbox email address 機能(専用メールアドレス)もある.アプリケーション側に実装してあるメール送信機能の宛先に指定することもできる💡使えるともっと良さそう〜

help.mailtrap.io

さっそく以下の curl コマンドを実行した(--user はマスキングしてある).

$ curl  \
--ssl-reqd \
--url 'smtp://sandbox.smtp.mailtrap.io:2525' \
--user 'xxxxx' \
--mail-from from@example.com \
--mail-rcpt to@example.com \
--upload-file - <<EOF
From: Magic Elves <from@example.com>
To: Mailtrap Inbox <to@example.com>
Subject: You are awesome!
Content-Type: multipart/alternative; boundary="boundary-string"

--boundary-string
Content-Type: text/plain; charset="utf-8"
Content-Transfer-Encoding: quoted-printable
Content-Disposition: inline

Congrats for sending test email with Mailtrap!

Inspect it using the tabs above and learn how this email can be improved.
Now send your email using our fake SMTP server and integration of your choice!

Good luck! Hope it works.

--boundary-string
Content-Type: text/html; charset="utf-8"
Content-Transfer-Encoding: quoted-printable
Content-Disposition: inline

<!doctype html>
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  </head>
  <body style="font-family: sans-serif;">
    <div style="display: block; margin: auto; max-width: 600px;" class="main">
      <h1 style="font-size: 18px; font-weight: bold; margin-top: 20px">Congrats for sending test email with Mailtrap!</h1>
      <p>Inspect it using the tabs you see above and learn how this email can be improved.</p>
      <img alt="Inspect with Tabs" src="https://assets-examples.mailtrap.io/integration-examples/welcome.png" style="width: 100%;">
      <p>Now send your email using our fake SMTP server and integration of your choice!</p>
      <p>Good luck! Hope it works.</p>
    </div>
    <!-- Example of invalid for email html/css, will be detected by Mailtrap: -->
    <style>
      .main { background-color: white; }
      a:hover { border-left-width: 1em; min-height: 2em; }
    </style>
  </body>
</html>

--boundary-string--
EOF

すると,送信したメールが Inbox に届く❗️スパム分析 (Spam Analysis) や HTML チェック (HTML Check) 機能もある.メール本文のデザイン崩れなどをデバイスごとに確認できたりもする.便利〜

メールを受信できた

Mailtrap API でメールを取得する 📩

Mailtrap には Mailtrap API もあって,幅広い操作を自動化できる❗️

api-docs.mailtrap.io

1. Show email message

まず,Show email message API を実行して,メールを取得する.以下の値は Mailtrap の画面から事前に取得しておく📝 ちなみに今回は上に載せたメールを取得するために MESSAGE_ID を Mailtrap の画面から取得したけど,実際に使う場合はその前に Get messages API を実行して一覧を取得するのが良いと思う.

  • ACCOUNT_ID
  • INBOX_ID
  • TOKEN
  • MESSAGE_ID
$ ACCOUNT_ID=0000
$ INBOX_ID=0000000
$ TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
$ MESSAGE_ID=0000000000

$ curl -s "https://mailtrap.io/api/accounts/${ACCOUNT_ID}/inboxes/${INBOX_ID}/messages/${MESSAGE_ID}" \
>   --header 'Accept: application/json' \
>   --header "Api-Token: ${TOKEN}" | jq .
{
  "id": 0000000000,
  "inbox_id": 0000000,
  "subject": "You are awesome!",
  "sent_at": "2023-05-14T12:23:52.922Z",
  "from_email": "from@example.com",
  "from_name": "Magic Elves",
  "to_email": "to@example.com",
  "to_name": "Mailtrap Inbox",
  "email_size": 1632,
  "is_read": true,
  "created_at": "2023-05-14T12:23:52.923Z",
  "updated_at": "2023-05-14T12:25:30.445Z",
  "html_body_size": 952,
  "text_body_size": 228,
  "human_size": "1.6 KB",
  "html_path": "/api/accounts/0000/inboxes/0000000/messages/0000000000/body.html",
  "txt_path": "/api/accounts/0000/inboxes/0000000/messages/0000000000/body.txt",
  "raw_path": "/api/accounts/0000/inboxes/0000000/messages/0000000000/body.raw",
  "download_path": "/api/accounts/0000/inboxes/0000000/messages/0000000000/body.eml",
  "html_source_path": "/api/accounts/0000/inboxes/0000000/messages/0000000000/body.htmlsource",
  "blacklists_report_info": {
    "result": "success",
    "domain": "example.com",
    "ip": "93.184.216.34",
    "report": [
      {
        "name": "BACKSCATTERER",
        "url": "http://www.backscatterer.org/index.php",
        "in_black_list": false
      },
      {
        "name": "BARRACUDA",
        "url": "http://barracudacentral.org/rbl",
        "in_black_list": false
      },
      {
        "name": "Spamrbl IMP-SPAM",
        "url": "http://antispam.imp.ch/?lng=1",
        "in_black_list": false
      },
      {
        "name": "Wormrbl IMP-SPAM",
        "url": "http://antispam.imp.ch/?lng=1",
        "in_black_list": false
      },
      {
        "name": "LASHBACK",
        "url": "http://blacklist.lashback.com/",
        "in_black_list": false
      },
      {
        "name": "NIXSPAM",
        "url": "https://www.heise.de/ix/NiX-Spam-DNSBL-and-blacklist-for-download-499637.html",
        "in_black_list": false
      },
      {
        "name": "PSBL",
        "url": "https://psbl.org/",
        "in_black_list": false
      },
      {
        "name": "SORBS-SPAM",
        "url": "http://www.sorbs.net/lookup.shtml",
        "in_black_list": false
      },
      {
        "name": "SPAMCOP",
        "url": "http://spamcop.net/bl.shtml",
        "in_black_list": false
      },
      {
        "name": "TRUNCATE",
        "url": "http://www.gbudb.com/truncate/index.jsp",
        "in_black_list": false
      }
    ]
  },
  "smtp_information": {
    "ok": false
  }
}

api-docs.mailtrap.io

2. Get raw message

次にメール本文を取得するために Get raw message API や Get text message API を実行する.今回は Get raw message API を実行して,raw のまま取得する.

$ ACCOUNT_ID=0000
$ INBOX_ID=0000000
$ TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
$ MESSAGE_ID=0000000000

$ curl -s "https://mailtrap.io/api/accounts/${ACCOUNT_ID}/inboxes/${INBOX_ID}/messages/${MESSAGE_ID}/body.raw" \
>   --header 'Accept: text/plain, application/json' \
>   --header "Api-Token: ${TOKEN}"
From: Magic Elves <from@example.com>
To: Mailtrap Inbox <to@example.com>
Subject: You are awesome!
Content-Type: multipart/alternative; boundary="boundary-string"

--boundary-string
Content-Type: text/plain; charset="utf-8"
Content-Transfer-Encoding: quoted-printable
Content-Disposition: inline

Congrats for sending test email with Mailtrap!

Inspect it using the tabs above and learn how this email can be improved.
Now send your email using our fake SMTP server and integration of your choice!

Good luck! Hope it works.

--boundary-string
Content-Type: text/html; charset="utf-8"
Content-Transfer-Encoding: quoted-printable
Content-Disposition: inline

<!doctype html>
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  </head>
  <body style="font-family: sans-serif;">
    <div style="display: block; margin: auto; max-width: 600px;" class="main">
      <h1 style="font-size: 18px; font-weight: bold; margin-top: 20px">Congrats for sending test email with Mailtrap!</h1>
      <p>Inspect it using the tabs you see above and learn how this email can be improved.</p>
      <img alt="Inspect with Tabs" src="https://assets-examples.mailtrap.io/integration-examples/welcome.png" style="width: 100%;">
      <p>Now send your email using our fake SMTP server and integration of your choice!</p>
      <p>Good luck! Hope it works.</p>
    </div>
    <!-- Example of invalid for email html/css, will be detected by Mailtrap: -->
    <style>
      .main { background-color: white; }
      a:hover { border-left-width: 1em; min-height: 2em; }
    </style>
  </body>
</html>

--boundary-string--

api-docs.mailtrap.io

Mailtrap と自動テストを組み合わせる 📩

Mailtrap API でメールを取得できるようになったので,活用例として自動テストと組み合わせることもできる❗️今回はサンプルとして Python の unittest と組み合わせたテストコードを書いてみた.メールを取得する get_message() とメール本文を取得する get_message_body()requests などの HTTP ライブラリを使って Mailtrap API を実行するように実装しつつ(今回は割愛),あとは from_emailsubjectbody など,重要な値をアサートしておくと良さそう💡

from mailtrap import Mailtrap
import unittest


class TestMailtrap(unittest.TestCase):
    def test_get_message(self):
        message = Mailtrap().get_message()
        self.assertEqual('from@example.com', message['from_email'])
        self.assertEqual('You are awesome!', message['subject'])

    def test_get_message_body(self):
        body = Mailtrap().get_message_body()
        self.assertIn('Good luck! Hope it works.', body)


if __name__ == '__main__':
    unittest.main()

実行すると...テスト2件とも成功している👏

..
----------------------------------------------------------------------
Ran 2 tests in 1.935s

OK

まとめ

メール配信プラットフォーム「Mailtrap」Email Testing を試してみた✌️

Mailtrap の機能は他にもまだまだたくさんあるし,引き続き検証を続けていくぞー❗️