🚇

DBeaver × SSM Port Forwarding で RDS に自動接続する

に公開

はじめに

AWS の RDS はプライベートサブネットに配置することが多く、ローカルから直接接続できません。
SSM Session Manager のポートフォワーディングを使えば SSH 不要で踏み台経由の接続が可能ですが、毎回ターミナルでトンネルを張ってから DBeaver を開くのは面倒です。

DBeaver の Shell Commands 機能を使えば、接続時にトンネルを自動で張り、切断時に自動で閉じることができます。

前提

  • macOS(Homebrew 環境)
  • AWS CLI + Session Manager Plugin がインストール済み
  • aws-vault で AWS クレデンシャルを管理(使わない場合は AWS_PROFILE=xxx aws ssm ... に置き換え可能)
  • SSM Session Manager で接続可能な踏み台 EC2 が存在する(AmazonSSMManagedInstanceCore ポリシーがアタッチ済み)
  • 踏み台の Security Group から RDS の Security Group への Ingress が許可されている

アーキテクチャ

DBeaver (localhost:LOCAL_PORT)

SSM Port Forwarding (session-manager-plugin)

踏み台 EC2 (SSM経由・SSH不要)

RDS (プライベートサブネット)

SSH Tunnel との違いは、EC2 に SSH ポート(22)を開ける必要がないことです。
SSM はマネージドサービス経由で接続するため、踏み台 EC2 の Security Group にインバウンドルールは不要です。

トンネル起動スクリプト

Before Connect 用のスクリプトを作成します。トンネルをバックグラウンドで起動するだけのシンプルなスクリプトです。

~/.local/bin/ssm-db-tunnel.sh
#!/bin/bash
# ssm-db-tunnel.sh
# DBeaver Before Connect 用: SSM ポートフォワーディングトンネルを起動
# 使い方: bash ssm-db-tunnel.sh <aws_profile> <instance_id> <rds_host> <rds_port> <local_port>
# 例: ${home}/.local/bin/ssm-db-tunnel.sh my-aws-profile i-0123456789abcdef0 my-rds-instance.xxx.ap-northeast-1.rds.amazonaws.com 5432 ${port}

export PATH="/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:$PATH"

AWS_PROFILE="$1"
INSTANCE_ID="$2"
RDS_HOST="$3"
RDS_PORT="$4"
LOCAL_PORT="$5"

if [ -z "$AWS_PROFILE" ] || [ -z "$INSTANCE_ID" ] || [ -z "$RDS_HOST" ] || [ -z "$RDS_PORT" ] || [ -z "$LOCAL_PORT" ]; then
  echo "Usage: $0 <aws_profile> <instance_id> <rds_host> <rds_port> <local_port>" >&2
  exit 1
fi

LOGFILE="/tmp/ssm-db-tunnel-${LOCAL_PORT}.log"

# 既にトンネルが張られていたら何もしない
if lsof -i :"$LOCAL_PORT" -sTCP:LISTEN >/dev/null 2>&1; then
  exit 0
fi

# バックグラウンドでトンネル起動
aws-vault exec "$AWS_PROFILE" -- aws ssm start-session \
  --target "$INSTANCE_ID" \
  --document-name AWS-StartPortForwardingSessionToRemoteHost \
  --parameters "{\"host\":[\"$RDS_HOST\"],\"portNumber\":[\"$RDS_PORT\"],\"localPortNumber\":[\"$LOCAL_PORT\"]}" \
  >> "$LOGFILE" 2>&1 &
chmod +x ~/.local/bin/ssm-db-tunnel.sh

引数の順序は aws ssm start-session コマンドの構造に合わせています。

aws-vault exec <aws_profile> -- aws ssm start-session \
  --target <instance_id> \
  --parameters host=<rds_host>, portNumber=<rds_port>, localPortNumber=<local_port>

DBeaver の Shell Commands にコマンドをベタ書きするので、引数の並びがコマンドの構造と一致しているほうが読みやすいです。

なぜスクリプトファイルが必要なのか

直感的には、DBeaver の Shell Commands にコマンドを直接書きたくなります。

/opt/homebrew/bin/aws-vault exec my-profile -- /usr/local/bin/aws ssm start-session --target i-xxx ... &

しかし、これは動きません。理由は2つあります。

  1. &(バックグラウンド実行)はシェルの機能であり、DBeaver が直接実行するコマンドでは解釈されない
  2. bash -c '... &' で囲んでも動かない — DBeaver はコマンド完了後にプロセスグループごと kill するため、バックグラウンドプロセスも巻き添えで終了する

スクリプトファイル経由であれば、& でバックグラウンド化したプロセスがスクリプト終了時に孤児化し、DBeaver の管理から外れるため生き残れます。

PATH の export が必要な理由

