非IT企業に勤める中年サラリーマンのIT日記

非IT企業でしかもITとは全く関係ない部署にいる中年エンジニア。唯一の趣味がプログラミングという”自称”プログラマー。

【Xserver VPS】C#からSSHトンネル経由でDocker上のMySQLに接続したのでメモ

   

Xserver VPS上のDocker環境で運用しているMySQLに対し、外部のC#デスクトップアプリケーションから接続するのにちょっと苦労したのでその手順を書き留めておきます。通常、データベースポートはセキュリティ上外部へ開放しませんが、SSHポートフォワーディング(トンネル)を活用することで、安全な通信経路を確保しつつ直接的なデータ操作が可能となります。本記事では、ライブラリの導入から、非同期処理を組み込んだ実用的なソースコードの実装、およびDocker側の設定上の留意点までを記録しておきます。

Visual Studioのライブラリインストール手順

C#でSSH接続およびMySQL操作を行うため、NuGetパッケージマネージャーから以下の2つのライブラリをインストールする。

  • SSH.NET: SSH接続およびポートフォワーディングを制御するための標準的なライブラリ。
  • MySqlConnector: 高速で非同期処理に対応したMySQL接続ライブラリ。

手順は、

  1. Visual Studioのメニューから [ツール] > [NuGet パッケージ マネージャー] > [ソリューションの NuGet パッケージの管理] を選択。
  2. [参照] タブで「SSH.NET」および「MySqlConnector」を検索し、プロジェクトにインストールする。
  3. もしくは、パッケージマネージャーコンソールで以下を実行する。

ライブラリが見つからない場合の設定(Visual Studio)

NuGetパッケージマネージャーで「SSH.NET」や「MySqlConnector」がヒットしない場合、主に「パッケージソースの設定」か「キャッシュ・反映の不備」が原因です。以下の手順で解決できます。

パッケージソースを確認する

NuGetが参照している「取得先」が正しく設定されているか確認します。

  • NuGetパッケージマネージャー画面の右上にある [パッケージソース] ドロップダウンを確認します。
  • ここが「Microsoft Visual Studio Offline Packages」などになっていると、インターネット上のライブラリが表示されません。
  • 必ず [nuget.org] を選択してください。
パッケージソース(nuget.org)を追加する

もし選択肢に「nuget.org」がない場合は、手動で追加が必要です。

  • ドロップダウン横の 歯車アイコン(設定) をクリックします。
  • 左メニューの [NuGet パッケージ マネージャー] > [パッケージ ソース] を開きます。
  • 右上の [+](追加) ボタンを押し、以下の内容を入力して [更新] をクリックします。
    • 名前: nuget.org
    • ソース: https://api.nuget.org/v3/index.json
  • これで再度検索を行うと、最新のパッケージが表示されるようになります。

Docker側の設定

SSHトンネルの「出口」がMySQLコンテナに到達できるよう、docker-compose.yml にてホスト側(VPS)へポートをバインドさせる必要があります。以下のコードを追加しましょう。

services:
  db:
    ports:
      - "127.0.0.1:3306:3306" # ホストの3306をコンテナの3306に繋ぐ
 

 

ports 設定においてホストの 3306 を明示的に公開することで、SSHトンネルがVPS内部でMySQLへ到達可能となります。

修正したら docker compose up -d で設定を反省させましょう。

C# ソースコード

using System;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using Renci.SshNet; // SSH接続用
using MySqlConnector; // MySQL接続用

namespace VPSDBClient
{
    public partial class Form1 : Form
    {
        // サーバー接続情報(実際の情報は環境に合わせて置換)
        private const string SshHost = "000.000.000.000"; // VPSのIPアドレス
        private const string SshUser = "vps_user";       // SSH接続ユーザー名
        private const string KeyPath = @"C:\path\to\your_key.pem"; // 秘密鍵のフルパス

        private const string DbUser = "db_user";     // MySQLユーザー名
        private const string DbPass = "db_password"; // MySQLパスワード
        private const string DbName = "db_name";     // 接続対象データベース名

        public Form1()
        {
            InitializeComponent();
        }

        private async void button1_Click(object sender, EventArgs e)
        {
            button1.Enabled = false; // 処理中の重複クリック防止
            textBox1.Text = "接続中...";

            try
            {
                // 非同期でデータ取得処理を実行し、UIスレッドをブロックしない
                string result = await FetchDataViaSshAsync();
                textBox1.Text = result;
            }
            catch (Exception ex)
            {
                // 詳細なエラー内容をメッセージボックスで表示
                MessageBox.Show($"エラーが発生しました:\n{ex.Message}");
                textBox1.Text = "接続失敗";
            }
            finally
            {
                button1.Enabled = true;
            }
        }

        private async Task<string> FetchDataViaSshAsync()
        {
            var sb = new StringBuilder();

            // 1. 秘密鍵を用いたSSH認証の設定
            var keyFile = new PrivateKeyFile(KeyPath);
            var connectionInfo = new ConnectionInfo(SshHost, SshUser, 
                new PrivateKeyAuthenticationMethod(SshUser, keyFile));

            using (var client = new SshClient(connectionInfo))
            {
                // VPSへSSH接続を開始
                client.Connect();

                // 2. ローカルポートフォワーディングの設定
                // ローカルPCの13306ポートを、VPS内部の127.0.0.1:3306へ転送する
                var forwardedPort = new ForwardedPortLocal("127.0.0.1", 13306, "127.0.0.1", 3306);
                client.AddForwardedPort(forwardedPort);
                forwardedPort.Start();

                // トンネル開通後、通信が安定するまで待機
                await Task.Delay(1000);

                // 3. MySQL接続文字列の構築
                // 接続先はトンネルの入り口である 127.0.0.1:13306 を指定
                string connStr = $"Server=127.0.0.1;Port=13306;Database={DbName};Uid={DbUser};Pwd={DbPass};SslMode=None;AllowPublicKeyRetrieval=True;";

                try
                {
                    using (var conn = new MySqlConnection(connStr))
                    {
                        // データベース接続を非同期で開始
                        await conn.OpenAsync();

                        string sql = "SELECT * FROM target_table LIMIT 10;";
                        using (var cmd = new MySqlCommand(sql, conn))
                        using (var reader = await cmd.ExecuteReaderAsync())
                        {
                            while (await reader.ReadAsync())
                            {
                                // 取得データを整形してStringBuilderに蓄積
                                sb.AppendLine($"{reader.GetValue(0)} | {reader.GetValue(1)}");
                            }
                        }
                    }
                }
                finally
                {
                    // MySQL接続終了後、ポート転送とSSH接続を確実にクローズする
                    if (forwardedPort.IsStarted) forwardedPort.Stop();
                    if (client.IsConnected) client.Disconnect();
                }
            }

            return sb.Length > 0 ? sb.ToString() : "データが存在しません。";
        }
    }
}
 

 

実行結果です。ボタンを押すと接続されます。

 

 

スポンサーリンク

 - Xserver VPS