Amazon MSKのKafkaクラスターにCDCでデータベースの変更差分を送る（Json／Avro）

# はじめに

[Amazon Managed Streaming for Apache Kafka（以下、Amazon MSK）](https://aws.amazon.com/jp/msk/)のKafkaクラスターに対して[IBM InfoSphere Data Replication - CDC Replication（以下、IIDR CDC）](https://www.ibm.com/docs/ja/idr/11.4.0?topic=change-data-capture-cdc-replication)を使用してDb2のデータをKafkaに送る方法を確認します。
IIDR CDCのことはよく触っているので知っていることもありますが、IIDR CDCからAmazon MSKとAWS Glue Schema Registryに対するメッセージングの方法は知らなかったのでトライしたものとなります。
特に、IIDR CDC → Amazon MSK／AWS Glue Schema Registryを起点にイベントドリブンのアーキテクチャーを検討している方に参考になる内容だと思います。

なお、試行にあたり何度もトライ＆エラーしていますが、ここでは、成功した際の設定と手順と注意点（ハマりポイント）について説明します。


# 前提

- Apache Kafkaのバージョン：3.5.1
- Zookeeper構成（KRaft構成ではない）
- トライした時期：2024/9/13-17
- AWS環境は個人利用のもの（自費）
- Amazon MSKクラスターはパブリックアクセス可能（構築方法は後述の「実施した概要」①参照）
- Db2のバージョン:DB2 v11.1.4.5
- IIDR CDCのバージョン
  - IBM InfoSphere Data Replication (IBM DB2)：11.4.0.4（ビルド master_5660）
  - IBM InfoSphere Data Replication (Kafka)：11.4.0.4（ビルド master_5660）
- IIDR CDCのインストールディレクトリ：/opt/ibm

# 実施した概要

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/838636/2ff2c3bb-380f-9773-9f4c-d47eb32f222b.png)

