WSL2 + 透過プロキシでプロキシ環境下のDockerを(少し)改善する


## はじめに

Dockerを本格的に使い始めた際に、コンテナ個別にプロキシを設定しないといけなかったり、コンテナイメージがビルド出来なかったりと散々だったので、どうにかこうにか解決しようとした際のメモを共有します。

## 最終的に構築した環境

![環境概要](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695104/ba4f007a-9c8f-518a-d603-2e49d342039f.png)

構築した環境は上記の通りで、本記事では構築するにあたり実施した下記の項目について記載しています。

1. WSL2・Dockerのインストール
2. [こちらのQiita記事](https://qiita.com/wadahiro/items/7fa852b7217940e6ef26)の透過プロキシを設定
3. Windowsのポートフォワード設定
4. WSL2/Dockerの自動起動設定

## 1.1. WSL2の導入

最新のWSL2の導入については、[こちら](https://docs.microsoft.com/ja-jp/windows/wsl/install-win10)のMicrosoftのページに沿ってインストールしてください。
実施した事項は以下の通りです。

* WSL、仮想マシンプラットフォーム機能の有効化

``` PowerShell:PowerShell
dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart
dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart
```

* Windowsを再起動し、機能のインストールを完了する
* [こちら](https://wslstorestorage.blob.core.windows.net/wslblob/wsl_update_x64.msi)のインストーラを実行しLinux カーネル更新プログラムをインストール
* WSL2を既定のバージョンに設定
 
``` PowerShell:PowerShell
wsl --set-default-version 2
```

* Microsoftストアを開き、Ubuntu18.04をインストール  
  ※もしWSUSを利用している方でエラーになった場合、グループポリシーで「インターネット上の Windows Update に接続しない」が有効になっていないか確認してください。
* Ubuntuを起動し、ユーザー名・パスワードを設定

## 1.2. Dockerのインストール
ここではUbuntu上にDockerをインストールします。
Docker Desktop for Windowsを利用すると独自のバックエンドが動作し、透過プロキシを設定できなさそうだったのが理由の一つになります。

* Proxyを環境変数に設定

``` bash
$ export http_proxy="http://<user_id>:<password>@<host>:<port>"
$ export https_proxy="http://<user_id>:<password>@<host>:<port>"
```

``` conf:/etc/apt/apt.conf
Acquire::http::Proxy "http://<user_id>:<password>@<host>:<port>";
Acquire::https::Proxy "http://<user_id>:<password>@<host>:<port>";
```

* Docker インストール

``` bash
$ sudo -E curl https://get.docker.com | sh
```

> WSL DETECTED: We recommend using Docker Desktop for Windows.

※上記のような警告がでますが、ここでは無視してください。

* ユーザーをdockerグループに追加

``` bash
$ sudo usermod -aG docker <username>
```

* docker プロキシ設定  
※ ホスト上からのアクセスは透過プロキシを経由しないため。

``` conf:/etc/default/docker
export no_proxy='127.0.0.1,localhost'
export NO_PROXY="${no_proxy}"
export http_proxy='http://<user_id>:<password>@<host>:<port>'
export HTTP_PROXY="${http_proxy}"
export https_proxy='http://<user_id>:<password>@<host>:<port>'
export HTTPS_PROXY="${https_proxy}"
```

* docker サービス開始

``` bash
$ sudo service docker start
```

> Starting Docker: docker                                         [ OK ]

と表示されればインストール完了です。

## 2. 透過プロキシを設定

wadahiroさんが開発された[go-transproxy](https://github.com/wadahiro/go-transproxy)をここでは利用します。
※この場をお借りしてお礼申し上げます！
基本的にはgithubからダウンロードしてきて展開すればOKで、仕様・起動引数等は、[参考記事#2](https://qiita.com/wadahiro/items/7fa852b7217940e6ef26)を参照ください。

``` bash
$ wget https://github.com/wadahiro/go-transproxy/releases/download/v0.5.2/go-transproxy-0.5.2.tar.gz
$ sudo mkdir /opt/transproxy
$ sudo tar xvzf go-transproxy-0.5.2.tar.gz -C /opt/transproxy/
```

* 透過プロキシの動作確認

``` bash
$ export http_proxy="http://<user_id>:<password>@<host>:<port>"
$ export https_proxy="http://<user_id>:<password>@<host>:<port>"
$ export no_proxy=192.168.0.0/16,172.0.0.0/8

$ sudo -E /opt/transproxy/bin/transproxy -public-dns 8.8.8.8 -tcp-proxy-dports 22
```

``` log
…
[  info ] 2020/11/04 16:24:18.317589 Start listener on :3129  category=HTTP-Proxy
[  info ] 2020/11/04 16:24:18.317664 Start listener on :3130  category=HTTPS-Proxy
[  info ] 2020/11/04 16:24:18.317720 Start listener on :3133  category=Explicit-Proxy(Auth)
[  info ] 2020/11/04 16:24:18.317748 Start listener on :3132  category=Explicit-Proxy(NoAuth)
[  info ] 2020/11/04 16:24:18.317793 All proxy servers started.
[  info ] 2020/11/04 16:24:18.329269 iptables rules inserted as follows.
---
iptables -t nat -I PREROUTING -p tcp -m multiport --dport 22 -j REDIRECT --to-ports 3128
iptables -t nat -I PREROUTING -p tcp --dport 443 -j REDIRECT --to-ports 3130
iptables -t nat -I PREROUTING -p tcp --dport 80 -j REDIRECT --to-ports 3129
iptables -t nat -I PREROUTING -p udp --dport 53 -j REDIRECT --to-ports 3131
iptables -t nat -I PREROUTING -p tcp --dport 53 -j REDIRECT --to-ports 3131
iptables -t nat -I OUTPUT -p tcp -d 8.8.8.8 --dport 53 -j REDIRECT --to-ports 3128
---
```

と表示されて、透過プロキシが開始されます。
ここで別にUbuntuのコンソールを開き下記の通りコンテナ上でwget等を実行すると、プロキシを設定せずに通信が出来るようになります。

```
$ docker run -it --rm alpine:latest /bin/sh
# wget www.google.com
Connecting to www.google.com (172.217.25.100:80)
saving to 'index.html'
index.html           100% |************************************************************************| 14051  0:00:00 ETA
'index.html' saved

# printenv
HOSTNAME=c33b70401601
SHLVL=1
HOME=/root
TERM=xterm
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
PWD=/
```

元のコンソールを確認すると、透過プロキシを経由して通信していることが確認できます。

``` bash
[  info ] 2020/11/04 16:24:31.588214   category=DNS-Proxy  questionName=www.google.com.  questionType=A  remoteAddr=172.17.0.4
[  info ] 2020/11/04 16:24:31.588574   category=TCP-Proxy  host=8.8.8.8:53  method=CONNECT  remoteAddr=192.168.114.143
[  info ] 2020/11/04 16:24:31.588742   category=TCP-Proxy  host=8.8.8.8:53  method=CONNECT  remoteAddr=192.168.114.143
[  info ] 2020/11/04 16:24:32.021183   category=HTTP-Proxy  method=GET  remoteAddr=172.17.0.4  url=http://www.google.com/
```

## 3. Windowsのポートフォワード設定

このままではUbuntu上でdockerが動作しているため、dockerコマンドでポートを公開する設定にしても外部PCからはアクセスできません。
このため外部PCからアクセスする際はホストのWindowsにてWSL2のUbuntuへとポートフォワードするよう設定する必要があります。
新たに管理者権限にてUbuntuアプリを起動し、下記のコマンドを実行します。

```
$ export IP=$(ifconfig eth0 | grep 'inet ' | awk '{print $2}')
$ netsh.exe interface portproxy delete v4tov4 listenport=<port>
$ netsh.exe interface portproxy add    v4tov4 listenport=<port> connectaddress=$IP
```
WSL2のUbuntuは固定IPにできないため1行目でIPアドレスを取得し、2行目以降でnetshコマンドを利用しWindowsに対しポートフォワードの設定を実施しています。
※少しややこしいですが`netsh.exe`はWindows上で実行されています。

## 4. WSL2/Dockerの自動起動設定

WSL2上のdockerは残念ながらWindows開始時に自動起動しません。
このため、Ubuntu上に透過プロキシやdockerを起動するスクリプトを作成し、タスクスケジューラから起動するようにします。

``` shell:/usr/local/bin/init.sh
#!/bin/bash

export http_proxy="http://<user_id>:<password>@<host>:<port>"
export https_proxy="http://<user_id>:<password>@<host>:<port>"
export no_proxy=192.168.0.0/16,172.0.0.0/8

# IP=$(ifconfig eth0 | grep 'inet ' | awk '{print $2}')
# netsh.exe interface portproxy delete v4tov4 listenport=<port>
# netsh.exe interface portproxy add    v4tov4 listenport=<port> connectaddress=$IP

nohup sh -c '/opt/transproxy/bin/transproxy -public-dns 8.8.8.8 -tcp-proxy-dports 22' &>> /tmp/transproxy.log &

service docker start
```
コンテナを外部公開する場合、7～9行目のコメントアウトを外し、ポート毎にポートフォワードをするよう設定してください。
  
またタスクスケジューラに設定する内容は以下の通りです。
wslコマンドを利用し、root権限にて上記スクリプトを実行するようにしています。
ログイン時に起動したい場合等は、トリガータブで修正してください。

* 「全般」タブ
    * 名前：`(好きな名称)`
    * セキュリティオプション
        * タスク実行時に使うユーザーアカウント: `ログインしているアカウント`
        * ユーザーがログオンしているかどうかにかかわらず実行する：`チェック` ※自動起動する場合
        * 最上位の特権で実行する：`チェック`
* 「トリガー」タブ
    * タスクの開始：`スタートアップ時`
* 「操作」タブ
    * 操作：`プログラムの開始`
    * 設定
        * プログラム/スクリプト：`C:\Windows\System32\wsl.exe`
        * 引数の追加：`-u root --exec bash /usr/local/bin/init.sh`  
          ※もし複数のディストリビューションをインストールしている場合は、引数に`-d Ubuntu-18.04`といったようにディストリビューションを指定してください。

## 最後に

Docker Desktopで出来れば自動起動やらポートフォワードの設定やらが不要になってもう少し便利に出来そうですが、WSL2上でCUDAとか動かしたいっていうのがあったので、このような構成になっています。
他に改善事項としては、認証プロキシのパスワードの設定が複数に散らばっているので、Windows上で[認証プロキシ爆発しろ！](https://github.com/ipponshimeji/MAPE)とかを動作させて、もう少し簡略化できるかなとは思っています。
また余裕ができて、いい方法が見つかれば記事更新します。

## 参考記事

1. [Windows Subsystem for Linux (WSL) を Windows 10 にインストールする](https://docs.microsoft.com/ja-jp/windows/wsl/install-win10)
2. [プロキシとの戦いに疲れたのでgoで透過プロキシを作ってみた - Qiita](https://qiita.com/wadahiro/items/7fa852b7217940e6ef26)

## 商標
* Windowsは，米国Microsoft Corporationの米国およびその他の国における登録商標または商標です。
* Ubuntuは，Canonical Ltd.の商標または登録商標です。
* DockerおよびDockerのロゴはDocker,Inc.の米国およびその他の国における商標または登録商標です。
* その他記載の会社名、製品名、サービス名等はそれぞれの会社の商標または登録商標です。