テスト用のダミーSMTPサーバ「smtp4dev」をAWSのマネージドコンテナ環境、ECS/Fargateを使ってサーバーレスで動かします。
サーバーレスで動かすことで、EC2の管理が不要となり、コストも抑えることができます。
メール送信アプリケーションをAWSで開発するときの開発・テストが便利になると思います。
はじめに
本記事はAWSやコンテナについて知識があり、使用経験があるかたを読者として想定しています。
個々のサービスや用語についての説明は割愛しますので、必要に応じてAWSの公式ドキュメントなどを参考に調べていただけるとありがたいです。
実現すること
AWSのプライベートネットワークでsmtp4devをECS/Fargateのサーバーレス環境で動かしテストメールを送信できるようにし、さらにデータの永続化を実現します。
AWSでダミーのSMTPサーバを動かす
SMTPでのメール送信アプリケーションを開発・テストする際に、安全かつ効率的にテストメールを送信するためのダミーの送信先SMTPサーバーとして使用できるsmtp4devというアプリケーションがあります。
Windows、Linux、コンテナなどで動かすことができます。
メールアプリケーションの稼働確認として実際にどのようなメールが送られるのか確認できます。
ECS/Fargateで動かす
smtp4devはEC2で起動するのは容易ですが、さりとてこのためだけにEC2の管理はしたくありません。
コンテナ版smtp4devをECSの単体タスクとして起動することもできますが、SMTPサーバとしてアプリケーションからの通信を受けることになるので、IPアドレスがタスク再起動のたびに変更されるのも使いづらいです。
ですのでコンテナ版をECS/Fargateの「サービス」として動かします。
Amazon ECSについてはこちら
Fargateについてはこちら
インターネット疎通のない「プライベートネットワーク」で動かす
smtp4devのコンテナはDocker Hubにホストされているので、一般的にはインターネットを通じたコンテナイメージの取得が必要です。
しかし今回はセキュリティ関連の要件が厳しくインターネットへの直接疎通が制限された環境を想定し、直接のインターネット疎通のないプライベートネットワークで動かします。
smtp4devのデータをAWSのストレージサービスで永続化する
コンテナのデータは揮発性です。コンテナ内にデータを持つと、smtp4devの設定やメールも再起動で消えてしまいます。
今回はECSで使用できるAWSのストレージサービスを使用して、smtp4devのデータを永続化します。
事前準備
必要なAWS環境を準備します。
使用リージョンはアイルランド(eu-west-1)リージョンです。
※サービス展開が早い傾向があるのでアイルランドを使用しています。別のリージョンでも手順は同じですので、ご自分の環境に合わせて適切なリージョンを選択ください。
VPC/Subnetの作成
まずVPCを作成します。
今回はPublic3つ(-pub)、Private3つ(-prv)の6サブネットを作成しました。
publicサブネットには踏み台サーバを配置します。
AWSのネットワークについてはこちら
またコンテナに付与するセキュリティグループも作成しておきます。
VPC Endpointの作成
プライベートネットワークでECSタスクを動かすためには、コンテナのPullに使用するECRとS3のエンドポイント、コンテナのログをCloudwatch Logsに送るためのエンドポイントが必要です。
以下のVPC Endpointを作成します。
- com.amazonaws.eu-west-1.ecr.dkr(Interface)
- com.amazonaws.eu-west-1.ecr.api(Interface)
- com.amazonaws.eu-west-1.logs(Interface)
- com.amazonaws.eu-west-1.s3(Gateway)
を作成します。
必要なVPC Endpointについてはこちら
ECSクラスターの作成
ECSのクラスターを作成しておきます。
クラスター名はtstclstr01
としました。
Docker HUBへのECR Pull Through Cacheの設定
Docker Hubからのsmtp4devのコンテナImageの取得をプライベートサブネットで可能にするため、ECRでDocker HubへのPull Through Cacheを設定します。
Pull Through Cacheについてはこちら
ECRのコンソールから以下を選択して設定していきます。
Pull Through CacheのアップストリームとしてDocker Hubを選択します。
認証情報としてDocker HubのアカウントとAPIキーを入力します。
Pull Through Cacheの設定を行います。
これで、プライベートサブネットから、ECRのVPCエンドポイント経由でDockerHubのイメージが取得できるようになります。
取得する時のコマンドは以下のようになります。
ECRのリポジトリURIにPull Through Cacheで設定したPrefixがついたものになります。
docker pull xxxxxxxxxxx.dkr.ecr.eu-west-1.amazonaws.com/docker-hub/{UpstreamRepositoryName}:{ImageTag}
ロードバランサーとターゲットグループ
smtp4devの起動ガイドにあるように、smtp4devのコンテナのSMTP(25)とHTTP(80)のポートに通信を行うように構成します。
docker run --rm -it -p 5000:80 -p 2525:25 rnwood/smtp4dev
80ポートの通信はsmtp4devのWEB画面にアクセスして、smtp4devの設定やメールの確認のために使用します。
転送先が二つあるので、ターゲットグループも二つ必要なのですが、ECSのサービスはGUIで設定すると1つのサービスに一つのターゲットグループしか定義できません。
そこで、Cloudformationで一つのサービスに複数のターゲットグループを定義する方法をとります。
あらかじめロードバランサーやターゲットグループを定義しておく必要があります。
まずロードバランサーを作成します。
SMTPの通信を受けるためNLBで作成します。
ターゲットグループをPort 25とPort 80で二つ作成します。
ターゲットタイプはIPで作成し、ターゲットは登録しません。
Port 25とPort 80のリスナーを作成し、ターゲットグループを登録します。
設定や受信したメールの永続化
コンテナの場合、タスクが再起動するたびにコンテナのストレージは初期化されるので、受信したテストメールや設定は消えてしまいます。
smtp4devのInstallationガイドによると、/smtp4dev
というパスに設定等が保存されるとあります。
The folder /smtp4dev will be used for the database and auto-generated TLS certificate. You can mount a directory outside of the container here for peristent storage.
ECSの場合、外部ストレージとしてAWSのストレージサービスであるAmazon EFS(Elastic File system)が使用できます。
EFSについてはこちら
EFSを作成し、永続ストレージとしてコンテナの/smtp4dev
にマウントします。
一旦別のEC2からマウントしsmtp4devのデータを保存するパス/smtpdata
を作成しておきます。
$ sudo mount -t efs -o tls fs-08d8e02b6fd668856:/ /mnt/efs
$ sudo mkdir /mnt/efs/smtpdata
smtp4dev用タスク定義の作成
ECSのタスク定義を作成します。
タスク定義の名前はdummy-smtp4dev-1
としました。
タスク定義では以下を設定します。
起動タイプ
タスクの起動タイプはFargateを指定します。
イメージURL
コンテナ名はsmtp4devとしました。
イメージURIは事前に作成したPull Through CacheのURLとsmtp4devのリポジトリを指定します。
コンテナのポート
ボリューム
事前に作成したEFSの/smtpdata
をコンテナsmtp4dev
の/smtp4dev
にマウントするように設定します。
ボリュームを定義します。
ボリュームをコンテナにマウントします。コンテナパスに/smtp4dev
を指定します。
ECSサービスのデプロイ
Cloudformationを使用してサービスを作成します。
Cloudformationコード
サービスの作成には以下のCloudformationテンプレートを使用します。
LoadBalancer
以下にコンテナとターゲットグループの組み合わせを複数記載することで、一つのサービスに対して複数のターゲットグループを設定できます。
AWSTemplateFormatVersion: '2010-09-09'
Description: ecs multi tgt svc
Resources:
service:
Type: AWS::ECS::Service
Properties:
#ECSクラスター名の指定
Cluster: tstclstr01
#タスク数は1
DesiredCount: 1
#Fargate起動タイプを指定
LaunchType: FARGATE
#事前に作成したタスク定義を指定
TaskDefinition: dummy-smtp4dev-1:1
#作成するサービス名
ServiceName: testsvc
#スケジューラはレプリカを指定
SchedulingStrategy: REPLICA
LoadBalancers:
#事前作成した80ポートと25ポートのターゲットグループのARNを指定
- ContainerName: smtp4dev
ContainerPort: 80
TargetGroupArn: arn:aws:elasticloadbalancing:eu-west-1:xxxxxxxxxxxx:targetgroup/tgt-ecssvc-http/2b53f6a0ff81f3d4
- ContainerName: smtp4dev
ContainerPort: 25
TargetGroupArn: arn:aws:elasticloadbalancing:eu-west-1:xxxxxxxxxxxx:targetgroup/tgt-ecssvc-smtp/139f95508c75ec76
NetworkConfiguration:
AwsvpcConfiguration:
#プライベートネットワークなのでパブリックIPは付与しない。
AssignPublicIp: DISABLED
#事前作成したセキュリティグループとサブネットを指定
SecurityGroups:
- sg-01bb32c5719e2fbfd
Subnets:
- subnet-0b6129430274e59ed
- subnet-0afecdc596823b4fa
- subnet-043482be4aaa20168
Cloudformation スタックの作成
マネジメントコンソールからCloudformation テンプレートをデプロイし、サービスを作成します。
以下のように複数ターゲットグループへの割り振りを行うNLBを持ったサービスが作成できました。
動作確認
WEB設定画面へのアクセス
踏み台EC2のブラウザからNLBのアドレスにアクセスします。
以下のようなsmtp4devのUIが表示されます。
テストメールの送信
PowerShellのSend-MailMessage
を使ってからnlbのDNS名にあててテストメールを送信します。
PS C:\Users\Administrator> $ToADDR = "to@example.com"
>> $FromADDR = "from@example.com"
>> $SMTPHost = "testnlb-a8b32e9c0524cf93.elb.eu-west-1.amazonaws.com"
>> $PortNum = "25"
>> $Subject = "smtp4dev test"
>> $MailBody = "test mail"
>>
>> Send-MailMessage -To $ToADDR -From $FromADDR -SmtpServer $SMTPHost -Port $PortNum -Subject "$Subject" -Body $MailBody -Encoding UTF8
EFSによるデータ永続化の確認
ECSのコンソールからタスクを強制的に終了します。
EFSにはこのようなファイルが作成されていました。
$ ls -l /mnt/efs/smtpdata/
total 4200
-rw-r--r--. 1 root root 1296 Mar 26 05:35 appsettings.json
-rw-r--r--. 1 root root 106496 Mar 28 04:58 database.db
-rw-r--r--. 1 root root 32768 Mar 28 05:08 database.db-shm
-rw-r--r--. 1 root root 4148872 Mar 28 05:08 database.db-wal
-rw-r--r--. 1 root root 748 Mar 26 05:11 selfsigned-certificate.cer
-rw-r--r--. 1 root root 2343 Mar 26 05:11 selfsigned-certificate.pfx
$
まとめ
テスト用のダミーSMTPサーバ「smtp4dev」をAWSのマネージドコンテナ環境、ECS/Fargateを使ってサーバーレスで動かすことができました。
動かすために
- Cloudformationを使った複数ターゲットグループを持つECSサービスの作成
- プライベートサブネットでのDocker HUBのコンテナの起動
- EFSを使ったデータの永続化
を使用しました。
すべての手順が役立つとは限りませんが、この記事の一部でも参考になれば幸いです。