結論
今回inですがoutでも多分同じノリで行けると思われる
# 動かない
in:
type: jdbc
driver_class: net.snowflake.client.jdbc.SnowflakeDriver
driver_path: /opt/snowflake/snowflake-jdbc.jar
url: {{ env.SNOWFLAKE_URL}}
user: {{ env.SNOWFLAKE_USER}}
private_key_file: {{ env.SNOWFLAKE_PRIVATE_KEY_FILE }}
private_key_passphrase: ""
# 動く(スマート)
in:
type: jdbc
driver_class: net.snowflake.client.jdbc.SnowflakeDriver
driver_path: /opt/snowflake/snowflake-jdbc.jar
url: {{ env.SNOWFLAKE_URL}}
user: {{ env.SNOWFLAKE_USER}}
options:
private_key_file: {{ env.SNOWFLAKE_PRIVATE_KEY_FILE }}
private_key_passphrase: ""
# 動く(URLにねじ込む)
in:
type: jdbc
driver_class: net.snowflake.client.jdbc.SnowflakeDriver
driver_path: /opt/snowflake/snowflake-jdbc.jar
url: {{ env.SNOWFLAKE_URL }}?user={{ env.SNOWFLAKE_USER }}&private_key_file={{ env.SNOWFLAKE_PRIVATE_KEY_FILE }}&private_key_file_pwd={{ env.SNOWFLAKE_PRIVATE_KEY_PASSPHRASE }}
動かないときのエラー内容
2025-08-18 04:32:43.844 +0000 [INFO] (0001:cleanup): Gem's home and path are set by system configs "gem_home": "/root/.embulk/lib/gems", "gem_path": ""
2025-08-18 04:32:44.031 +0000 [INFO] (0001:cleanup): Loaded JRuby runtime 9.4.5.0
2025-08-18 04:32:44.064 +0000 [INFO] (0001:cleanup): Loaded plugin embulk-input-jdbc (0.13.2)
2025-08-18 04:32:44.108 +0000 [INFO] (0001:cleanup): Loaded plugin embulk-output-mysql (0.10.6)
org.embulk.exec.PartialExecutionException: java.lang.RuntimeException: net.snowflake.client.jdbc.SnowflakeSQLLoggedException: Missing password.
at org.embulk.exec.BulkLoader$LoaderState.buildPartialExecuteException(BulkLoader.java:340)
at org.embulk.exec.BulkLoader.doRun(BulkLoader.java:580)
at org.embulk.exec.BulkLoader.access$000(BulkLoader.java:36)
at org.embulk.exec.BulkLoader$1.run(BulkLoader.java:353)
at org.embulk.exec.BulkLoader$1.run(BulkLoader.java:350)
at org.embulk.spi.ExecInternal.doWith(ExecInternal.java:26)
at org.embulk.exec.BulkLoader.run(BulkLoader.java:350)
at org.embulk.EmbulkEmbed.run(EmbulkEmbed.java:278)
at org.embulk.EmbulkRunner.runInternal(EmbulkRunner.java:288)
at org.embulk.EmbulkRunner.run(EmbulkRunner.java:153)
at org.embulk.cli.EmbulkRun.runInternal(EmbulkRun.java:115)
at org.embulk.cli.EmbulkRun.run(EmbulkRun.java:24)
at org.embulk.cli.Main.main(Main.java:53)
Suppressed: java.lang.NullPointerException
at org.embulk.exec.BulkLoader.doCleanup(BulkLoader.java:477)
at org.embulk.exec.BulkLoader$3.run(BulkLoader.java:411)
at org.embulk.exec.BulkLoader$3.run(BulkLoader.java:408)
at org.embulk.spi.ExecInternal.doWith(ExecInternal.java:26)
at org.embulk.exec.BulkLoader.cleanup(BulkLoader.java:408)
at org.embulk.EmbulkEmbed.run(EmbulkEmbed.java:283)
... 5 more
Caused by: java.lang.RuntimeException: net.snowflake.client.jdbc.SnowflakeSQLLoggedException: Missing password.
at org.embulk.input.jdbc.AbstractJdbcInputPlugin.transaction(AbstractJdbcInputPlugin.java:227)
at org.embulk.exec.BulkLoader.doRun(BulkLoader.java:521)
... 11 more
Caused by: net.snowflake.client.jdbc.SnowflakeSQLLoggedException: Missing password.
at net.snowflake.client.jdbc.DefaultSFConnectionHandler.initialize(DefaultSFConnectionHandler.java:134)
at net.snowflake.client.jdbc.DefaultSFConnectionHandler.initializeConnection(DefaultSFConnectionHandler.java:102)
at net.snowflake.client.jdbc.SnowflakeConnectionV1.initConnectionWithImpl(SnowflakeConnectionV1.java:146)
at net.snowflake.client.jdbc.SnowflakeConnectionV1.<init>(SnowflakeConnectionV1.java:123)
at net.snowflake.client.jdbc.SnowflakeDriver.connect(SnowflakeDriver.java:231)
at org.embulk.input.jdbc.JdbcInputPlugin.newConnection(JdbcInputPlugin.java:87)
at org.embulk.input.jdbc.AbstractJdbcInputPlugin.transaction(AbstractJdbcInputPlugin.java:213)
... 12 more
Error: java.lang.RuntimeException: net.snowflake.client.jdbc.SnowflakeSQLLoggedException: Missing password.
.env の概要
SNOWFLAKE_URL=jdbc:snowflake://xxxxx-xxxxx.snowflakecomputing.com
SNOWFLAKE_USER=xxx
SNOWFLAKE_PASSWORD=xxxxxx
SNOWFLAKE_PRIVATE_KEY_FILE=/tmp/snowflake_rsa_key.p8
SNOWFLAKE_RSA_PRIVATE_KEY=xxxxxxxx
前置き
Snowflakeを導入した当初にもprivate key接続は始まっていたはずですが、サボりともかく動くことを優先するためにID,Password認証を選びました。
当時の記事
in:
type: jdbc
driver_class: net.snowflake.client.jdbc.SnowflakeDriver
driver_path: /opt/snowflake/snowflake-jdbc.jar
url: {{ env.SNOWFLAKE_URL}}
user: {{ env.SNOWFLAKE_USER}}
password: {{ env.SNOWFLAKE_PASSWORD}}
query: |
SELECT * FROM table_name
columns:
- {name: as_of_date, type: string}
out:
type: mysql
host: {{ env.DB_HOST }}
user: {{ env.DB_USER }}
password: {{ env.DB_PASSWORD }}
database: {{ env.DB_DATABASE }}
port: {{ env.DB_PORT }}
table: table_name
mode: merge
create_table_constraint: 'primary key()'
column_options:
as_of_date : {type: date, value_type: string}
アウトプットは移行時のみで、現在はインプット(Snowflake→mysqlのリバースETL用)のみ使っています。
※この先パイプラインによってはインプットで使う可能性はあります。
有志がOSSとして公開しているプラグインはアウトプットが多いです。
Embulk で Snowflake からデータを読み込む embulk-input-snowflakedb を公開しました
Snowflake output plugin for Embulk
OSSではないですが、今見るとURLのみで記述していますね。
ETL ツールのEmbulk を使ってSnowflake のデータをDB にロードする方法
当初 input,output両方使う関係もありjdbc 接続を選び、苦戦しながらも実務に載せましたが、Snowflakeのパスワード認証終了に伴い、当初先送りした問題に後追いで取り組みました。
対応内容
[余談]Dockerfileの修正
intel macからm4 macに変わったせいなのか、セキュリティツールの影響なのか謎にbuildが通らなくなりdebian系にimageを変えました
# FROM openjdk:8-jre-alpine 以前使っていたimage
FROM openjdk:8-jre-slim
RUN apt-get update && apt-get install -y wget
RUN wget -q https://github.com/embulk/embulk/releases/download/v0.11.5/embulk-0.11.5.jar -O /bin/embulk \
&& chmod +x /bin/embulk
RUN mkdir -p /opt/snowflake \
&& wget --no-check-certificate -O /opt/snowflake/snowflake-jdbc.jar "https://repo1.maven.org/maven2/net/snowflake/snowflake-jdbc/3.19.1/snowflake-jdbc-3.19.1.jar"
RUN mkdir -p /bin/jruby \
&& wget --no-check-certificate -O /bin/jruby/jruby-complete-9.4.5.0.jar "https://repo1.maven.org/maven2/org/jruby/jruby-complete/9.4.5.0/jruby-complete-9.4.5.0.jar"
RUN chmod +x /bin/jruby/jruby-complete-9.4.5.0.jar
COPY ./embulk.properties /root/.embulk/embulk.properties
RUN embulk gem install embulk -v 0.11.5
RUN embulk gem install msgpack -v 1.7.2
RUN embulk gem install liquid -v 4.0.0
RUN embulk gem install bundler -v 1.16.0
RUN embulk gem install embulk-input-jdbc
RUN embulk gem install embulk-input-mysql
RUN embulk gem install embulk-output-mysql
RUN apt-get update && apt-get install -y default-mysql-client
WORKDIR /work
COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
COPY config /work
# ENTRYPOINT ["/usr/bin/entrypoint.sh"] 以下デバッグ用
CMD ["tail", "-f", "/dev/null"]
以降紹介する内容はコンテナにexecで接続し切り分けを行いました
base64エンコード周り
今回Snowflakeの認証で使う秘密鍵は、base64エンコードした状態でAWS Systems Manager Parameter Storeに登録されています。
base64エンコードした状態だと1行の文字列で管理できる反面、接続方法によってはデコードが必要になります。
(知ってるような口調ですが今回理解しました)
この鍵の扱いが間に挟まるところでトラブルシューティングが複雑化します…
最終的にはembulkを呼び出す前にp8を作る処理を入れました。
#!/bin/sh
if [ -n "$SNOWFLAKE_RSA_PRIVATE_KEY" ]; then
echo "-----BEGIN PRIVATE KEY-----" > /tmp/snowflake_rsa_key.p8
echo "$SNOWFLAKE_RSA_PRIVATE_KEY" | fold -w 64 >> /tmp/snowflake_rsa_key.p8
echo "-----END PRIVATE KEY-----" >> /tmp/snowflake_rsa_key.p8
export SNOWFLAKE_PRIVATE_KEY_FILE=/tmp/snowflake_rsa_key.p8
fi
WORK_DIR=${WORK_DIR:=hourly}
ls /work/${WORK_DIR}/*.yml.liquid | xargs -n1 java -jar /bin/embulk run
接続テスト
Github copilotに動作確認用のプログラムを用意してもらいました。
実環境は環境変数化していたのと、パスワード認証のユーザーとprivate key接続のユーザーは別で作っていたので、ベタ書きで切り分けを行いました
import java.sql.Connection;
import java.sql.DriverManager;
import java.util.Properties;
public class SnowflakeJDBCTest {
public static void main(String[] args) throws Exception {
String url = "jdbc:snowflake://xxxxx-xxxxx.snowflakecomputing.com/?user=xxxx&private_key_file=/tmp/snowflake_rsa_key.p8&private_key_file_pwd=";
// props.put("user", "xxx");
// props.put("privateKeyFile", "/tmp/snowflake_rsa_key.p8");
// props.put("privateKeyType", "PKCS8"); // または "PKCS1"
// props.put("privateKeyFilePwd", ""); // パスフレーズなしの場合は空文字
// props.put("password", "xxxxxx"); // パスワード認証の場合のみ
Connection conn = DriverManager.getConnection(url);
System.out.println("接続成功!");
conn.close();
}
}
Github copilotのサポートを受けながら、コンパイルするためのパッケージを適当に追加します。
Javaのコンパイルとか久しぶりすぎてクラス名とファイル名を合わせる必要があるんだ…となりました。(Eclipseで触ってたから意識していなかったのかもしれないし、そもそもjavaは一瞬しか触っていないので…)
このプログラム自体の動作確認としてパスワード認証で接続できることを確認します。
URLに押し込む書き方を見つける
最初は以下のように動かない方法で記述していたのですが、何度やっても Missing password. になり、Issueでも探してみるか。となったところそれっぽいものを見つけ解決といった具合です。
# 動かない
in:
type: jdbc
driver_class: net.snowflake.client.jdbc.SnowflakeDriver
driver_path: /opt/snowflake/snowflake-jdbc.jar
url: {{ env.SNOWFLAKE_URL}}
user: {{ env.SNOWFLAKE_USER}}
private_key_file: {{ env.SNOWFLAKE_PRIVATE_KEY_FILE }}
private_key_passphrase: ""
# 動く(URLにねじ込む)
in:
type: jdbc
driver_class: net.snowflake.client.jdbc.SnowflakeDriver
driver_path: /opt/snowflake/snowflake-jdbc.jar
url: {{ env.SNOWFLAKE_URL }}?user={{ env.SNOWFLAKE_USER }}&private_key_file={{ env.SNOWFLAKE_PRIVATE_KEY_FILE }}&private_key_file_pwd={{ env.SNOWFLAKE_PRIVATE_KEY_PASSPHRASE }}
stackoverflowでも似たような回答があり、両方なんとなく見ながら、URL形式を試した記憶があります。
Embulk Core Teamの方からのアドバイス
Xで今回の概要をpostしたところ、optionsに書けば行けるという情報をいただきました
こうして以下の書き方も見つけることができました。(返信もらったからには早く確認せねばと思った夜)
# 動く(スマート)
in:
type: jdbc
driver_class: net.snowflake.client.jdbc.SnowflakeDriver
driver_path: /opt/snowflake/snowflake-jdbc.jar
url: {{ env.SNOWFLAKE_URL}}
user: {{ env.SNOWFLAKE_USER}}
options:
private_key_file: {{ env.SNOWFLAKE_PRIVATE_KEY_FILE }}
private_key_passphrase: ""
確かにいただいたURL https://github.com/embulk/embulk-output-jdbc/tree/master/embulk-output-jdbc#configuration のoptionsの記述を見ると気づけたかもしれない…
options: extra JDBC properties (hash, default: {})
copilotに出された提示を鵜呑みにしてはいけない…部分もありつつマイナーな記述だから気付けない気もする。
接続テストで作ったコードも通らなかったし…(これは改めて見ると謎かもしれない)
ここで改めて御礼申し上げます。
終わりに
ベストではないがベターに着地させるってありますよね。ここでスケジュール内にベストを出せるかどうかが一流か二流の壁な気がします。
とはいえスケジュール内に着地させるのも必要な技術なのですごい負い目を感じることはないですが、こういうときに技術力〜〜〜となりますね。この積み重ねが技術的負債になったりするので塩梅は難しいところもありつつ。
ということで、気合を入れて書くほどの内容ではないような気もするのですが、同じ罠にハマる人を救うかつ備忘も兼ねて供養しておきます。