1. yut0201

    Posted

    yut0201
Changes in title
+【AWS】ECR, ECS(Fargate), StepFunctions, CloudWatch でバッチ処理~後編~
Changes in tags
Changes in body
Source | HTML | Preview
@@ -0,0 +1,338 @@
+# はじめに
+
+本記事は、AWS FargateタスクとActivityステートマシンを利用して
+・ソースの管理用リポジトリ(ECR)
+・Activityをポーリングするワーカー
+・アクティビティの処理内容
+を実装しつつ、CloudWatchで定期実行するバッチ処理を作成するための概要(&備忘録)の**後編**です。
+
+**前編**は[こちら](https://qiita.com/yut0201/items/5a8d5876b0fbfcafb82f)です。
+
+筆者自身、まだ理解が追いついていないと感じる部分はありますが、初学者の方の一助にでもなれば幸いです。
+
+誤字・脱字や前編での疑問点などあればコメントいただけると幸いです。
+
+改めて、手順の全体は以下の通り。
+
+1. ECR リポジトリの作成
+2. アクティビティステートマシンの作成
+ 1. アクティビティの作成
+ 2. アクティビティステートマシン用のタスク定義ファイルを作成
+ 3. アクティビティステートマシンの作成
+3. docker イメージの準備
+ 1. ソースのbuildとタグ付け
+ 2. リポジトリへpush
+4. ECS クラスターの作成
+5. ECS タスク定義の作成
+ 1. コンテナに登録するタスク定義ファイルの作成
+ 2. Fargateタスク定義の登録
+6. CloudWatch Event 設定
+ 1. Fargateタスク起動用ルールの作成
+ 2. Fargateタスク起動用ターゲットの作成と割り当て
+ 3. アクティビティステートマシン用ルールの作成
+ 4. アクティビティステートマシン用ターゲットの作成と割り当て
+
+上記の `2-3. アクティビティステートマシンの作成`までを前編で実施済みなので、以降は `3. dockerイメージの準備`からとします。
+
+## 事前準備
+
+### Lambdaファンクションの作成
+
+サンプルは以下のようなコードです。コピペしてそのまま使えます。
+必要なSDKをインストールするために pom.xml を編集する必要があるかと思いますが、今回は割愛します。
+
+```java:サンプルコード
+package AwsServices.samples;
+
+import java.io.IOException;
+import java.util.Properties;
+import java.util.concurrent.TimeUnit;
+
+import com.amazonaws.ClientConfiguration;
+import com.amazonaws.auth.EnvironmentVariableCredentialsProvider;
+import com.amazonaws.regions.Regions;
+import com.amazonaws.services.stepfunctions.AWSStepFunctions;
+import com.amazonaws.services.stepfunctions.AWSStepFunctionsClientBuilder;
+import com.amazonaws.services.stepfunctions.model.GetActivityTaskRequest;
+import com.amazonaws.services.stepfunctions.model.GetActivityTaskResult;
+import com.amazonaws.services.stepfunctions.model.SendTaskFailureRequest;
+import com.amazonaws.services.stepfunctions.model.SendTaskSuccessRequest;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.amazonaws.util.json.Jackson;
+
+public class GreeterActivities {
+
+ static final com.amazonaws.regions.Regions TARGET_REGION = Regions.AP_NORTHEAST_1;
+ static final String ACTIVITY_HELLO_WORLD = "HelloWorldActivity.properties";
+
+ public void main(final String[] args) throws Exception {
+ GreeterActivities greeterActivities = new GreeterActivities();
+ ClientConfiguration clientConfiguration = new ClientConfiguration();
+ clientConfiguration.setSocketTimeout((int)TimeUnit.SECONDS.toMillis(0)); // 0 = タイムアウトなし
+
+ // StepFunctions設定ファイルの読み込み
+ Properties properties = new Properties();
+ try {
+ properties.load(this.getClass().getResourceAsStream("/env/" + ACTIVITY_HELLO_WORLD));
+ } catch (IOException e) {
+ System.out.println("Failed : cannot read of properties of StepFunctions Activity.\n" + e.getMessage() + "\n");
+ }
+
+ AWSStepFunctions client = AWSStepFunctionsClientBuilder.standard()
+ .withRegion(TARGET_REGION).withCredentials(new EnvironmentVariableCredentialsProvider())
+ .withClientConfiguration(clientConfiguration)
+ .build();
+
+ while (true) {
+ // ワーカーの実装
+ GetActivityTaskResult getActivityTaskResult = client.getActivityTask(
+ new GetActivityTaskRequest().withActivityArn(properties.getProperty("activity.arn")));
+
+ if (getActivityTaskResult.getTaskToken() != null) {
+ try {
+ JsonNode json = Jackson.jsonNodeOf(getActivityTaskResult.getInput());
+ String greetingResult = greeterActivities.helloWorld(json.get("world").textValue());
+ client.sendTaskSuccess(
+ new SendTaskSuccessRequest().withOutput(greetingResult).withTaskToken(getActivityTaskResult.getTaskToken()));
+ } catch (Exception e) {
+ client.sendTaskFailure(new SendTaskFailureRequest().withTaskToken(
+ getActivityTaskResult.getTaskToken()));
+ }
+ } else {
+ Thread.sleep(1000);
+ }
+ }
+ }
+
+ // タスクの実際の処理内容
+ private String helloWorld(String world) {
+ return "{\"Hello\": \"" + world + "\"}";
+ }
+}
+
+```
+
+/src/main/resources/env 以下には HelloWorldActivity.properties を作成します。
+
+```properties
+access.key=$IAM_ACCESS_KEY
+secret.key=$IAM_ACCESS_SECRET
+activity.arn=arn:aws:states:$REGION:$ACCOUNT_ID:activity:HelloWorldActivity
+```
+
+# 3. dockerイメージの準備
+
+上記のLambdaファンクションのタスク本体を含めてdockerイメージを作成するため、`Dockerfile`を作成しましょう。
+
+```dockerfile:Dockerfileの例
+FROM openjdk:8-jre-alpine
+WORKDIR /opt/app/
+ADD ./XXXXXXXX-0.0.1-SNAPSHOT.jar /opt/app/
+
+EXPOSE $PORT
+RUN sh -c 'touch /opt/app/XXXXXXXX-0.0.1-SNAPSHOT.jar'
+ENTRYPOINT [ "sh", "-c", "java -Dlog4j.configurationFile=log4j2.xml -cp /opt/app/XXXXXXXX-0.0.1-SNAPSHOT.jar com.personal.XXXXXXXX. GreeterActivities.main" ]
+```
+
+`XXXXXXXX`となっているプロジェクト名や、EXPOSEするポートは環境に合わせて定義して下さい。
+Dockerfileの各パラメータなどについては、本記事では扱いません。
+タスクとして作成したJavaのソースを`maven`ビルドし、出来上がったjarファイルをDockerfileと同じディレクトリに格納しましょう。
+
+## 3-1. ソースのビルドとタグ付け(コマンド)
+
+タグ付けとビルドはコマンドで行ってしまいます。
+
+```shell
+docker build -t $REPOSITORY:$TAG .
+```
+
+ここで使用した`$REPOSITORY`は[前編](https://qiita.com/yut0201/items/5a8d5876b0fbfcafb82f#1-ecr-%E3%83%AA%E3%83%9D%E3%82%B8%E3%83%88%E3%83%AA%E3%81%AE%E4%BD%9C%E6%88%90--%E3%83%96%E3%83%A9%E3%82%A6%E3%82%B6)で作成したリポジトリのARNを使用します。
+`$TAG`はバージョンを使用すると管理が楽ですが、特に気にしない場合は**latest**などとしておきましょう。
+
+末尾の`.(dot)`はソース及びDockerfileのあるディレクトリを指定します。
+
+## 3-2. リポジトリへのPush(コマンド)
+
+こちらもコマンドで実施します。
+
+```shell
+docker push $IMAGE_NAME
+```
+
+`3-1`でのビルド後、`docker images`コマンドなどでイメージの一覧が確認できますが、
+`$IMAGE_NAME`は`$REPOSITORY:$TAG`に相当します。
+これによりdockerコマンドが適切なリポジトリへPushしてくれます。
+
+# 4. ECSクラスターの作成
+
+Pushしたコンテナイメージを稼働させるためのクラスタを作成します。
+
+```shell
+aws ecs create-cluster --cluster-name "hello-world-cluster"
+```
+クラスタ名は任意です。ここでは仮に`hello-world-cluster`としておきます。
+
+# 5. ECS タスク定義の作成と登録
+
+## 5-1. コンテナに登録するタスク定義ファイルの作成
+コンテナが実際に実行するタスクの定義を、JSONで記述していきます。
+
+```json:タスク定義の例
+{
+ "family": "MyFamily",
+ "taskRoleArn": "arn:aws:iam::XXXXXXXXXXXX:role/MY_ROLE",
+ "executionRoleArn": "arn:aws:iam::XXXXXXXXXXXX:role:role/MY_ROLE",
+ "networkMode": "awsvpc",
+ "containerDefinitions": [
+ {
+ "name": "MyContainerName",
+ "image": "$REPOSITORY_URI",
+ "portMappings": [
+ {
+ "hostPort": 1234,
+ "protocol": "tcp",
+ "containerPort": 1234
+ },
+ {
+ "hostPort": 54321,
+ "protocol": "tcp",
+ "containerPort": 54321
+ }
+ ],
+ "essential": true,
+ "entryPoint": [
+ "コンテナが実行する docker run コマンドの --entrypoint オプションに渡す内容"
+ ],
+ "command": [
+ ""
+ ],
+ "environment": [
+ {
+ "name": "ENV",
+ "value": "development"
+ }
+ ],
+ "logConfiguration": {
+ "logDriver": "awslogs",
+ "options": {
+ "awslogs-group": "/logs/MyContainerLogs",
+ "awslogs-region": "ap-northeast-1",
+ "awslogs-stream-prefix": "logs"
+ }
+ }
+ }
+ ],
+ "requiresCompatibilities": [
+ "コンテナの起動タイプ。「EC2」または「FARGATE」を使用"
+ ],
+ "cpu": "2048",
+ "memory": "4096"
+}
+```
+
+各パラメータの定義は[こちら](https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/task_definition_parameters.html)を参照ください。
+
+タスク定義ファイルのJSONスケルトンだけが欲しい場合は、↓のコマンドで取得可能です。
+
+```shell
+aws ecs register-task-definition --generate-cli-skeleton
+```
+
+## 5-2. タスク定義の登録
+それでは、実際に作成したタスク定義をコンテナへ登録しましょう。
+
+```shell:登録用コマンド
+aws ecs register-task-definition --cli-input-json file://$TASK_DEFINITION_FILE
+
+# 確認
+aws ecs list-task-definitions
+```
+
+# 6. CloudWatch Event 設定
+
+## 6-1. Fargateタスク起動用ルールの作成
+
+FARGATEタスクを起動させるためのCloudWatchイベントを定義します。
+
+```shell
+aws events put-rule --schedule-expression "cron(0 20 10 * ? *)" --role-arn "arn:aws:iam::XXXXXXXXXXXX:role/MY_ROLE" --name MyTestRule
+```
+
+スケジュール(実行タイミング)に cron を使用していますが、引数の数はAWS独自で、通常のLinux系コマンドにある cron よりは1つ多いです。
+
+## 6-2. Fargateタスク起動用ターゲットの作成と割り当て
+イベントルールが定義できたら、イベントの呼び出し対象となるターゲットを定義します。
+
+```json
+{
+ "Rule": "MyTestRule", # 登録するルール名
+ "Targets": [
+ {
+ "Id": "target_01",
+ "Arn": "arn:aws:ecs:ap-northeast-1:XXXXXXXXXXXX:cluster/hello-world-cluster",
+ "RoleArn": "arn:aws:iam::XXXXXXXXXXXX:role/My_ROLE",
+ "EcsParameters": {
+ "TaskDefinitionArn": "arn:aws:ecs:ap-northeast-1:XXXXXXXXXXXX:task-definition/TestHelloWorldTask",
+ "TaskCount": 1,
+ "LaunchType": "FARGATE",
+ "NetworkConfiguration": {
+ "awsvpcConfiguration": {
+ "Subnets": [
+ "subnet-0a123456789az123"
+ ],
+ "SecurityGroups": [
+ "sg-0d00abcd1e1f1g999",
+ "sg-0123x4x999x9999xy"
+ ],
+ "AssignPublicIp": "ENABLED"
+ }
+ }
+ }
+ }
+ ]
+}
+```
+
+上記も例によってkeyに対するvalueを適当な値に置き換えています。
+作成したJSONを使用して、ルールにターゲットを割り当てます。
+
+```shell
+aws events put-targets --cli-input-json file://$TARGET_DEFINITION_FILE
+```
+
+> ここまで、アクティビティ、ステートマシン、イベントルール、ターゲットなど複数の要素が出てきて複雑化してしまっていますが、
+ポーリングなどに関する実行イメージは クラスメソッドさんの[この記事](https://dev.classmethod.jp/cloud/aws/aws-step-functions-activity/)が分かりやすいと思います。
+
+## 6-3. アクティビティステートマシン用ルールの作成
+
+Fargateタスクに関する設定は概ね完了したので、Fargateタスクがポーリングするアクティビティ用ステートマシンに関するスケジュールを登録していきます。
+
+```shell:アクティビティステートマシンを起動するためのEventを作成
+aws events put-rule --name $RULE_NAME --schedule-expression $SCHEDULE --role-arn "arn:aws:iam::XXXXXXXXXXXX:role/MY_ROLE"
+```
+
+## 6-4. アクティビティステートマシン用ターゲットの作成と割り当て
+イベントの作成ができたので、最後に該当のイベントにターゲットとしてアクティビティステートマシンを登録します。
+これにより、スケジューリングしたタイミングに、アクティビティステートマシンをアプリケーションからのポーリングを待つ状態で起動させることができます。
+
+```shell:
+aws events put-targets --rule vImportSqlServerDataByFargate \
+ --targets "Id"="vImportHoshoDataByFargate"\
+ ,"Arn"="arn:aws:states:ap-northeast-1:XXXXXXXXXXXX:stateMachine:$STATE_MACHINE_NAME","RoleArn"="arn:aws:iam::XXXXXXXXXXXX:role/MY_ROLE"
+```
+
+以上でセットアップは全て完了です。
+
+- それぞれのルールが定義したタイミングで起動している
+- アプリケーションがステートマシンをポーリングできている
+- ステートマシンが応答を返し、アプリケーションが実行できている
+
+といった点を確認できればハッピーエンドです :clap:
+
+# 最後に
+
+本記事は筆者の実行の履歴ベースで執筆しました。
+「◯◯を使えばもっと楽だよ」「◯◯は要らないのでは」などご指摘あればお願いします。
+
+長くなりましたが、最後までご覧いただきありがとうございました。m(_ _)m
+