- ① Amazon MSKのクラスターを構築する（→[Amazon MSKでオンプレから接続可能なKafkaクラスターの構築方法（できるだけ普通のKafkaっぽく使えるように）](https://qiita.com/ysskjck/items/40478b77a9af46f7779c)）
- ② AWS外からJavaプログラムを使用して以下を実施（→[Amazon MSKのKafkaクラスターにJavaプログラムでProduce／Consume（String形式＆Avro形式）](https://qiita.com/ysskjck/items/6da09ddb093f228d1b60)）
  - シンプルな文字列形式のメッセージのProduce/Consume
  - AWS Glue Schema Registryの設定
  - Avro形式のメッセージのProduce/Consume
- ③ AWS外からIBM Infosphere Replication CDCを使用して以下を実施（**本稿で紹介**）
  - JSON形式でキャプチャー対象テーブルのデータをAmazon MSKに送信
  - Avro形式でキャプチャー対象テーブルのデータをAmazon MSKに送信

# IIDR CDCの設定ファイルについて

IIDR CDCはKafka Clientライブラリを使用してメッセージのProduce/Consumeを行います。
Kafka producer/Kafka consumerとしての振る舞いを設定するプロパティを決められたファイル内に記述するものとなっています。
また、メッセージにヘッダーをつけてトランザクションIDを付与したり、メッセージ全体の形式を制御したりすることができる[Kafkaカスタム操作プロセッサー (KCOP)](https://www.ibm.com/docs/ja/idr/11.4.0?topic=crek-kafka-custom-operation-processor-kcop-cdc-replication-engine-kafka) の設定を記述する設定ファイルがあります。
これらはIIDR CDCのターゲットとなるKafka Brokerやスキーマレジストリの接続先や認証情報を記載する重要なファイルになっています。

今回の手順で登場する設定ファイルは以下の通りです。

| ファイル名 | 配置場所 | 役割 |
| --- | --- | --- |
| kafkaproducer.properties | _CDC_Kafka_instance_directory_ /conf/ | Kafka producerとしての設定内容
| kafkaconsumer.properties | 同上 | Kafka consumerとしての設定内容
| kcop.properties（任意） | 同上（任意） | KCOPの設定内容
| user-classloader.cp | 同上 | KCOPで使用するクラスパスを定義

_CDC_Kafka_instance_directory_ はインストール先およびインスタンス名により可変です。
今回は以下を使用します。
```sh:本手順上の _CDC_Kafka_instance_directory_ /conf/
/opt/ibm/InfoSphereDataReplication/ReplicationEngineforKafka/instance/KAFKAF/conf
```

kafkaproducer.propertiesとkafkaconsumer.propertiesはファイル名も配置場所も固定です。
KCOPの設定ファイルはファイル名も配置場所も任意で手順の中で指定するものとなりますが、便宜的にファイル名はkcop.propertiesとし配置場所はkafkaproducer.propertiesとkafkaconsumer.propertiesと同じにします。
user-classloader.cpはIIDR CDCからAvro形式でメッセージングする際に設定が必要です。

# 実施手順の流れ
実施手順の基本的な流れは以下の通りです。

(1) kafkaproducer.properties/kafkaconsumer.propertiesの設定
(2) kcop.propertiesの設定
(3) サブスクリプション設定
(4) リフレッシュ（ソーステーブルのデータの一括転送）
(5) ミラーリング（変更データのキャプチャー開始）

(4)、(5)はリフレッシュ後に継続的にミラーリングがなされるように実行することができます。
本稿では、リフレッシュが行えればデータの疎通は確認できたことになるためミラーリングの実行については含んでいません。
また、IIDR CDCの導入に関する手順も含んでいません。

# Amazon MSK側で事前に実施しておいたほうがいいかもしれない設定
IIDR CDCによりKafkaにメッセージを送る際、デフォルトまたは指定された名称のKafkaトピックが利用されることになります。
Kafka Brokerでは、Producerがメッセージを書き込もうとしたトピックがない場合にトピックを自動作成するかエラーとするかを制御するプロパティがあります。
今回のように、特にトピック名へのこだわりなく試行したいような場合は、トピックの自動作成を有効にしておくのが良いでしょう。

- クラスターの構成変更：auto.create.topics.enable=**true**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/838636/5310cfaa-edc3-e858-635d-d9be292d65d4.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/838636/4ab14d4f-6d35-bd84-7c49-656ba99f824a.png)

- - -
# Json形式のメッセージング（AWS Glue Schema Registryを使用しない）

#### (1) kafkaproducer.properties/kafkaconsumer.propertiesの設定
```sh
retries=3
retry.backoff.ms=500
batch.size=65536
bootstrap.servers=b-2-public.mymskcluster.zxrapj.c4.kafka.ap-northeast-1.amazonaws.com:9196,b-3-public.mymskcluster.zxrapj.c4.kafka.ap-northeast-1.amazonaws.com:9196,b-1-public.mymskcluster.zxrapj.c4.kafka.ap-northeast-1.amazonaws.com:9196
security.protocol=SASL_SSL
sasl.mechanism=SCRAM-SHA-512
sasl.jaas.config=org.apache.kafka.common.security.scram.ScramLoginModule required username="my-user" password="my-password";
ssl.truststore.location=/tmp/kafka.client.truststore.jks
ssl.truststore.password=changeit
```
:::note warn
最後の2行の ssl.truststore.location と ssl.truststore.password については、トラストストアファイルの作成方法を含めて後述します。
ここは**ハマりポイント**です。
:::

#### (2) kcop.propertiesの設定
```sh
header.jcfs=ENTTYP,TIMSTAMP,CCID
```
上記はメッセージのヘッダーにCCIDなどの情報を付与する設定です。
この設定がなくても、kcop.properties自体がなくても今回の手順は上手くいきます。
（つまり、この設定は必須ではありません）

#### (3) サブスクリプション設定

###### サブスクリプションの作成（以下は作成後）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/838636/2cba0ca4-72de-3206-98fe-c6f7fac9d3fc.png)

###### Kafka Propertiesの設定
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/838636/15807372-0953-4a85-83cb-105fb53df7a6.png)

:::note info
Zookeeperの「Host Name」と「Port」は必須入力欄となっています。
ただし、ここで指定する内容は便宜的なもので **内容はなんでもいいみたいです**。
:::

###### User Exitsの設定
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/838636/f4b24c89-de15-5408-9931-37388d373d01.png)
```sh
# Class Name
com.datamirror.ts.target.publication.userexit.sample.kafka.KcopJsonFormatIntegrated
# Parameter（Kcop.propertiesファイルを指定）　※先頭ハイフンも必要
-file:/opt/ibm/InfoSphereDataReplication/ReplicationEngineforKafka/instance/KAFKAF/conf/kcop.properties
```
（上記のParameterの入力は今回は必須ではありません）

