データベース技術調査ブログ

LinuxやPostgreSQL、Oracleデータベース、AWSの知識をアウトプットしていきます

【AWS/ECS/Fargate】Embulkのジョブをサーバレスに実行する基盤を作ってみるチュートリアル(パート2)

以下の記事のパート2です。


【パート1】開発環境の準備
【パート2】Embulkコンテナの作成・単体テスト(★本記事)
【パート3】ECSのタスク定義と動作確認
【パート4】Step Functionsで簡単に実行できるように設定する


Cloud9とRDSのセットアップとデータの登録などの下準備が完了した状態です。このパートではEmbulkのコンテナのビルド、ETLジョブの開発と単体テスト及びデプロイまでを行います。


目次

2-1) CodeCommitの作成とクローン

CodeCommitのリポジトリを作成します。マネジメントコンソールを開いてリポジトリを作成しましょう。
リポジトリ:datafound-embulk(好きな名前でいいです。)


出来上がったらCloud9側でクローンをしましょう(マネジメントコンソール上に表示されているコマンドを控えておくと楽です)。チュートリアルなので適当に設定していいです。
Cloud9のターミナルで以下のコマンドを実行します。(※<>は読み替えてください)

# git configで設定をする
git --version
git config --global user.name "<あなたの名前>"
git config --global user.email <あなたの名前>@example.com

# AWS credentialのヘルパーを有効化する
git config --global credential.helper '!aws codecommit credential-helper $@'
git config --global credential.UseHttpPath true

# クローンする(コンソールからコピーしたコマンドに読み替えます)
git clone https://git-codecommit.ap-northeast-1.amazonaws.com/v1/repos/<リポジトリ名>

クローンされたディレクトリが生成されていることを確認します。


2-2) 設定ファイルやスクリプトをデプロイするS3バケットの作成

合わせて、このタイミングでS3バケットを好きな名前で作成しておいてください。名前以外はデフォルトの設定で大丈夫です。


2-3) Embulkコンテナのビルド

ETLジョブは比較的似たような処理を複数実行されることが多いです。そのため、今回は汎用的なコンテナイメージを作成します。処理毎に設定ファイルやスクリプトを個別に用意してコンテナイメージは使い回せるようにします。

f:id:jimatomo:20210920002619p:plain
今回のチュートリアルのコンテナ戦略

まずはDockerfileをコーディングしていきましょう。
★今回はヒアドキュメントで生成できるようにコマンド貼っておきます。手を動かして写経するのもありだと思います。(※<>は読み替えてください)

cd ~/environment/<git cloneしたディレクトリ>
mkdir -p docker/postgres-postgres
cd docker/postgres-postgres

# Dockerfileを作成
cat << 'EOF' > Dockerfile
FROM openjdk:8-slim

ENV LANG=C.UTF-8
ENV PATH_TO_EMBULK=/opt/embulk
ENV PATH=${PATH}:/opt/embulk

# Change timezone
RUN ln -sf /usr/share/zoneinfo/Asia/Tokyo /etc/localtime

RUN apt-get update && apt-get install -y curl unzip

# AWS CLI install
WORKDIR /tmp
RUN curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
RUN unzip awscliv2.zip
RUN ./aws/install

# Embulk install
RUN mkdir -p ${PATH_TO_EMBULK}
RUN curl --create-dirs -o ${PATH_TO_EMBULK}/embulk -L "https://dl.embulk.org/embulk-0.9.23.jar"
RUN chmod +x ${PATH_TO_EMBULK}/embulk

# install Embulk Plugin
RUN embulk gem install embulk-input-postgresql
RUN embulk gem install embulk-output-postgresql

WORKDIR /app

CMD [ "bash" ]

EOF


次にdocker buildコマンドでビルドします。

# Build
docker build -t embulk-0.9.23-postgres-postgres .

出来上がったイメージを確認します。

# 確認
docker image ls


このコンテナを使用した単体テストは設定ファイルやスクリプトを作成した後でやります。ここではまず先にレジストリへpushしておきます。

2-4) ビルドしたコンテナイメージをECRに保存

今回はECRをコンテナレジストリとして利用します。


まずはECRのレジストリを作成しましょう。
ESR(ECS、EKSと同じページ)のマネジメントコンソールを開きます。


リポジトリを作成」を開きます。項目を入力したら作成しましょう。
可視性設定:プライベート
リポジトリ:(好きなものでOK)
※その他は変えても変えなくてもいいです。


すぐに出来上がるのでリポジトリを開きます。
右上に「プッシュコマンドの表示」をクリックします。コンテナイメージをプッシュするまでに必要な手順をコンソールは教えてくれます。案内に従って進めましょう。(※<>は読み替えてください)


1. 認証トークンを取得し、レジストリに対して Docker クライアントを認証します。
Cloud9のターミナルで以下のコマンドを実行します。(※<>は読み替えてください)

aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin <アカウントID>.dkr.ecr.ap-northeast-1.amazonaws.com


2. (Dockerイメージの構築はスキップします。)


3. 構築が完了したら、このリポジトリにイメージをプッシュできるように、イメージにタグを付けます。(※<>は読み替えてください)

