Dockerコンテナに外部ネットワークからアクセスできるように環境構築しました(CentOS7)

Dockerによる仮想環境の構築をしてみました。
ラボ環境に、前から色んな用途のサーバーをIP個別にして自由自在に立てられるようにしたいって思ってたんですよ。それならもう仮想化しかないし、そしたらDockerしかないじゃないか! 最近はkubernetesとかが流行ってるみたいだけど、いきなりそこに手を付けるよりかはまず素のDockerを弄ってみようかなと。

Dockerで何をどうしたいのか

Dockerで仮想環境作る一番の目的は「Dockerで立てたコンテナのIPに、外部ネットワークからアクセスできるようにする!」です。

図にするとこんな感じ↓

単純にコンテナ立てるだけならサクッとできると思いますが「外部ネットワークからアクセスできるようにする」ってはネットでもあんまり情報がなくて苦労しました。同じようなこと思ってる人も多いと思うので、記事にまとめておきたいと思います。

Docker構築したベース環境

今回は以下のようなversionで構築してます。

CentOS Linux release 7.6.1810 (Core)  
Linuxカーネル 3.10.0-957.1.3.el7.x86_64
Docker version 1.13.1, build 07f3374/1.13.1

CentOS7や、最新のDockerでの構築記事が少なくて試行錯誤しました……。
それでは構築手順を書いていきましょう。

1.Dockerインストール

下記のサイトを参考にしてDocker環境構築開始。

超簡単! CentOS 7上のDockerでCentOS 7を立ち上げる
https://mseeeen.msen.jp/activate-docker-and-start-centos7/

そしたら超簡単にコンテナ立ち上げまでできたのでびっくりしました。

以下をコピペすればもうインストールとコンテナ立ち上げまで、一気に完了です。
せっかくなので一行一行の説明も追加してます。

yum -y install docker net-tools                                  # dockerとnet-toolsのインストール
systemctl start docker                                           # dockerの起動
systemctl enable docker                                          # サーバー再起動時のdockerの自動起動設定
docker pull centos:centos7                                       # docker hubからcentos7をダウンロード
docker run --privileged -d --name TEST centos:centos7 /sbin/init # コンテナ起動
docker exec -it TEST /bin/bash                                   # コンテナにログイン

ただ、単純に立てるのは簡単なんですが、外部ネットワークからのアクセスを可能にするには、結構な骨が折れました。次からが今回の記事の本題ですね。

2.外部ネットワークからDockerで立てたコンテナのIPにアクセス可能にする

まず参考にしたのがこのサイト。アジャイルさんの記事が無かったら私この環境作れてないです……マジ感謝。

Dockerコンテナをブリッジ接続で使う : アジャイル株式会社
https://www.agilegroup.co.jp/technote/docker-network-in-bridge.html

ただ、こちらの記事は2015年の記事で、DockerのVersionも古く、CentOS6という環境。
記事の通りにはなかなかうまく行かず、結構調べるのに時間かかりました。
それではCentOS7環境で外部ネットワークからのアクセスを可能にするやり方を紹介していきます。

3.Dockerを停止し、docker0を削除

上記を参考にしてここまで進めてきたのであれば、既にDockerが起動状態だと思います。
なので、まずDockerを停止して、docker0という仮想interfaceを削除するようにしましょう。

service docker stop               # Dockerを停止
ip link set dev docker0 down      # Dcokerが生成していたdocker0のinterfaceをdownさせる
brctl delbr docker0               # docker0のinterfaceを削除

4.bridge interfaceの作成

docker0を削除したら、その代わりになるbridge interfaceの作成です。
以下のような感じでifcfg-br0ファイルを作成してみてください。

 vi /etc/sysconfig/network-scripts/ifcfg-br0
 
 [root@server ~]# cat /etc/sysconfig/network-scripts/ifcfg-br0 
 DEVICE=br0
 TYPE=Bridge
 BOOTPROTO=static
 IPADDR=192.168.1.2
 NETMASK=255.255.255.0
 NETWORK=192.168.1.0
 BROADCAST=192.168.1.255
 DELAY=0
 DNS1=192.168.1.1
 GATEWAY=192.168.1.1
 ONBOOT=yes

5.bridge interfaceと物理ポートの関連付け