#### (4) リフレッシュ（ソーステーブルのデータの一括転送）
###### Flag for Refresh
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/838636/e6bc929c-6b0b-c96b-a94b-9d0d9ec0f773.png)

###### Start Refresh
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/838636/ff5aff05-1f4d-c41f-67cd-03fa1627620f.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/838636/e1873d94-f534-21ce-7c2e-bf9fe7d79c1b.png)

画面下部にあるEvents欄で「45 rows were sent.」というメッセージを確認できます。
リフレッシュ成功。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/838636/c2f7580d-6601-d44b-26fb-174bc1d0307c.png)

###### ソースのDB内容
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/838636/338e869d-f808-c772-39eb-cb66b5e42d48.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/838636/ea764473-fa08-aa35-5680-94a578c8db5f.png)

###### Amazon MSKのトピック一覧をKafkaのユーティリティで確認

```sh:Kafkaのユーティリティを使用する際のプロパティファイルの内容
# /mskconfig-ssk.properties
security.protocol=SASL_SSL
sasl.mechanism=SCRAM-SHA-512
sasl.jaas.config=org.apache.kafka.common.security.scram.ScramLoginModule required username="my-user" password="my-password";
ssl.truststore.location=/tmp/kafka.client.truststore.jks
ssl.truststore.password=changeit
```

```sh:トピックの一覧表示
./kafka-topics.sh  --bootstrap-server b-2-public.mymskcluster.zxrapj.c4.kafka.ap-northeast-1.amazonaws.com:9196,b-3-public.mymskcluster.zxrapj.c4.kafka.ap-northeast-1.amazonaws.com:9196,b-1-public.mymskcluster.zxrapj.c4.kafka.ap-northeast-1.amazonaws.com:9196 --command-config /mskconfig-ssk.properties --list
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/838636/508cc40f-f90f-d790-2544-355ed0b7b004.png)
kafkaf.mskjson.sourcedb.cdcadmin.test1-jsonがIIDR CDCでのリフレッシュによって作成されたトピックです。

```sh:トピックの中身のメッセージを表示
./kafka-console-consumer.sh --bootstrap-server b-2-public.mymskcluster.zxrapj.c4.kafka.ap-northeast-1.amazonaws.com:9196,b-3-public.mymskcluster.zxrapj.c4.kafka.ap-northeast-1.amazonaws.com:9196,b-1-public.mymskcluster.zxrapj.c4.kafka.ap-northeast-1.amazonaws.com:9196 --topic kafkaf.mskjson.sourcedb.cdcadmin.test1-json --consumer.config /mskconfig-ssk.properties --from-beginning
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/838636/b8ad3dce-caa4-6468-990c-15181096a55b.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/838636/a3b5c65e-159a-c3e7-a5a2-f6da9897cc3b.png)

# ハマりポイント：トラストストアファイルの作成
今回IIDR CDCの接続先となるAmazon MSKのKafkaクラスターは認証方式として「SASL/SCRAM認証」を採用しています。この認証方式を採用している場合の諸々のやり方は下記の公式ドキュメントに記載があります。

参考：[Sign-in credentials authentication with AWS Secrets Manager](https://docs.aws.amazon.com/msk/latest/developerguide/msk-password.html)

このドキュメント内に以下のような記述があります。
> ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/838636/7218fb44-f135-1d1f-c3f6-a5f2d617d367.png)


Amazon MSKの認証方式には相互TLS（=mTLS）もありますが、それを採用しなくてもTLS暗号化をする以上はTLS認証の作法に準拠する必要があります。
つまり、クライアントはサーバーが提示する証明書が信頼できる認証局（CA）によって発行されたかどうかを確認できる必要があります。この確認には、クライアントが持つトラストストアが使われます。

従って上記の公式ドキュメントには以下のような手順が記載されているものと理解できます。
> ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/838636/be932fa9-8219-afab-c539-faa0bf976742.png)

当初、この手順に従っていましたが、うまいきませんでした。
トライ＆エラーの結果確認した失敗の原因は「**IIDR CDCを導入したサーバーのJava（IIDR CDCの要件でJavaバージョンはJava8）が信頼している認証局（CA）の中に、Amazon MSK側の証明書のCAがなかったから**」です。

確認したところ、Amazon MSKの証明書のCAは「Amazon Root CA」です。
少し新しめのJavaが導入されている環境のCAの中にはAmazon Root CAがありますが、IIDR CDCが搭載されているJava8の環境にはAmazon Root CAはありませんでした。

