はじめに
本連載を読んでいる方であれば、WSLはWindows上における開発環境として使っているケースが多いのではないでしょうか。Webアプリであれば、コードを書いてテストサーバーを起動したら、別のPCのブラウザからアクセスしてテストしたいこともあるでしょう。
今回は、WSL1/2それぞれの環境において、外部からサーバーへネットワークアクセスする方法を紹介します。
WSL1/2における
ネットワークアーキテクチャの違い
以前にも解説した通り、WSL2はHyper-Vの軽量仮想マシン上で本物のLinuxカーネルを動かしています。これにより、ほぼ完全なシステムコール互換性を実現しているわけです。ところが実体が仮想マシンであるため、ホストのWindowsとは「別マシン」になってしまうという問題があります。
これはネットワークも例外ではなく、WSL2はHyper-Vの仮想スイッチによる独立した仮想ネットワークを持っており、外部ネットワークにはNATを経由して出て行きます。家庭内LANに所属するPCがルーターを経由してインターネットに出ていくのと、構成的には同じです。そのためインターネットから家庭内のPCに直接アクセスできないのと同様に、LAN内の別のPCからWSL2内で立ち上げたテスト環境にも直接アクセスはできません。
対してWSL1はシステムコールの変換レイヤーであるため、LinuxのプロセスをWindows上で直接動作させています。ネットワークもWindowsのTCP/IPスタックを直接利用しており、WindowsとIPアドレスも共有しているのです。そのためWSL1を使えば、Windows Defenderファイアウォールの設定のみで、外部からWSL環境へのアクセスを実現できます。
WSL1環境の用意
まずは、シンプルなWSL1を使った方法を紹介しましょう。第21回を参考に、WSL1のUbuntu 24.04環境を用意してください。新規にインストールする場合は、PowerShellで以下のコマンドを実行します。
$ wsl --install -d Ubuntu-24.04 --version 1
既存のWSL2環境をWSL1に変換するには、以下のコマンドを実行します。
$ wsl --set-version ディストリビューション名(例:Ubuntu-24.04) 1
Streamlitのインストール
今回は、Webアプリ開発の例としてPythonベースのフレームワークStreamlitを使って解説します。WSL1のUbuntu 24.04に、以下のコマンドでStreamlitをインストールしてください。なお、ここでは後述のスクリプトで利用する「psutil」と「plotly」というライブラリも同時にインストールしています。
$ sudo apt install -U -y python3-venv
$ mkdir ~/work && cd ~/work
$ python3 -m venv .venv
$ source .venv/bin/activate
$ pip install streamlit psutil plotly
StreamlitでWebアプリを立ち上げる
WSLの「~/work」ディレクトリ内に、以下のPythonスクリプトを「dashboard.py」という名前で作成してください。
import streamlit as st
import psutil
import pandas as pd
import plotly.graph_objects as go
from datetime import datetime
import time
st.set_page_config(page_title="システムモニター", layout="wide")
st.title("WSLシステムモニタリング")
st.markdown("---")
auto_refresh = st.sidebar.checkbox("自動更新", value=True)
refresh_interval = st.sidebar.slider("更新間隔(秒)", 1, 10, 2)
col1, col2, col3, col4 = st.columns(4)
cpu_percent = psutil.cpu_percent(interval=1)
col1.metric(
label="CPU使用率",
value=f"{cpu_percent}%",
delta=f"{cpu_percent - 50}%" if cpu_percent > 50 else None
)
memory = psutil.virtual_memory()
col2.metric(
label="メモリ使用率",
value=f"{memory.percent}%",
delta=f"{memory.used / (1024**3):.1f}GB 使用中"
)
disk = psutil.disk_usage('/')
col3.metric(
label="ディスク使用率",
value=f"{disk.percent}%",
delta=f"{disk.used / (1024**3):.1f}GB 使用中"
)
col4.metric(label="プロセス数", value=len(psutil.pids()))
st.markdown("---")
col_left, col_right = st.columns(2)
with col_left:
st.subheader("CPUコア別使用率")
cpu_per_core = psutil.cpu_percent(percpu=True)
fig_cpu = go.Figure(data=[
go.Bar(
x=[f"Core {i}" for i in range(len(cpu_per_core))],
y=cpu_per_core,
marker_color='lightblue'
)
])
fig_cpu.update_layout(
yaxis_title="使用率 (%)",
height=300,
showlegend=False
)
st.plotly_chart(fig_cpu, use_container_width=True)
with col_right:
st.subheader("メモリ使用状況")
fig_mem = go.Figure(data=[
go.Pie(
labels=['使用中', '利用可能'],
values=[memory.used, memory.available],
hole=0.4,
marker_colors=['#ff6b6b', '#51cf66']
)
])
fig_mem.update_layout(height=300)
st.plotly_chart(fig_mem, use_container_width=True)
st.subheader("ネットワーク統計")
net_io = psutil.net_io_counters()
net_col1, net_col2, net_col3, net_col4 = st.columns(4)
net_col1.metric("送信", f"{net_io.bytes_sent / (1024**2):.2f} MB")
net_col2.metric("受信", f"{net_io.bytes_recv / (1024**2):.2f} MB")
net_col3.metric("送信パケット", f"{net_io.packets_sent:,}")
net_col4.metric("受信パケット", f"{net_io.packets_recv:,}")
st.subheader("CPU使用率上位プロセス")
processes = []
for proc in psutil.process_iter(['pid', 'name', 'cpu_percent', 'memory_percent']):
try:
processes.append(proc.info)
except (psutil.NoSuchProcess, psutil.AccessDenied):
pass
df = pd.DataFrame(processes)
df = df.sort_values('cpu_percent', ascending=False).head(10)
df['memory_percent'] = df['memory_percent'].round(2)
df['cpu_percent'] = df['cpu_percent'].round(2)
st.dataframe(
df,
column_config={
"pid": "PID",
"name": "プロセス名",
"cpu_percent": "CPU (%)",
"memory_percent": "メモリ (%)"
},
hide_index=True,
use_container_width=True
)
if auto_refresh:
time.sleep(refresh_interval)
st.rerun()
以下のコマンドでアプリを起動します。
$ streamlit run dashboard.py
初回起動時、アプリへのネットワークアクセスを許可して良いかを尋ねてきます。ここで「許可」をクリックすると、Pythonが待ち受けているポートに対してアクセスを許可するルールがWindows Defenderファイアウォールに追加され、外部からのアクセスが可能になります。
起動が完了すると、ターミナル内にアプリへのアクセスに使用するIPアドレスとポートが表示されます。2番目に表示されているのがWindowsのプライベートIPアドレスです。Webブラウザから、このアドレスとポートへアクセスしてみましょう。
初回起動時に
アクセスを許可しなかった場合の対処
初回起動時のダイアログで「キャンセル」を選択すると、Windows Defenderファイアウォールに明示的にアクセスをブロックするルールが追加されてしまいます。うっかりこの状態になってしまったら、一度ルールを削除しない限りアクセスを許可できなくなります。そのような場合は、次のように操作してください。
「コントロールパネル」→「システムとセキュリティ」→「Windows Defenderファイアウォール」を開き、左ペインにある「詳細設定」をクリックします。
「受信の規則」をクリックすると、ファイアウォールのルール一覧が表示されます。その中に「Python」をブロックするルールが2つ登録されているので、これを削除してください。
この状態でもう一度Streamlitを起動すれば、再度許可を求めるダイアログが表示されます。
WSL2に外部からアクセスする方法
ポート転送でアクセスする
ここまで説明したように、WSL1はWindowsとネットワークを共有しているため、Windows側でファイアウォールにルールを追加するだけでWSL上のプロセスにアクセスできました。しかし、WSL2のネットワークはホストのWindowsとは異なる仮想ネットワークです。そのため、WSL2で同様に外部からのアクセスを可能にするには別途ポート転送の設定が必要となります。
PowerShellを管理者権限で起動したら以下のコマンドを実行し、WSL2に割り当てられているIPアドレスを調べてください。この例では172.22.112.136が該当のアドレスになります。
$ wsl -d (WSL2のディストリビューション名) hostname -I
172.22.112.136
続いて、PowerShellで以下のコマンドを実行してください(connectaddressに指定するIPアドレスは、上記のコマンドで調べたものに読み替えてください)。これで、Windowsのポート8501番へのアクセスがWSL2に転送されるようになります。
$ netsh interface portproxy add v4tov4 listenport=8501 listenaddress=0.0.0.0 connectport=8501 connectaddress=172.22.112.136
ただし、この方法には問題もあります。WSL2のIPアドレスは再起動などで動的に変化するという点です。netshによるポート転送は、ターゲットのIPアドレスが変化すると機能しなくなります。そのため、都度ポート転送の設定をやり直す必要があるのです。恒久的に利用するには向きません。
ミラーモードでアクセスする
これを解決する手段が、ホストであるWindowsのNICをWSL2にミラーリングする「ミラーモード」です。この機能を有効にするとWSL2とWindowsで同じIPアドレスが使えます。もし前述のポート転送を設定済みの場合は管理者権限のPowerShellで以下のコマンドを実行し、ルールをすべて削除しておいてください。
$ netsh interface portproxy reset
ミラーモードを有効にするには、スタートメニューから「WSL Settings」を起動してください。「ネットワーク」→「ネットワークモード」を既定の「Nat」から「Mirrored」に変更します。
設定が完了したら、以下のコマンドで一度WSL2をシャットダウンしてから再起動します。
$ wsl --shutdown
もう一度WSL2のIPアドレスを確認してみると、ホストのWindowsと同一のIPアドレスが割り当てられていることが分かります。また、IPv6も有効になっています。
$ wsl -d Ubuntu-2 hostname -I
192.168.1.99 2405:6583:d820:800:4c6:55f4:f2e:4575 2405:6583:d820:800:55e3:e5c9:91b5:384b
ミラーモードは、WSL2のネットワークがWSL1のようにWindowsと統合されます。そのため、サービスに外部からアクセスさせるには別途Windows Defenderファイアウォールの設定が必要となる点に注意してください。Streamlitにアクセスさせるには管理者権限のPowerShellで以下のコマンドを実行し、8501番ポートへのアクセスを許可する必要があります。
$ New-NetFirewallRule -DisplayName "Streamlit" -Direction Inbound -LocalPort 8501 -Protocol TCP -Action Allow
以後は、WSL2でもWindowsのIPアドレスでStreamlitにアクセスできるようになります。それだけならWSL1と同じに過ぎませんが、WSL2はsystemdが使えます。つまり、複数のデーモンを動かしっぱなしにするような用途ではWSL2+ミラーモードの方が便利でしょう。例えば、WSL2内でSSHサーバーを起動し、以下のコマンドで22番ポートへのアクセスを許可すれば別のPCからSSH経由でWSLを操作できます。
$ New-NetFirewallRule -DisplayName "SSH" -Direction Inbound -LocalPort 22 -Protocol TCP -Action Allow
おわりに
WSL1でも2でも、外部からアクセス可能なサービスを立てられることが理解できたのではないでしょうか。ただし、WSL2はミラーモードを使わない場合は少々面倒です。また、ミラーモードはすべてのWSL2ディストリビューションに設定が反映されてしまうという問題もあります。
最初からWindowsとネットワークが統合されているWSL1と、systemdで複数サービスを動かしやすいWSL2。ネットワークの用途によっても、WSLのバージョンの使い分けを検討してみてください。
- この記事のキーワード
