やること
- DockerコンテナをDockerホストと同じネットワークに繋ぎます
- DockerコンテナにDockerホストと同じDHCPサーバーからIPアドレスを割り当てます
- Dockerコンテナとその他ホストで、ポートバインディングなしで双方向に通信します
- Dockerコンテナ内でsystemdを動かします
つまり仮想マシンをブリッジ接続したときと同じようなことをDockerコンテナで行います。
これができると、特権ポートである80や443を使うhttpサーバーを、仮想マシンよりも軽量にいくらでも増やせるようになります。
環境
- Dockerホスト:Fedora37
- 物理PC上で動作している
- firewalldとselinuxは無効
- ブローバンドルーターからDHCPでIPアドレスをアサインされている
- Dockerエンジン:Moby-Engine 20.10.21
Moby-Engineは、DockerのOSS版です。chromeに対するchromiumのような位置づけです。
仮想ブリッジ環境構築
まずDockerホスト側で仮想ブリッジを作ります。特にDockerに依存した話ではなく、ここで作成した仮想ブリッジをKVM仮想マシンでも使うことができます。
既存の接続情報を削除
# nmcli connection del 有線接続
ブリッジを追加
# nmcli connection add type bridge autoconnect yes con-name br0 ifname br0
IPv4はDHCP、IPv6は無効
# nmcli connection modify br0 ipv4.method auto
# nmcli connection modify br0 ipv6.method disabled
ブリッジbr0にネットワークインタフェースenp0s3を追加する。enp0s3の実際のイーサーネットデバイスに置き換える
# nmcli connection add type bridge-slave autoconnect yes con-name enp0s3 ifname enp0s3 master br0
Linuxカーネルのbridgeモジュールは、br_netfilterというモジュールを使ってパケットをフィルタしているので、フィルタを無効化する。
br_netfilter
net.bridge.bridge-nf-call-ip6tables=0
net.bridge.bridge-nf-call-iptables=0
net.bridge.bridge-nf-call-arptables=0
設定が終わったら再起動する
# shutdown -r now
再起動後、うまく行っていれば、br0にブロードバンドルーターのDHCPからIPアドレスがアサインされています
よくわからなかったもの
- スパニングツリープロトコル
- イーサーネットフレームがネットワークを循環しないようにするプロトコル
- 仮想ブリッジについて調べると、これを無効にしている記事をよく見かける。今回のような仮想ブリッジの構成では、循環が発生しないので、不要なので無効にしているということか?
- プロミスキャス・モード
- Ethernetデバイスが自身宛のパケット以外も受け取るモード
- 物理デバイスの方で設定が必要そうにも思えるが、今の所設定しなくても動いている。
docker-net-dhcp
ここからDockerコンテナを立ち上げていきます。
dockerエンジンは以下のようにインストールします。
$ sudo dnf install -y moby-engine
$ sudo systemctl enable docker --now
$ sudo usermod -aG docker $USER
$ shutdown -r now
Dockerコンテナのプラグインであるdocker-net-dhcpをインストールします。
# docker-net-dhcpのインストール
$ docker plugin install ghcr.io/devplayer0/docker-net-dhcp:release-linux-amd64
# docker-net-dhcpを使ったDockerネットワークを作成
$ docker network create -d ghcr.io/devplayer0/docker-net-dhcp:release-linux-amd64 --ipam-driver null -o bridge=br0 my-dhcp-net
上記でbridge=br0を指定しています。これでdocker-net-dhcpが、br0を経由してDHCPサーバーにアクセスするようになります。
ネットワークにmy-dhcp-netを指定してnginxコンテナを立ち上げて、そのIPアドレスを取得します。
$ docker run --rm -d --network my-dhcp-net --name nginx nginx
$ docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' nginx
192.168.1.109
ブラウザで、 http://192.168.1.109 を開くとnginxのページが表示されます。
Dockerホスト以外のマシンからでもアクセス可能です。
追記 2024/4/7
最近のdockerでは、docker-net-dhcpはバージョンミスマッチで動かないようです。
修正版があるのでこちらを使えば動きます。
古いdocker-net-dhcpをインストールしている場合は、事前にネットワークとプラグイン削除しておきます(ブリッジは残します)。
$ docker network rm my-dhcp-net
$ docker plugin disable ghcr.io/devplayer0/docker-net-dhcp:release-linux-amd64
$ docker plugin rm ghcr.io/devplayer0/docker-net-dhcp:release-linux-amd64
修正版を使用します
$ git clone git@github.com:Celerway/docker-net-dhcp.git
$ cd docker-net-dhcp
$ make
$ docker network create -d ghcr.io/devplayer0/docker-net-dhcp:golang --ipam-driver null -o bridge=br0 my-dhcp-net
これでネットワークができたので、nginxの動作確認方法は同じです。
$ docker run --rm -d --network my-dhcp-net --name nginx nginx
$ docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' nginx
192.168.1.109
コンテナ内でsystemdを動かす
最後にコンテナ内でsystemdとsshdを動かして、そこにsshログインします。
ここまでくるとDockerコンテナを仮想マシンのように扱えます。
ただし、セキュリティ的によろしくないと言われる、priviledgeモードを使うので、あくまで実験ということで。パラメータによっては、Dockerホスト側にも影響するのでご注意ください。
まずsystemdを含んだコンテナイメージを作ります。
以下のようなDockerfileを作ります。
FROM rockylinux/rockylinux:8.4
ENV container docker
RUN (cd /lib/systemd/system/sysinit.target.wants/; for i in *; do [ $i == \
systemd-tmpfiles-setup.service ] || rm -f $i; done); \
rm -f /lib/systemd/system/multi-user.target.wants/*;\
rm -f /etc/systemd/system/*.wants/*;\
rm -f /lib/systemd/system/local-fs.target.wants/*; \
rm -f /lib/systemd/system/sockets.target.wants/*udev*; \
rm -f /lib/systemd/system/sockets.target.wants/*initctl*; \
rm -f /lib/systemd/system/basic.target.wants/*;\
rm -f /lib/systemd/system/anaconda.target.wants/*;
RUN dnf install -y openssh-server openssh-clients passwd procps-ng iproute
RUN echo "hogehoge" | passwd --stdin root
RUN echo "PermitRootLogin yes" >> /etc/ssh/sshd_config
VOLUME [ "/sys/fs/cgroup" ]
CMD ["/usr/sbin/init"]
上記ではrootのパスワードをhogehogeにして、sshログインできるようにしています。
ビルドして実行します。
$ docker build -t myrocky .
$ docker run --rm -d -v /sys/fs/cgroup:/sys/fs/cgroup:rw --privileged --cgroupns=host --network my-dhcp-net --name myrocky myrocky
$ docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' myrocky
192.168.1.111
$ ssh root@192.168.1.111
参考