概要
AWS環境にて定期実行ジョブを実装するにあたり、運用ソフトウェアを導入できないケースを想定し、AWSサービスだけでジョブを実装するにはどうすれば良いか考えてみました。
パッと思いつく限りで、実装方法は以下があると考えています。
案1.EventBridgeのスケジュール実行にてLambdaを定期実行
案2.EC2インスタンスにてCronにより自動実行
案3.ECSタスクの「タスクのスケジューリング」にて自動実行
本記事では案3の実装方法を記載します。案3は他案と比較し以下の特徴があると思います。
・ECSタスクのデータプレーンとしてFargateを選択すればEC2インスタンスを利用しないため、
インスタンスのメンテナンスが不要
・ECSタスクで稼働させるコンテナは利用者側で自由に選択可能なため
Lambdaのようにランタイムによる制約※はないこと
・コンテナのメンテナンスは必要であるため、コンテナに関する知見は必要
※:正確にはカスタムランタイムなどで指定が可能ですが、
標準で実装されている機能での比較
簡易的なバッチ処理をbashなどのシェルで実装したい、EC2のメンテナンスは実施たくない、などのケースでは案3が対応しやすいと思います。
本記事では案3のうち、ECSonFargateの構成でSFTPによるファイル連携ジョブを実装したいと思います。
構成
前提と構築順序は以下の通りです。前提としては構築済とします。
<前提>
・EC2(SFTPサーバ)のSFTPサーバの作成
・S3バケットとPUT対象ファイルの作成
・ECRリポジトリの作成
<構築順序>
・ジョブ実行用コンテナのDockerfile作成
・ジョブ実行用コンテナイメージ作成
・ECRへコンテナイメージの登録
・ECSタスク作成
・ECSタスクの「タスクのスケジューリング」にて定期実行の実装
ジョブ実行用コンテナのDockerfile作成
Dockerfileは以下の通りです。
FROM public.ecr.aws/amazonlinux/amazonlinux:2
RUN yum -y install shadow-utils
RUN yum -y --security update
RUN yum -y install expect
RUN yum -y install openssh-clients
RUN yum install -y unzip
RUN curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
RUN unzip awscliv2.zip
RUN aws configure list
COPY ./sftp-get.sh /
COPY ./id_rsa /
RUN aws s3 cp s3://<S3バケット名>/sftp-put.csv / --region ap-northeast-1
RUN useradd sftpuser
RUN chown sftpuser: sftp-get.sh
RUN chown sftpuser: id_rsa
USER sftpuser
CMD ["sh","/sftp-get.sh"]
ベースイメージはECR Public GallaryのAmazonLinux2を利用します。
ポイントを補足します。
・RUN yum -y install shadow-utils
コンテナの実行ユーザはデフォルトだとrootとなってしまいます。
セキュリティ強化を目的に別ユーザを「useradd」コマンドで実行しますが、
amazonlinux2は当コマンドを実行するためのモジュール「shadow-utils」が入っていないため
パッケージをインストールします。
・RUN yum -y install expect
SFTPは対話形式にて行います。バッチシェルで対話形式を自動で実現するために必要なパッケージです。
「sftp-get.sh」を参照。
・CMD ["sh","/sftp-get.sh"]
SFTPを実行するシェル。以下にコードを記載。
#!/bin/sh
# sftpサーバのIPアドレス
HOST="xxx.xxx.xxx.xxx"
# sftpにてアクセスする際のユーザ
USER="xxxxx"
# sftp接続用ポート
PORT="22"
# SFTPは秘密鍵認証にて実施する。秘密鍵ファイル(id_rsa)はDockerfileでシェルのカレントに配置
ID_RSA="./id_rsa"
# PUT対象のファイル。PUT対象のファイル(sftp-put.csv)はDockerfileでシェルのカレントに配置
PUT_FILE="./sftp-put.csv"
# sftpサーバのput先ディレクトリに移動するコマンド
CMD_CD="cd sftp-work-dir"
# sftpサーバのput先ディレクトリ
CMD_PUT="put ${PUT_FILE}"
# SFTP実行コマンド
expect -c "
set timeout 5
spawn sftp -v -o StrictHostKeyChecking=no -i $ID_RSA $USER@$HOST
expect \"sftp\"
send \"${CMD_CD}\r\"
expect \"sftp\"
send \"${CMD_PUT}\r\"
expect \"sftp\"
send \"bye\r\"
expect eof
exit
"
シェルは以下を参考にさせていただきました。
参考リンク(Qiita)
コンテナイメージの作成
Dockerfileが配置されたカレントディレクトリでdocker buildコマンドを実行。
イメージのタグは最初からECRへpushできるよう指定しておきます。
実行はcloud9で行っています。
> docker build -t XXXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/sftp-put-image:v01 .
ECRへコンテナイメージの登録
ECRへの認証後、ECRへpushします。
> aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin XXXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com
> docker push XXXXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/sftp-put-image:v01
ECSタスク作成
ECSタスク定義をもとにECSタスクが起動し、ECSタスクはECSクラスタの中で稼働します。
ECSクラスタにてECSタスクのスケジュール実行が定義できます。
このため、作成の順序としては
1.ECSタスク定義
2.ECSクラスタ作成
3.タスクのスケジューリング
でいきます。
1.ECSタスク定義
今回はFargate上で稼働させるため、Fargateを選択し作成します。
タスク定義にて指定するコンテナは「ECRへコンテナイメージの登録」で登録したコンテナイメージを指します。
ECSクラスタの作成
ECSクラスタはネットワーキングのみでOK。
タスクのスケジューリング
ECSクラスタの「タスクのスケジューリング」より作成を押下。
スケジュールタイプは以下から選択。
・固定された間隔で実行
・Cron式
今回はCron式を定義。毎時10分に起動するよう定義。
ターゲットのスケジュールは作成したタスク定義などを指定。
見慣れない「ターゲットID」は任意の名称で問題ないはず。
残りの設定であるVPCなどは所定のものを指定。
なお、タスクのスケジューリングの実態はCloudWatchEventとなります。
ルール一覧を見ると先ほど作成した「タスクのスケジューリング」のルールが作成されています。
結果
注意
実施にやってみてわかりましたが、厳密に設定した時刻にECSタスクが実行されるわけではなく、数秒程度のラグはありました。
また、コンテナの起動には時間がかかるため、そこまで時刻に厳密性が伴われない処理である必要はあると思います。