```sh:確認コマンド
keytool -list -keystore /tmp/kafka.client.truststore.jks | grep -i amazon
```
Java11環境での確認結果
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/838636/77cbd904-1951-f61a-2e63-f2997a481eb6.png)
（Amazon Root CAがある）

Java8環境（IIDR CDCが導入されている環境）での確認結果
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/838636/f0f1eccb-e920-b91a-536f-d3e90f784125.png)
（Amazon Root CAがない）

##### ハマりポイントへの対応
- [Amazon Root CAのダウンロード](https://www.amazontrust.com/repository/)
```sh
wget https://www.amazontrust.com/repository/AmazonRootCA1.cer
wget https://www.amazontrust.com/repository/AmazonRootCA2.cer
wget https://www.amazontrust.com/repository/AmazonRootCA3.cer
wget https://www.amazontrust.com/repository/AmazonRootCA4.cer
```
- Amazon Root CAのインポート
```sh
keytool -import -alias amazonrootca1 -keystore /tmp/kafka.client.truststore.jks -file /home/cdcadmin/kafka/AmazonRootCA1.cer
keytool -import -alias amazonrootca2 -keystore /tmp/kafka.client.truststore.jks -file /home/cdcadmin/kafka/AmazonRootCA2.cer
keytool -import -alias amazonrootca3 -keystore /tmp/kafka.client.truststore.jks -file /home/cdcadmin/kafka/AmazonRootCA3.cer
keytool -import -alias amazonrootca4 -keystore /tmp/kafka.client.truststore.jks -file /home/cdcadmin/kafka/AmazonRootCA4.cer
keytool -import -alias amazonrootca4 -keystore /tmp/kafka.client.truststore.jks -file /home/cdcadmin/kafka/AmazonRootCA4.cer
keytool -import -alias starfieldservicesrootcertificateauthority-g2 -keystore /tmp/kafka.client.truststore.jks -file /home/cdcadmin/kafka/SFSRootCAG2.cer
```
インポート時のコマンド実行結果（AmzaonRootCA4のインポート実行結果のみ抜粋）は以下の通り。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/838636/ac8e6eb7-b5fd-9b28-fbf6-c7a56305b2c7.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/838636/3a978b1d-9409-8451-88ef-e0d07796cb73.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/838636/dbeebd97-a3e8-1d67-d234-32ed8e8cd833.png)



上記の手順を実行した上で、kafkaproducer.propertiesとkafkaconsumer.propertiesに以下の記述を行う。
（ssl.truststore.passwordのchangeitはデフォルト設定。）
```log
ssl.truststore.location=/tmp/kafka.client.truststore.jks
ssl.truststore.password=changeit
```
- - -

# Avro形式のメッセージング（AWS Glue Schema Registryを使用する）

ここまでIIDR CDCからJson形式でAmazon MSKのKafkaクラスターにメッセージングする方法やハマりポイントを解説しましたが、ここからが本題です。
Json形式ではデータ型の表現が限定され、データベースのカラム毎のデータ型を厳密に伝搬することができないため、データソースとなるデータベースのデータを型を含めてデータターゲットに送りたい場合はAvro形式でのメッセージングを検討します。
以下は、IIDR CDCからのAvro形式でのメッセージング方法です。

# 実施手順の流れ
実施手順の基本的な流れは以下の通りです。

(1) AWS Glue Schema Registry Library Serializer / Deserializerの準備
(2) kafkaproducer.properties/kafkaconsumer.propertiesの設定
(3) kcop.propertiesの設定
(4) AWS Glue Schema Registryのアクセス権をもつIAMロールの使用
(5) サブスクリプション設定
(6) リフレッシュ（ソーステーブルのデータの一括転送）
(7) ミラーリング（変更データのキャプチャー開始）

(6)、(7)はリフレッシュ後に継続的にミラーリングがなされるように実行することができます。
本稿では、リフレッシュが行えればデータの疎通は確認できたことになるためミラーリングの実行については含んでいません。
また、IIDR CDCの導入に関する手順も含んでいません。

#### (1) AWS Glue Schema Registry Library Serializer / Deserializerの準備

###### AWS Glue Schema Registry Library Serializer / DeserializerのソースをGithubから持ってきてビルドする

