30歳からのプログラミング

30歳無職から独学でプログラミングを開始した人間の記録。

Embulk に入門して Amazon RDS にあるデータを BigQuery に転送する

この記事では、Embulk を使ってデータ転送を行う方法について述べていく。
今回は題材として Amazon RDS から Google Cloud の BigQuery にデータを転送する。Embulk の実行はローカルマシンで行う。

使っている Embulk のバージョンは0.9.25
0.100.11だと異なる手順や設定が必要になると思われるので注意。

事前準備

まずは Amazon RDS のインスタンスを作成する。今回はデータベースの種類は MySQL にした。バージョンは8.0.35
ローカルマシンから Embulk を実行する都合上、パブリックアクセスを「あり」にしておく必要がある。そして、セキュリティグループを適切に設定し、ローカルマシンの IP アドレスからのアクセスを許可しておく必要もある。
作成後は$ docker run -it --rm mysql mysql -u admin -p -h <RDSインスタンスのエンドポイント>でインスタンスにログインし、以下のクエリを実行する。

CREATE DATABASE source_db;
USE source_db;
CREATE TABLE user (
  id INT NOT NULL AUTO_INCREMENT,
  name VARCHAR(191) NOT NULL,
  PRIMARY KEY (id)
);
INSERT INTO user (name) VALUES ('Alice'), ('Bob');

source_dbというデータベースを作り、そのなかにuserテーブルを作成、以下のデータを投入した。

mysql> SELECT * FROM user;
+----+-------+
| id | name  |
+----+-------+
|  1 | Alice |
|  2 | Bob   |
+----+-------+
2 rows in set (0.01 sec)

このデータを BigQuery に転送するのが今回の目的。

次に BigQuery 側の準備を行う。
任意の GCP プロジェクトの BigQuery にdestination_datasetというデータセットを用意しておく。テーブルは作らなくてよい。
RDS だけでなく BigQuery にアクセスする権限も必要なので、サービスアカウントキーを JSON 形式で発行しておく。

ローカルマシンで Embulk を実行できるようにする

今回は Docker を使って Embulk の実行環境を用意する。
Dockerfileinit_embulk_commands.sh、2 つのファイルを用意する。

まずはDockerfile

FROM openjdk:8-jdk

# embulk.jarをダウンロード
RUN mkdir -p /root/.embulk/bin && \
    curl -o /root/.embulk/bin/embulk.jar -L "https://dl.embulk.org/embulk-0.9.25.jar"

# 必要なgemをインストールするためのスクリプトを追加
COPY init_embulk_commands.sh /root/init_embulk_commands.sh
RUN chmod +x /root/init_embulk_commands.sh && /root/init_embulk_commands.sh

WORKDIR /workspace

# embulk実行のためのシェルコマンドを設定
RUN echo '#!/bin/bash\njava -classpath "/root/.embulk/bin/embulk.jar:/root/.embulk/lib/postgresql.jar" org.embulk.cli.Main "$@"' > /usr/local/bin/embulk && \
    chmod +x /usr/local/bin/embulk

CMD [ "embulk", "--version" ]

このなかでinit_embulk_commands.shをコピーしそれを実行している。
Dockerfileのコメントに書いたようにこれは、必要な gem をインストールするためのシェルスクリプト。
まずは以下の内容にする。

#!/bin/bash

# embulk.jarへのパス
EMBULK_JAR="/root/.embulk/bin/embulk.jar"

# 必要なgemをインストール
java -jar $EMBULK_JAR gem install embulk -v 0.9.25

この状態で$ docker build -t embulk-image .を実行して Docker image を作成する。

そしてその image から Docker container を作成・実行して Embulk のバージョンが表示されれば、Embulk の実行は成功。

$ docker run --rm  embulk-image
embulk 0.9.25

転送設定を記述し実行する

Embulk を動かすことに成功したので次は、転送元と転送先を設定し、実際に転送を行うようにする。

Embulk は多種多様なデータソースを対象としており、今回対象とした MySQL と BigQuery 以外にも、 Redshift や Snowflake なども対象にすることができる。
これを可能にしているのがプラグインシステムであり、対応するプラグインさえあれば、転送元や転送先を自由に設定できる。

今回の例では MySQL を転送元、BigQuery を転送先にするので、embulk-input-mysqlembulk-output-bigqueryというプラグインが必要になる。

init_embulk_commands.shを以下のように書き換えて、Docker image の作成時に必要なプラグインがインストールされるようにする。

#!/bin/bash

# embulk.jarへのパス
EMBULK_JAR="/root/.embulk/bin/embulk.jar"

# 必要なgemをインストール
java -jar $EMBULK_JAR gem install embulk -v 0.9.25
java -jar $EMBULK_JAR gem install embulk-input-mysql
java -jar $EMBULK_JAR gem install jwt -v 2.3.0
java -jar $EMBULK_JAR gem install multipart-post -v 2.1.1
java -jar $EMBULK_JAR gem install public_suffix -v 4.0.7
java -jar $EMBULK_JAR gem install mini_mime -v 1.0.2
java -jar $EMBULK_JAR gem install representable -v 3.0.4
java -jar $EMBULK_JAR gem install embulk-output-bigquery -v 0.6.4

