FTPサーバーとしてのS3とフォルダ振り分け


FTPサーバーもサーバーレスに

ファイルをFTPサーバーで受信するような旧来型のあるサービスをAWSに移行したときの話です。
移行後もFTPサーバーとしての EC2 を立てて…というのはなんだかなぁということで、S3 に配信することにしました。

このとき、まず仕組みとして考えたのが以下の構成。

  • (HTTP) CloudFront → API Gateway → Lambda → S3

見様見真似で構築自体は問題なくいきましたが、配信元システムの仕様でHTTPでのファイル配信では運用できないことが分かりました。

そこでFTPで配信する仕組みを構築すべく、AWS Transfer Family を検討しました。

AWS Transfer Family

従来はSFTPのみサポートしていたマネージドサービスですが、今年からFTP/FTPSをサポートするようになりました。まさに僥倖。
※FTPやめようよ…

構築について詳しくはこちらを参考に。
AWS Transfer for SFTPがFTP/FTPSをサポートしました!

Cloud Formation でテンプレートから構築されます。

  • Transfer for SFTP
  • API Gateway
  • Lambda

FTPの場合、認証はカスタムするしかないので、Secrets Manager を利用することにして、作成された Lambda から読み込むようにしました。
(Secrets Manager の読み込みはサンプルコードが生成されるのでそれを元に)

あとは、つくられた VPC のエンドポイントにアクセス確認。

問題なければ、配信元のシステムの設定も更新します。
これで無事、S3にFTP配信される仕組みが整いました。

配信されたファイルをフォルダに振り分ける

あとは元々、ファイル名から適切なフォルダに振り分ける(なければつくる)という処理が入っていました。
このままだとバケットのルートにファイルが溜まり続けるので、S3トリガーで動く Lambda を作成します。

トリガーは S3 ファイルPUT時。
ファイルを適切なフォルダに移動(コピー&削除)する処理を Lambda で記述します。

  • Pythonでの例
move.py
import logging
import urllib.parse
import boto3

logger = logging.getLogger()
logger.setLevel(logging.INFO)

def handler(event, context):
    try:
        client = boto3.client('s3')
        bucket_name = event['Records'][0]['s3']['bucket']['name']

        src = urllib.parse.unquote_plus(event['Records'][0]['s3']['object']['key'], encoding='utf-8')

        ## ここで対象ファイルのチェックなど

        ## 向き先のフォルダ名を決めるなど

        dst = folder_name + '/' + src

        client.copy_object(Bucket=bucket_name, Key=dst, CopySource={'Bucket': bucket_name, 'Key': src})
        client.delete_object(Bucket=bucket_name, Key=src)

        logger.info("moved file: from[" + src + "]" + ", to[" + dst + "]")

        return True

    except Exception as e:
        logger.error(e)

        return False

トリガーを「すべてのイベント時」ではなく PUT に限定することで、このファイル移動処理での COPY のときにも起動されないようにしています。

これで、サーバーレスなFTPサーバー(?)ができました。