1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

EmbulkでJDBC Driverを使ってSnowflakeにprivate key接続するときにハマったメモ

Posted at

結論

今回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に出された提示を鵜呑みにしてはいけない…部分もありつつマイナーな記述だから気付けない気もする。
接続テストで作ったコードも通らなかったし…(これは改めて見ると謎かもしれない)

ここで改めて御礼申し上げます。

終わりに

ベストではないがベターに着地させるってありますよね。ここでスケジュール内にベストを出せるかどうかが一流か二流の壁な気がします。

とはいえスケジュール内に着地させるのも必要な技術なのですごい負い目を感じることはないですが、こういうときに技術力〜〜〜となりますね。この積み重ねが技術的負債になったりするので塩梅は難しいところもありつつ。

ということで、気合を入れて書くほどの内容ではないような気もするのですが、同じ罠にハマる人を救うかつ備忘も兼ねて供養しておきます。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?