使いたいプラグイン以外にも様々な gem をインストールしているが、それらは、embulk-output-bigqueryの依存関係を解決するために必要な gem。
必要な gem とそのバージョンは以下の記事を参考にした。
embulk-input-bigqueryのインストールでエラー - kikukawa's diary

そしてDockerfileCMD [ "embulk", "--version" ]CMD ["embulk", "run", "config.yml"]に書き換える。

config.ymlは Embulk の転送設定を記述するファイルであり、どのデータをどこに転送するのか記述していく。

今回は以下の内容のembulk/config.ymlを用意する。

in:
  type: mysql
  host: RDSインスタンスのエンドポイント
  port: 3306
  user: admin
  password: "rds password"
  database: source_db
  table: user
  select: "*"

out:
  type: bigquery
  mode: replace
  auth_method: json_key
  json_keyfile: "key.json"
  project_id: BigQueryのプロジェクト名
  dataset: destination_dataset
  table: user
  auto_create_table: true

inが転送元でoutが転送先。

out.json_keyfileにはサービスアカウントキーのパスを記述する。今回はkey.jsonにしたので、config.ymlと同じディレクトリ、つまりembulkディレクトリにkey.jsonとして置いておく。

つまり以下の構成になる。

.
├── Dockerfile
├── embulk
│   ├── config.yml
│   └── key.json
└── init_embulk_commands.sh

この状態で再び image を作り、そこから container を作成し実行する。container がconfig.ymlkey.jsonを利用できるように、-vオプションで Volume を作っている。

$ docker build -t embulk-image .
$ docker run --rm -v "$(pwd)/embulk:/workspace" embulk-image

そうするとdestination_datasetuserテーブルが作られ、RDS に入れておいたのと同じデータが入っている。

Terraform で GitHub の Branch protection rule を定義する

Terraform では GitHub の Branch protection rule を管理する resource が提供されている。この記事ではそれを使って rule を定義する方法について扱う。
動作確認は Terraform のv1.7.4で行った。

Branch protection rule は、指定したブランチに対してルールを設定する仕組みで、pull request に対してレビューを必須にする、特定のテストをパスしないと merge できないようにする、などのルールを設定することができる。

GitHub の UI から設定可能だが、Terraform で管理していくこともできる。
Terraform には GitHub Provider が提供されており、その中には Branch protection rule を扱う resource もある。そのためそれを使えば、Terraform で Branch protection rule を管理することができる。

registry.terraform.io

今回はtf-testというリポジトリを作り、そのmainブランチに対して「Status Check にパスしないと merge できない」という rule を作成することにする。

Branch protection rule はリポジトリのページの Settings から見れるが、最初は何の rule も存在しない。

まずはリポジトリに.github/workflows/ci.yamlというファイルを追加し、そこに Status Check として行いたい処理(自動テストや型チェックなど)を書いておく。
これで、記述したトリガー条件に基づいてワークフローが実行されるようになるが、この時点ではまだ Status Check として設定されていない。そのため、このワークフローが失敗しても pull request の merge はできてしまう。

今回は Terraform Cloud で Terraform の state 管理を行うので、Terraform Cloud の設定を行う。
github-workspaceという workspace を作り、その workspace にTF_VAR_GITHUB_TOKENという Environment Variable を設定する。TF_VAR_GITHUB_TOKENに必要な権限を持ったトークンを設定することで、Terraform から GitHub を操作できるようになる。

あとは、以下のような Terraform ファイルを書けばよい。

terraform {
  cloud {
    organization = "your-org-name"
    workspaces {
      name = "github-workspace"
    }
  }
  required_providers {
    github = {
      source  = "integrations/github"
      version = "~> 5.0"
    }
  }
}

variable "GITHUB_TOKEN" {
  type        = string
  sensitive   = true
}

provider "github" {
  owner =  "your github account name"
  token = var.GITHUB_TOKEN
}

resource "github_branch_protection" "example" {
  repository_id = "tf-test"
  pattern       = "main"

  enforce_admins = true
  required_status_checks {
    strict   = true
    contexts = ["ci"]
  }
}

GitHub の Organization にリポジトリがある場合も、provider "github"organizationではなくownerを書く。
今回は自分が作った Organization にtf-testを作ったのだが、上記の記述で問題なく動作した。
organizationという引数もあるのだが、2024/03/03 現在非推奨になっている。
https://registry.terraform.io/providers/integrations/github/latest/docs#organization

この状態で$ terraform init$ terraform applyを実行すると rule が作られる。

リポジトリのページで確認してみると、mainブランチを対象に以下が有効になっていることが分かる。

  • Require status checks to pass before merging
  • Require branches to be up to date before merging
    • ciという status check が必須になる
  • Do not allow bypassing the above settings