docker tag embulk-0.9.23-postgres-postgres:latest <アカウントID>.dkr.ecr.ap-northeast-1.amazonaws.com/datafound-embulk:pg-pg

# 確認
docker image ls


4. コンテナイメージをプッシュする(※<>は読み替えてください)

docker push <アカウントID>.dkr.ecr.ap-northeast-1.amazonaws.com/datafound-embulk:pg-pg


無事イメージが登録できました。続いてこのコンテナイメージを使ってETLジョブの開発と単体テストを実施しましょう!

2-5) Embulkの設定ファイルとスクリプトのコーディング

EmbulkはYAML形式でETLジョブの設定を行うことができます。プラグインを開発しているGitHubのReadmeを見れば各種パラメータの意味が分かります。


今回利用するプラグインGitHubのリンクを貼っておきます。
・embulk-input-postgresql
https://github.com/embulk/embulk-input-jdbc/tree/master/embulk-input-postgresql

・embulk-output-postgresql
https://github.com/embulk/embulk-output-jdbc/tree/master/embulk-output-postgresql



また、Embulkはliquidテンプレートを利用できるので、認証情報などは環境変数に書いておいたり、共通の設定を外だししたりすることが可能です。

今回はSSMのパラメータストアに共通の設定や認証情報を保存して、スクリプトファイルでパラメータを取得して環境変数に取り込むようにします。


それでは、設定ファイルを書いていきましょう。(※<>は読み替えてください)

cd ~/environment/<git cloneしたディレクトリ>
mkdir -p job/001-test_tbl
cd job/001-test_tbl

cat << 'EOF' > config.yml.liquid
in:
  type: postgresql
  host: {{ env.SOURCE_HOST }}
  port: {{ env.SOURCE_PORT }}
  user: {{ env.SOURCE_USER }}
  password: {{ env.SOURCE_PASSWORD }}
  database: {{ env.SOURCE_DATABASE }}
  schema: {{ env.SOURCE_SCHEMA }}
  table: test_tbl
  incremental: true
  incremental_columns: 
    - updated_at
out:
  type: postgresql
  host: {{ env.TARGET_HOST }}
  port: {{ env.TARGET_PORT }}
  user: {{ env.TARGET_USER }}
  password: {{ env.TARGET_PASSWORD }}
  database: {{ env.TARGET_DATABASE }}
  schema: {{ env.TARGET_SCHEMA }}
  table: test_target_tbl
  mode: insert
EOF


続いてスクリプトファイルです。スクリプトファイルはほとんど環境変数を設定しているだけです。別ファイルにしてもいいくらいです。
は先ほど作成したものを指定します。最後の方に2か所あります。

cat << 'EOF' > run.sh
#!/bin/bash
set -e

# input関連の環境変数
export SOURCE_HOST=`aws ssm get-parameter --name "/embulk/source/postgresql/host" --query "Parameter.Value" --output text`
export SOURCE_PORT=`aws ssm get-parameter --name "/embulk/source/postgresql/port" --query "Parameter.Value" --output text`
export SOURCE_USER=poc_user
export SOURCE_PASSWORD=`aws ssm get-parameter --name "/embulk/source/postgresql/poc_user" --with-decryption --query "Parameter.Value" --output text`
export SOURCE_DATABASE=source
export SOURCE_SCHEMA=public

# output関連の環境変数
export TARGET_HOST=`aws ssm get-parameter --name "/embulk/target/postgresql/host" --query "Parameter.Value" --output text`
export TARGET_PORT=`aws ssm get-parameter --name "/embulk/source/postgresql/port" --query "Parameter.Value" --output text`
export TARGET_USER=poc_user
export TARGET_PASSWORD=`aws ssm get-parameter --name "/embulk/target/postgresql/poc_user" --with-decryption --query "Parameter.Value" --output text`
export TARGET_DATABASE=target
export TARGET_SCHEMA=public

# ジョブ固有の環境変数
export JOB_NAME=001-test_tbl

# logディレクトの作成
mkdir ./log

# ジョブの実行
embulk run -c config.diff.yml config.yml.liquid > ./log/embulk-result-${JOB_NAME}-`date +%Y%m%d-%H%M%S`.log

aws s3 cp ./config.diff.yml s3://<バケット名>/job/${JOB_NAME}/config.diff.yml
aws s3 cp ./log s3://<バケット名>/log/${JOB_NAME}/`date +%Y%m%d` --recursive

exit 0
EOF

「set -e」があることによって、エラー時の後続の処理を停止することができます。


embulkコマンドのオプションで、差分更新を可能にするためにdiffファイルを指定している点に注目してください。

このdiffファイルをスクリプトや設定ファイルを保存しているS3に一緒に保存してあげて次回以降に一緒に取ってこれるようにしています。EC2などで実行する分にはわざわざS3に毎回putしておく必要はありませんが、毎回コンテナが破棄されるサーバレスでやる上で必要不可欠な仕組みです。

ちなみに、FargateでもEFSをサポートしているので、単純にEFSをマウントさせるという技も使えます。今回はCodeDeployで連携がしやすいという点で利点があるのでS3を使います。