bridge interfaceを作成したら、物理ポートと関連付けの設定をします。

 vi /etc/sysconfig/network-scripts/ifcfg-enp3s0f0
 
 [root@server ~]# cat /etc/sysconfig/network-scripts/ifcfg-enp3s0f0
 DEVICE="enp3s0f0"
 BOOTPROTO=none
 NM_CONTROLLED="yes"
 ONBOOT="yes"
 TYPE="Ethernet"
 BRIDGE=br0 #ここが物理ポートと関連付けの設定

6.設定反映するためにnetwork restart

上記で作ったbridge interfaceと物理ポートの関連付け設定を有効にするために、network restartをかけましょう。

[root@server ~]# /etc/rc.d/init.d/network restart
Restarting network (via systemctl):                        [  OK  ]

7.iptablesにFORWARDの設定を追加

物理ポートで受信したパケットをコンテナに転送するのに必要な設定っぽいです。
CentOS7なのにiptablesですみません。これでできちゃったので簡便してください。

iptablesの設定ファイルに、FORWARDの設定を一番下の行にでも追加してください。

vi  /etc/sysconfig/iptables
-I FORWARD -m physdev --physdev-is-bridged -j ACCEPT 

追加したら、iptablesを再起動して設定反映しましょう。

[root@server ~]# systemctl restart iptables.service

※もし下記のforwardの設定が有効になってなかったら、下記コマンドで有効にしてあげてください。

 ・ iptablesのforwardの設定を有効・無効にする
有効にする時:echo 1 > /proc/sys/net/ipv4/ip_forward
無効にする時:echo 0 > /proc/sys/net/ipv4/ip_forward

8.DOCKER_OPTSの設定

DOCKER_OPTSは、Dockerが起動する時にbridge interfaceを使用するようにする設定です。
これをやらないと、せっかく作ったbr0が使えません。

docker-networkファイルに、DOCKER_OPTSの設定を追加します。

[root@server ~]# echo 'DOCKER_OPTS="-b=br0"' >> /etc/sysconfig/docker-network

ファイルの中身はこんな感じです。

[root@server ]# cat /etc/sysconfig/docker-network 
# /etc/sysconfig/docker-network
DOCKER_NETWORK_OPTIONS="-b=br0"

ここ、アジャイル株式会社さんの記事だとファイル名が違ってて、結構ハマりましたね……。

CentOS6:/etc/default/docker
CentOS7:/etc/sysconfig/docker-network

OSの違いっていうよりかはDockerのVersionの違いかな?

9.Dockerを起動させて外部ネットワークからのアクセスを可能にする

上記DOCKER_OPTSの設定までできたら、systemctl start dockerで起動すればOKで、
後はコンテナ内のrouteを修正すれば外部からのアクセスはできます。
ただ、それだとコンテナに自動でIPが設定されてしまって、
自分が設定したい好きなIPを自由自在に設定できないのです。

そこで好きなIPを設定する場合は、nsenterコマンドで出来るのですが、手動でやるとえらい面倒。
なので、アジャイル株式会社さんでは起動スクリプトを作成されてました。

しかし、この起動スクリプト、Version違いからのコマンド違いで、そのままだと正常に動かなかったので、
色々調べて頑張って現環境に合わせて作成したのが以下のスクリプトです。

[root@server ~]# cat /root/script/run_docker.sh
#!/bin/bash

usage() {
  echo "usage : $0 [-d] | [-a address] IMAGE NAME" 1>&2
  exit 1
}

while getopts da:h OPT
do
  case $OPT in
    d) DHCP_FLG="true"
       ;;
    a) ADDRESS_FLG="true"
       ADDRESS="$OPTARG"
       ;;
    h) usage
       ;;
    \?) usage
       ;;
  esac
done
shift $(($OPTIND -1))