参考：[AWS Glue Schema Registry Library Serializer / Deserializer（Github）](https://github.com/awslabs/aws-glue-schema-registry/blob/master/README.md#using-the-aws-glue-schema-registry-library-serializer--deserializer)
```sh
wget https://github.com/awslabs/aws-glue-schema-registry/archive/refs/heads/master.zip
unzip master.zip
cd build-tools/
mvn clean install -Dcheckstyle.skip
cd ..
mvn clean install -Dcheckstyle.skip -DskipTests
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/838636/0a9d4959-3bf5-87b9-0075-cc122bee294c.png)

###### 依存性のあるJarコピーする
```sh
# 依存性のあるJarをコピー
cd /work/aws-glue-schema-registry-master/serializer-deserializer
mvn dependency:copy-dependencies
# 依存性のあるJarを確認（あとでファイルに記述する必要があるため確認は手順上必要）
cd target
ls dependency
```
```log:依存性のあるJarの一覧
annotations-13.0.jar
annotations-2.22.12.jar
apache-client-2.22.12.jar
apiguardian-api-1.1.0.jar
arns-2.22.12.jar
asm-9.1.jar
auth-2.22.12.jar
auto-value-annotations-1.8.1.jar
avro-1.11.3.jar
aws-core-2.22.12.jar
aws-java-sdk-core-1.12.660.jar
aws-java-sdk-sts-1.12.660.jar
aws-json-protocol-2.22.12.jar
aws-query-protocol-2.22.12.jar
byte-buddy-1.10.5.jar
byte-buddy-agent-1.10.5.jar
checker-qual-3.13.0.jar
checksums-2.22.12.jar
checksums-spi-2.22.12.jar
classgraph-4.8.120.jar
commons-codec-1.15.jar
commons-collections4-4.4.jar
commons-digester-2.1.jar
commons-lang3-3.8.1.jar
commons-logging-1.1.3.jar
commons-validator-1.7.jar
endpoints-spi-2.22.12.jar
error_prone_annotations-2.7.1.jar
eventstream-1.0.1.jar
everit-json-schema-1.14.2.jar
failureaccess-1.0.1.jar
glue-2.22.12.jar
guava-32.0.0-jre.jar
hamcrest-core-1.3.jar
handy-uri-templates-2.1.8.jar
http-auth-2.22.12.jar
http-auth-aws-2.22.12.jar
http-auth-spi-2.22.12.jar
http-client-spi-2.22.12.jar
httpclient-4.5.13.jar
httpcore-4.4.13.jar
identity-spi-2.22.12.jar
j2objc-annotations-2.8.jar
jackson-annotations-2.12.2.jar
jackson-core-2.12.2.jar
jackson-databind-2.12.2.jar
jackson-dataformat-cbor-2.12.2.jar
javapoet-1.13.0.jar
jimfs-1.1.jar
jmespath-java-1.12.660.jar
joda-time-2.8.1.jar
json-20231013.jar
json-utils-2.22.12.jar
jsr305-3.0.2.jar
junit-4.13.1.jar
junit-jupiter-api-5.6.3.jar
junit-jupiter-engine-5.6.3.jar
junit-jupiter-params-5.6.3.jar
junit-platform-commons-1.6.2.jar
junit-platform-engine-1.6.3.jar
kafka-clients-3.6.1.jar
kotlin-reflect-1.7.10.jar
kotlin-script-runtime-1.7.10.jar
kotlin-scripting-common-1.7.10.jar
kotlin-scripting-compiler-embeddable-1.7.10.jar
kotlin-scripting-compiler-impl-embeddable-1.7.10.jar
kotlin-scripting-jvm-1.7.10.jar
kotlin-stdlib-1.7.10.jar
kotlin-stdlib-common-1.7.10.jar
kotlin-stdlib-jdk7-1.7.10.jar
kotlin-stdlib-jdk8-1.7.10.jar
kotlinpoet-1.10.2.jar
kotlinx-coroutines-core-jvm-1.5.2.jar
kotlinx-datetime-jvm-0.3.2.jar
kotlinx-serialization-core-jvm-1.4.0.jar
listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar
lombok-1.18.26.jar
lz4-java-1.8.0.jar
mbknor-jackson-jsonschema_2.12-1.0.39.jar
metrics-spi-2.22.12.jar
mockito-core-3.3.3.jar
mockito-junit-jupiter-3.3.3.jar
netty-buffer-4.1.100.Final.jar
netty-codec-4.1.100.Final.jar
netty-codec-http-4.1.100.Final.jar
netty-codec-http2-4.1.100.Final.jar
netty-common-4.1.100.Final.jar
netty-handler-4.1.100.Final.jar
netty-nio-client-2.22.12.jar
netty-resolver-4.1.100.Final.jar
netty-transport-4.1.100.Final.jar
netty-transport-classes-epoll-4.1.100.Final.jar
netty-transport-native-unix-common-4.1.100.Final.jar
objenesis-2.6.jar
okhttp-4.9.3.jar
okio-3.4.0.jar
okio-fakefilesystem-3.2.0.jar
okio-fakefilesystem-jvm-3.2.0.jar
okio-jvm-3.4.0.jar
opentest4j-1.2.0.jar
profiles-2.22.12.jar
proto-google-common-protos-2.7.4.jar
protobuf-java-3.19.6.jar
protocol-core-2.22.12.jar
re2j-1.6.jar
reactive-streams-1.0.4.jar
regions-2.22.12.jar
scala-library-2.12.10.jar
schema-registry-build-tools-1.1.20.jar
schema-registry-common-1.1.20.jar
sdk-core-2.22.12.jar
slf4j-api-1.7.30.jar
snappy-java-1.1.10.5.jar
sts-2.22.12.jar
swiftpoet-1.3.1.jar
third-party-jackson-core-2.22.12.jar
truth-1.1.3.jar
truth-liteproto-extension-1.1.3.jar
truth-proto-extension-1.1.3.jar
url-connection-client-2.22.12.jar
utils-2.22.12.jar
validation-api-2.0.1.Final.jar
wire-compiler-4.3.0.jar
wire-grpc-client-jvm-4.3.0.jar
wire-grpc-server-generator-4.3.0.jar
wire-java-generator-4.3.0.jar
wire-kotlin-generator-4.3.0.jar
wire-profiles-4.3.0.jar
wire-runtime-4.3.0.jar
wire-runtime-jvm-4.3.0.jar
wire-schema-4.3.0.jar
wire-schema-jvm-4.3.0.jar
wire-swift-generator-4.3.0.jar
zstd-jni-1.5.5-1.jar
```

###### user-classloader.cpの記述
user-classloader.cpはKCOPのためにロードするクラスを記述するファイルです。
参考：[Specifying a custom classpath for KCOPs](https://www.ibm.com/docs/en/idr/11.4.0?topic=kafka-specifying-custom-classpath-kcops)


前述の依存性のあるJarをIIDR CDCが参照できるディレクトリに配置した上で、user-classloader.cpにコロン（;）つなぎで全Jarファイルの絶対パスを記述します。

:::note info
試しましたがuser-classloader.cpを記述する際に、アスタリスク（*）を使用した記述は有効ではありませんでした。
:::

なお、前述の「依存性のあるJar」だけでは不足しておりCDCによるリフレッシュ実行時にこのようなエラーになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/838636/37b7faab-8478-5e89-6d0e-dae7cd728c4f.png)

よって、IIDR CDCのインストールディレクトリ直下にある下記2のJarファイルもuser-classloader.cpに追記します。

```log
/opt/ibm/InfoSphereDataReplication/ReplicationEngineforKafka/lib/jackson-mapper-asl-1.9.13.jar
/opt/ibm/InfoSphereDataReplication/ReplicationEngineforKafka/lib/jackson-core-asl-1.9.13.jar
```

また、後述の手順で確認する「com.amazonaws.services.schemaregistry.serializers.avro.CustomerProvidedSchemaNamingStrategy」はビルドした際のtarget/schema-registry-serde-1.1.20-tests.jarの中にいるクラスのため、schema-registry-serde-1.1.20-tests.jarもuser-classloader.cpへの記述が必要になる。

user-classloader.cpの記述を有効化させるためにインスタンスの再起動を行います。
```sh:CDCインスタンスの再起動
[CDC_Kafka_install_directory]/bin/dmshutdown -I [インスタンス名]
nohup [CDC_Kafka_install_directory]/bin/dmts64 -I [インスタンス名] &
```

#### (2) kafkaproducer.properties/kafkaconsumer.propertiesの設定
```sh:
retries=3
retry.backoff.ms=500
batch.size=65536
bootstrap.servers=b-2-public.mymskcluster.zxrapj.c4.kafka.ap-northeast-1.amazonaws.com:9196,b-3-public.mymskcluster.zxrapj.c4.kafka.ap-northeast-1.amazonaws.com:9196,b-1-public.mymskcluster.zxrapj.c4.kafka.ap-northeast-1.amazonaws.com:9196
security.protocol=SASL_SSL
sasl.mechanism=SCRAM-SHA-512
sasl.jaas.config=org.apache.kafka.common.security.scram.ScramLoginModule required username="my-user" password="my-password";
ssl.truststore.location=/tmp/kafka.client.truststore.jks
ssl.truststore.password=changeit
```
（Json形式のときと内容変わらず）

#### (3) kcop.propertiesの設定
```log
schema.registry.url=
serializer.class=com.amazonaws.services.schemaregistry.serializers.GlueSchemaRegistryKafkaSerializer
serializer.property.dataFormat=AVRO
serializer.property.region=ap-northeast-1
serializer.property.registry.named=my-registry
serializer.property.schemaAutoRegistrationEnabled=true
serializer.property.schemaNameGenerationClass=com.amazonaws.services.schemaregistry.serializers.avro.CustomerProvidedSchemaNamingStrategy
```
`schema.registry.url`はAvro形式のメッセージングを行う際には入力必須の項目となっています。今回はAWS Glue Schema Registryを使用するため、従来のURLでのAPIエンドポイント指定をする記述が不要です。入力必須でありながら指定が不要の場合には、上記のようにプロパティの「=」の後を未入力とすることができます。

`serializer.property.schemaAutoRegistrationEnabled`はAWS Glue Schema Registry内にないスキーマを登録する場合は、スキーマを自動作成するか、自動作成させずにエラーとするかの振る舞いを決めるプロパティです。試行時にはスキーマ名を厳密に考慮して動作確認をしないため、基本は`true`にすべきと考えられます。

:::note warn
serializer.classについて、似たようなクラス名のものがあるため注意が必要です。
　GlueSchemaRegistryKafkaSerializer.java　◯
　GlueSchemaRegistrySerializer.java　×
前者のクラス名をプロパティに記述するようにしてください。
:::

`serializer.property.schemaNameGenerationClass`はProducerがAWS Glue Schema Registryにスキーマを書き込む際に、キー部とバリュー部のそれぞれのスキーマをどのように命名するかを決定づけるプロパティです。CustomerProvidedSchemaNamingStrategyを指定することでキー部用とバリュー部用のスキーマを区別して設定できるようになります。

:::note warn
`serializer.property.schemaNameGenerationClass`を設定しない場合、キー部用のスキーマとバリュー部用のスキーマが同名で登録されることになりスキーマの互換性エラーになります。
:::


#### (4) AWS Glue Schema Registryのアクセス権をもつIAMロールの使用
```sh
# AWS Glue Schema Registryのアクセス権を持つIAMロールの認証情報を取得
aws sts assume-role --role-arn arn:aws:iam::XXXXXXXXXXXX:role/MyGlueSchemaRegistryAccessRole --role-session-name test-session --profile default --region ap-northeast-1 --output json

# 取得したIAMロールの認証情報をAWSの認証情報系の環境変数にセット
export AWS_ACCESS_KEY_ID="・・・"
export AWS_SECRET_ACCESS_KEY="・・・"
export AWS_SESSION_TOKEN="・・・"
```

#### (5) サブスクリプション設定
###### サブスクリプションの作成
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/838636/0aab7bd1-c0f4-af70-6071-e49a1607157a.png)
###### ソーステーブルの選択
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/838636/c10bd9d9-e2d4-2814-f105-c768b1b90b5d.png)
###### Kafka Propertiesの設定
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/838636/4f58396f-ed51-4109-1465-44f9a1d90866.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/838636/f6a47b57-293c-d82b-b225-291953ead0ed.png)

:::note info
Zookeeperの「Host Name」と「Port」は必須入力欄となっています。
ただし、ここで指定する内容は便宜的なもので **内容はなんでもいいみたいです**。
（再掲）
:::

###### User Exitsの設定
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/838636/0ce86ffb-edf6-0653-76c4-aa3492ce8409.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/838636/3d39db8a-c13f-01ee-38a7-732a48a80d0a.png)
```sh
# Class Name
com.datamirror.ts.target.publication.userexit.sample.kafka.KcopDefaultBehaviorIntegrated
# Parameter（Kcop.propertiesファイルを指定）　※先頭ハイフンも必要
-file:/opt/ibm/InfoSphereDataReplication/ReplicationEngineforKafka/instance/KAFKAF/conf/kcop.properties
```

#### (6) リフレッシュ（ソーステーブルのデータの一括転送）
###### Flag for Refresh
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/838636/c30477ef-e3c4-b5f5-2186-1650541ee242.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/838636/b3bcfd02-7840-0298-3496-a3c5d4f767c8.png)
###### Start Refresh
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/838636/53883654-f582-e9f6-da5d-cb79c008a416.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/838636/e65ab52d-30da-5147-a057-dcc2b30d9117.png)
画面下部にあるEvents欄で「45 rows were sent.」というメッセージを確認できます。
リフレッシュ成功。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/838636/7cd49b93-c982-f599-5ad3-659f08845260.png)

###### Amazon MSKのトピック一覧をKafkaのユーティリティで確認
```sh:Kafkaのユーティリティを使用する際のプロパティファイルの内容
# /mskconfig-ssk.properties
security.protocol=SASL_SSL
sasl.mechanism=SCRAM-SHA-512
sasl.jaas.config=org.apache.kafka.common.security.scram.ScramLoginModule required username="my-user" password="my-password";
ssl.truststore.location=/tmp/kafka.client.truststore.jks
ssl.truststore.password=changeit
```

```sh:トピックの一覧表示
./kafka-topics.sh  --bootstrap-server b-2-public.mymskcluster.zxrapj.c4.kafka.ap-northeast-1.amazonaws.com:9196,b-3-public.mymskcluster.zxrapj.c4.kafka.ap-northeast-1.amazonaws.com:9196,b-1-public.mymskcluster.zxrapj.c4.kafka.ap-northeast-1.amazonaws.com:9196 --command-config /mskconfig-ssk.properties --list
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/838636/293929dd-5402-31c5-b4f9-297efa17b9da.png)
kafkaf.mskavro2.sourcedb.cdcadmin.test1がIIDR CDCでのリフレッシュによって作成されたトピックです。