2-6) Embulkのコンテナを利用したETLジョブの実行(単体テスト

それでは先ほどビルドしたコンテナでスクリプトと設定ファイルの単体テストを実施しましょう。

コンテナとのローカルファイルのやりとりをしますが、細かいところは以下のサイトなどをご確認ください。
https://blog.amedama.jp/entry/2018/01/30/221546


まずはコンテナを起動しておきます。

docker run -it embulk-0.9.23-postgres-postgres:latest


★別のターミナルを開きます。
実行中のコンテナを確認して先ほど書いたファイルをコピーします。

# スクリプトや設定ファイルがあるディレクトリまで移動
cd ~/evironment/<クローンしたディレクトリ>/job/001-test_tbl
ls -l

# 実行中のコンテナを確認
docker ps

# コンテナIDを置換してコピーを実施
docker cp . <コンテナID>:/app 


これまではcloud9の環境だったので、権限はコンソールにログインしているユーザーと同等で特に意識はそこまでしていませんでした。
ただしコンテナの中に入ると今までの環境か異なり、IAMの権限が必要になってきます。今回はIAMユーザーを作成してそのクレデンシャルを登録します。

以下のIAMポリシーを作成します。(ポリシー名は何でもいいです。)

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "s3:PutObject",
                "s3:GetObject",
                "s3:ListBucket"
            ],
            "Resource": [
                "arn:aws:s3:::<バケット名>",
                "arn:aws:s3:::<バケット名>/*"
            ]
        },
        {
            "Sid": "VisualEditor1",
            "Effect": "Allow",
            "Action": "ssm:GetParameter",
            "Resource": "arn:aws:ssm:ap-northeast-1:<アカウントID>:parameter/embulk/*"
        }
    ]
}


作成したIAMポリシーを付与したIAMユーザーを作成します。作成したらクレデンシャルの情報を控えておきましょう。(ユーザ名は何でもいいです。)


クレデンシャルをコンテナの環境変数にセットします。(aws configureで設定してもいいと思います)
★dockerのコンテナ内で実行します

export AWS_ACCESS_KEY_ID=<アクセスキー>
export AWS_SECRET_ACCESS_KEY=<シークレット>
export AWS_DEFAULT_REGION=ap-northeast-1


それでは満を持してスクリプトを実行していきましょう。

ls -l
chmod +x ./*
ls -l

./run.sh

エラーが出たら頑張って修正しましょう。だいたいタイポしていたり、構文エラーが原因のことが多いです。


ターゲットDBを確認し、データが転送されている事を確認します。
ターゲットDBに接続して以下のSQLを実行します。

SELECT * FROM test_target_tbl;

ソース側のテーブルにデータを追加してもう一度やってみてもいいですが、これ以降はconfig.diff.ymlをS3からとってくる必要が出てくるECSでテストすることとしましょう。


ちなみに、config.diff.ymlはこんな感じになっています。

in:
  last_record: ['2021-09-19T14:40:30.601724']
out: {}

このlast_recordが差分転送のために必要な情報です。

2-7) CodePipelineでS3にスクリプトと設定ファイルをデプロイ

無事スクリプトファイルや設定ファイルが問題なく動作することが分かったのでCodeCommitにpushしましょう。

cd ~/environment/<git cloneしたディレクトリ>
ls -l

# コミット対象を追加
git add .

# 確認
git status

# コミット(コメントは適宜変えてください)
git commit -m "add script and config in job/001-test_tbl"

# プッシュ
git push

※Dockerfile自体はスクリプト自体の実行には不要ですがセットでコミット対象としています。


続いて、マネジメントコンソールでCI/CDのパイプラインを作りましょう。

CodePipelineのマネジメントコンソールを開き、「パイプラインを作成する」をクリックします。


1. 【パイプラインの設定を選択する】
パイプライン名:(好きなものでOK)
ロールは新しく作るので大丈夫です。他の設定はいじらなくてもいいです。(意味が分かっていればいじってもOK)


2. 【ソースステージを追加する】
ソースプロバイダーAWS CodeCommit
リポジトリ:datafound-embulk(先ほど作成したリポジトリ名)
ブランチ:master
※その他はデフォルトで大丈夫です。


3. 【ビルドステージを追加する】
ビルドは不要なのでスキップします。「ビルドステージをスキップ」をクリックします。(確認ウィンドウが出るのでそちらもスキップをクリック)


4. 【デプロイステージを追加する】
デプロイプロバイダーAmazon S3
リージョン:アジアパシフィック(東京)
バケット:(先ほど作成したS3バケットを選択)
デプロイする前にファイルを抽出する:チェックする!
※S3 オブジェクトキー(デプロイパス)は入力しない
※追加設定はいじらなくていい


後はレビューして作成しましょう。


作成が完了すると、すぐに先ほどコミットしたイベントをベースにパイプラインが動いていきます。S3にファイルがデプロイされていることを確認してください。



以上でこのパートは終わりです。


このパートでは、ECSでタスクを実行するためにコンテナのビルドからコーディングと単体テスト、デプロイまで実施しました。


次のパートに続きます。


【パート3】ECSのタスク定義と動作確認