if [ $# -lt 2 ]; then
  usage
fi

if [ "$ADDRESS_FLG" = "true" ] && [ -z "$ADDRESS" ]; then
  echo specify address
  exit 1
fi

# run docker container from image and get container id
CONTAINER_ID=`docker run -d --privileged --hostname=$2 --name=$2 $1 /sbin/init`
#docker start $CONTAINER_ID

if [ $? -eq 1 ]; then
  exit 1
fi

# get docker pid, ip address (allocated by docker)
PID=`docker inspect --format '{{.State.Pid}}' $CONTAINER_ID`
IPADDR=`docker inspect --format '{{.NetworkSettings.IPAddress}}' $CONTAINER_ID`
IPSUB=`docker inspect --format '{{.NetworkSettings.IPPrefixLen}}' $CONTAINER_ID`

echo -------------------------------------
echo Container ID  : $CONTAINER_ID
echo Container PID : $PID
echo Allocated IP  : $IPADDR/$IPSUB
echo -------------------------------------

# del allocated ip address
nsenter -t $PID -n ip addr del $IPADDR/$IPSUB dev eth0

if [ "$ADDRESS_FLG" = "true" ]; then
  echo set address $ADDRESS
  nsenter -t $PID -n ip addr add $ADDRESS dev eth0
elif [ "$DHCP_FLG" = "true" ]; then
  # get ip address from dhcp
  echo set address from dhcp
  nsenter -t $PID -n -- dhclient eth0
  dhclient -r
fi

# reset default gateway address
nsenter -t $PID -n route add default gw 192.168.1.1 eth0
nsenter -t $PID -n route del -net 192.168.1.0 netmask 255.255.255.0

# print ip addr
nsenter -t $PID -n ip addr

「# reset default gateway address」の部分に関しては、
元のスクリプトだとnetstat -rn を打って各種IPを変数に取り込んで自動設定できるようにコマンド打ってたんですが、
私の環境だと色んなrouteが出てきてしまって、敢え無く固定値を設定するようにしました。
この部分のアドレスは適宜変更してください。

10.スクリプトでdockerコンテナ立ち上げ

上記のスクリプトを作成したら、以下のような感じで使います。

1.イメージファイルの確認

[root@server ~]# docker images
REPOSITORY               TAG                 IMAGE ID            CREATED             SIZE
docker.io/centos         centos7             1e1148e4cc2c        2 months ago        202 MB

2.スクリプト実行

[root@server ~]#  sh /root/script/run_docker.sh -a 192.168.1.10/24 docker.io/centos:centos7 docker-test

3.起動を確認

[root@server ~]# docker ps -a
CONTAINER ID        IMAGE                          COMMAND             CREATED             STATUS                     PORTS               NAMES
e333b8a9e27c        docker.io/centos:centos7   "/sbin/init"        2 minutes ago       Up 2 minutes                                   docker-test

無事起動できました。

11.コンテナにログインして疎通確認

起動したコンテナにログインするコマンドを打つとこんな感じ。

[root@server ~]# docker exec -it docker-test /bin/bash
[root@docker-test /]# 

コンテナから外部ネットワークにPingも飛びます。

[root@docker-test /]# ping 172.16.0.1
PING 172.16.0.1 (172.16.0.1) 56(84) bytes of data.
64 bytes from 172.16.0.1: icmp_seq=1 ttl=253 time=0.802 ms
64 bytes from 172.16.0.1: icmp_seq=2 ttl=253 time=0.855 ms
64 bytes from 172.16.0.1: icmp_seq=3 ttl=253 time=0.868 ms
64 bytes from 172.16.0.1: icmp_seq=4 ttl=253 time=0.778 ms
^C
--- 172.16.0.1 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3000ms
rtt min/avg/max/mdev = 0.778/0.825/0.868/0.051 ms
[root@docker-test /]# 

外部ネットワークのL3SWからもPingが飛びます。

L3SW#ping 192.168.1.10
Type escape sequence to abort.
Sending 5, 100-byte ICMP Echos to 192.168.1.10, timeout is 2 seconds:
!!!!!
Success rate is 100 percent (5/5), round-trip min/avg/max = 1/3/8 ms
L3SW#

これで外部ネットワークからコンテナのIPに疎通が取れ、アクセスできるようになりました。 telnetとかsshとかするには上記以外にも色々設定が必要ですが、ひとまずこれをやれば外部との通信は可能になります。

外部ネットワークからのアクセスを可能にしたいと思ってた方、参考にしてみてください。

12.最後に

尚、この手法にも色々課題があって、アジャイルさんのページにも書いてありますが、
・docker inspect に値が反映されない
というのがあります。
一応やりたいことは全然できてるので問題ないですが、エンジニアとしてはちょっと気持ち悪いですよね……。
商用ネットワークでDockerコンテナに直でアクセスするような場合、どう設定するのがベターなんですかね。
ここらへん詳しい人、もしいましたらこっそり教えてください。