```sh:トピックの中身のメッセージを表示
./kafka-console-consumer.sh --bootstrap-server b-2-public.mymskcluster.zxrapj.c4.kafka.ap-northeast-1.amazonaws.com:9196,b-3-public.mymskcluster.zxrapj.c4.kafka.ap-northeast-1.amazonaws.com:9196,b-1-public.mymskcluster.zxrapj.c4.kafka.ap-northeast-1.amazonaws.com:9196 --topic kafkaf.mskavro2.sourcedb.cdcadmin.test1 --consumer.config /mskconfig-ssk.properties --from-beginning
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/838636/74a86680-11bf-861b-afe2-6cb8f188a5e9.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/838636/23392187-766e-25cf-4c64-fb366ba6975a.png)

###### AWS Glue Schema Registryでスキーマを確認
トピック名-スキーマ名（スキーマ内のname）-key と
トピック名-スキーマ名（スキーマ内のname）-value という名前で登録されています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/838636/d464b150-b492-e4de-f909-d5349fb67171.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/838636/bcebc7d4-430a-80a8-50b6-e25be0dfdad0.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/838636/8af09ae2-8634-01fa-72c9-c40783986b24.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/838636/0e869585-644f-9a16-94ad-d7ca42fafae9.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/838636/84832756-a7b9-e337-d543-70cbcbb7cf8e.png)

# おわりに
ここで紹介した内容を参考にすることで、データベースにあるデータ更新の都度、その変更内容をAmazon MSKに伝えることができます。
これにより、Amazon MSKを利用したイベントドリブンなユースケースの実装や高鮮度データを分析を実現できるようになります。
以下は、ここまでに説明した内容です。

- ① Amazon MSKのクラスターを構築する（→[Amazon MSKでオンプレから接続可能なKafkaクラスターの構築方法（できるだけ普通のKafkaっぽく使えるように）](https://qiita.com/ysskjck/items/40478b77a9af46f7779c)）
- ② AWS外からJavaプログラムを使用して以下を実施（→[Amazon MSKのKafkaクラスターにJavaプログラムでProduce／Consume（String形式＆Avro形式）](https://qiita.com/ysskjck/items/6da09ddb093f228d1b60)）
  - シンプルな文字列形式のメッセージのProduce/Consume
  - AWS Glue Schema Registryの設定
  - Avro形式のメッセージのProduce/Consume
- ③ AWS外からIBM Infosphere Replication CDCを使用して以下を実施（**本稿で紹介**）
  - JSON形式でキャプチャー対象テーブルのデータをAmazon MSKに送信
  - Avro形式でキャプチャー対象テーブルのデータをAmazon MSKに送信