DBeaver を Dock や Spotlight から起動した場合、PATH は /usr/bin:/bin:/usr/sbin:/sbin のみです(macOS の GUI アプリは launchd から環境変数を受け取るため、.zshrc の設定は引き継がれません)。
aws-vaultawssession-manager-plugin はこの PATH に含まれないため、スクリプト内で明示的に export しています。

なぜスクリプト内でトンネルの確立を待たないのか

DBeaver の Shell Commands は Wait for process to finish がオフの場合、スクリプトの完了を待たず、Pause after execute に設定した時間だけ待機して接続を開始します。
そのため、スクリプト側で lsof 等を使ってポートの LISTEN を待つ処理を入れても効きません。
トンネルの確立待ちは DBeaver の Pause after execute に任せます。

トンネル停止スクリプト

After Disconnect 用のスクリプトです。ポート番号でプロセスを特定して kill します。

~/.local/bin/ssm-db-tunnel-stop.sh
#!/bin/bash
# ssm-db-tunnel-stop.sh
# DBeaver After Disconnect 用: SSM トンネルを終了
# 使い方: bash ssm-db-tunnel-stop.sh <local_port>
# 例: ${home}/.local/bin/ssm-db-tunnel-stop.sh ${port}

export PATH="/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:$PATH"

LOCAL_PORT="$1"

if [ -z "$LOCAL_PORT" ]; then
  echo "Usage: $0 <local_port>" >&2
  exit 1
fi

# 該当ポートを LISTEN しているプロセスを kill
kill $(lsof -ti :"$LOCAL_PORT") 2>/dev/null
chmod +x ~/.local/bin/ssm-db-tunnel-stop.sh

pgrep -fps aux | grep でプロセス名を検索する方法もありますが、環境変数等にもマッチして無関係なプロセスを kill する可能性があります。lsof -ti ならポートを LISTEN しているプロセスだけを正確に特定できます。

kill は SIGTERM を送信します。session-manager-plugin は SIGTERM を受けると SSM セッションを正常に終了するため、AWS 側にセッションが残る心配はありません。

DBeaver の設定

接続の基本設定

項目
Host localhost
Port 15432(他のサービスと衝突しない任意のポート。DB のポート 5432 に prefix 1 をつけるなど)
Database 接続先の DB 名

接続の基本設定

Shell Commands の設定

DBeaver の接続設定 → Shell Commands タブを開きます。

Before Connect:

項目
Command ${home}/.local/bin/ssm-db-tunnel.sh my-aws-profile i-0123456789abcdef0 my-rds-instance.xxx.ap-northeast-1.rds.amazonaws.com 5432 ${port}
Pause after execute 3000重要: トンネル確立までの待ち時間。後述)

Before Connect の設定

After Disconnect:

項目
Command ${home}/.local/bin/ssm-db-tunnel-stop.sh ${port}
Pause after execute 0

After Disconnect の設定

${port}${home} は DBeaver の変数で、接続設定の値に展開されます。

DBeaver の変数一覧

Pause after execute について

Before Connect の Pause after execute は、スクリプト実行後に DBeaver が接続を開始するまでの待ち時間(ミリ秒)です。
トンネルの確立には数秒かかるため、3000(3秒)程度を設定してください。接続に失敗する場合は値を大きくしてみてください。

複数環境の同時接続

環境ごとにローカルポートを変えれば同時接続が可能です。

環境 ローカルポート Before Connect の引数例
Staging (PostgreSQL) 15432 ... stg-rds.xxx.rds.amazonaws.com 5432 ${port}
Production (PostgreSQL) 25432 ... prod-rds.xxx.rds.amazonaws.com 5432 ${port}
Staging (MySQL) 13306 ... stg-mysql.xxx.rds.amazonaws.com 3306 ${port}
  • 各トンネルは独立したプロセスで動作する
  • Staging だけ切断しても Production のトンネルには影響しない
  • After Disconnect は ${port} でプロセスを特定するため、対象のトンネルだけが終了する

トンネルの確認

トンネルが張られているかは lsof で確認できます。

$ lsof -i :15432 -sTCP:LISTEN 2>/dev/null
COMMAND     PID             USER   FD   TYPE             DEVICE SIZE/OFF NODE NAME
session-m 53242 your-user   21u  IPv4 0xe85a263xxxxxxxxxx      0t0  TCP localhost:15432 (LISTEN)

出力があればトンネルは起動中、何も出なければ閉じています。

まとめ

  • DBeaver の Shell Commands を使えば、SSM トンネルの起動・停止を接続操作に統合できる
  • トンネルの確立待ちはスクリプトではなく DBeaver の Pause after execute に任せる
  • スクリプトは引数ベースで汎用的に作り、接続固有の情報は DBeaver 側にベタ書きするのがシンプル
  • ローカルポートを変えることで、複数環境への同時接続にも対応できる

Discussion