开发环境中有时候想把 Docker 容器实例当做正常的虚拟机来用,换句话说就是本机和Docker容器实例处于同一个子网中,本机可以直接通过IP地址访问Docker容器实例,而不是通过中间端口映射的方式来访问!
Mac和Windows操作系统都无法直接支持Docker,都是通过安装虚拟机的方式来支持Docker功能,Docker Toolbox同时支持Mac和Windows操作系统,实际上Docker Toolbox在本机的作用是安装一套对应的Docker命令可以间接访问虚拟机中真正的Docker宿主机。
在Mac下使用 Docker 可以选择 Docker Toolbox 或 Docker for Mac,Docker for Mac 是 Docker Toolbox 的替代品,但是 Docker for Mac 对网络连通性的支持比较有限,因此目前还是用Docker Toolbox。
最终效果:
- MacBook、VirtualBox、Docker容器实例相互之间可以自由的用IP地址互相访问,就像正常的虚拟机一样。
- 可以给 docker 容器指定静态 IP 地址
- 容器实例使用统一的DNS服务器
缺省情况下安装完Docker Toolbox
的网络配置如下:
Host | IP Address |
---|---|
MacBook( vboxnet1) | 192.168.33.1 |
docker(VirtualBox) | 172.17.0.1 |
c1 | 172.17.0.2 |
c2 | 172.17.0.3 |
说明
-
Docker Toolbox
会自动安装VirtualBox
虚拟机 -
192.168.33.1
是VirtualBox
在 Mac 上绑定的虚拟网卡vboxnet1
的地址$ ifconfig vboxnet1: flags=8843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> mtu 1500 ether 0a:00:27:00:00:01 inet 192.168.33.1 netmask 0xffffff00 broadcast 192.168.33.255
-
c1
和c2
是 Docker 容器实例,采用bridge
模式后自动分配的IP地址是172.17.0.2
和172.17.0.3
-
因为处于不同的网络,所以很显然
MacBook
无法直接访问容器实例c1
和c2
解决方案:
Docker为了安全原因会让Docker容器实例处于不同网段,既然因为是处于不同网段造成的网络不通,那么创建一个新的Docker网络,让它和docker虚拟机处于同一个网段即可。
当然也可以修改
vboxnet1
的IP地址使它和 docker0 处于同一网段,但是这种方式不能给容器指定静态IP地址。docker: Error response from daemon: user specified IP address is supported on user defined networks only.
Docker VM:
如果直接在Linux系统下使用 Docker那么这个Linux就是
Docker VM
,如果是在Windows或Mac操作系统中,这个Docker VM
就是VirtualBox里的虚拟机,Docker Toolbox缺省使用boot2docker
这个Linux系统,当然也可以改用ubuntu
或CentOS
本文以Mac为例,Windows上的操作也类似,因为要安装DNS服务器等其他服务,boot2docker
可能不太够用,另外服务器上最常用的操作系统是Centos
,因此打算把Docker VM
也换成 Centos7
,这样本机还可以同时有一个Centos环境。
最终效果:
- MacBook、VirtualBox、Docker容器实例相互之间可以自由的用IP地址互相访问,就像正常的虚拟机一样。
- Docker VM 换成 Centos7
- Docker VM 同时也是DNS服务器
- 可以给 docker 容器指定静态 IP 地址
- 容器实例使用统一的DNS服务器
最终的混杂网络配置如下:
Host | IP Address |
---|---|
MacBook | 192.168.33.253 |
docker(VirtualBox) | 192.168.33.1 |
c1 | 192.168.33.2 |
c2 | 192.168.33.3 |
说明
-
192.168.33.253 是 VirtualBox 在 Mac 上绑定的虚拟网卡
vboxnet1
的新地址为什么用
192.168.33.253
,因为采用网桥(Bridge)模式后,所有的容器实例没有指定IP地址的时候是从最小IP开始分配的,并且分配的时候并不知道某个IP地址已经被外面的vboxnet1
用了,为了避免容器自动分配的IP地址和外面的vboxnet1
的IP地址冲突,因此尽可能的给vboxnet1
设置比较大的IP地址
- Mac OS X 10.12.6:
- VirtualBox - v5.2.2:虚拟机
- Vagrant - v2.0.1:通过配置文件来快速创建定制的虚拟机
- Docker Toolbox - v1.10.3:Docker 工具箱,包含了 VirtualBox
请首先下载并安装 Vagrant 和 Docker Toolbox,Docker Toolbox中包含了VirtualBox,VirtualBox可以也单独安装最新的版本。
$ VBoxManage hostonlyif ipconfig vboxnet1 --ip 192.168.33.253 --netmask 255.255.255.0
$ ifconfig
vboxnet1: flags=8943<UP,BROADCAST,RUNNING,PROMISC,SIMPLEX,MULTICAST> mtu 1500
ether 0a:00:27:00:00:01
inet 192.168.33.253 netmask 0xffffff00 broadcast 192.168.33.255
注意:这里不能直接用centos/7
,而是用dolbager/centos-7-docker
$ vi Vagrantfile
Vagrant.configure(2) do |config|
config.vm.box = "dolbager/centos-7-docker"
config.vm.hostname = "default"
config.vm.network "private_network", ip: "192.168.33.1", netmask: "255.255.255.0"
config.vm.provider "virtualbox" do |v|
v.name = "default"
v.memory = "2048"
# Change the network adapter type and promiscuous mode
v.customize ['modifyvm', :id, '--nictype1', 'Am79C973']
v.customize ['modifyvm', :id, '--nicpromisc1', 'allow-all']
v.customize ['modifyvm', :id, '--nictype2', 'Am79C973']
v.customize ['modifyvm', :id, '--nicpromisc2', 'allow-all']
end
# Install bridge-utils
config.vm.provision "shell", inline: <<-SHELL
curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo
curl -o /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo
yum clean all
yum makecache
yum update -y
yum install bridge-utils net-tools -y
SHELL
end
配置说明:
-
hostname:
Docker VM 主机的名称=default
-
private_network:
Docker VM 主机的IP=192.168.33.1
-
v.memory
内存2048M
-
bridge-utils
创建网桥需要的辅助工具
根据 Vagrantfile
运行vagrant up
命令创建虚拟机
$ vagrant up
如果下载太慢(它是用 ruby 下载的,速度很慢),也可以用其他方式下载后让入指定目录
-
Mac OS X and Linux:
~/.vagrant.d/boxes
-
Windows:
C:/Users/USERNAME/.vagrant.d/boxes
默认的root
密码是vagrant
你可以修改为其他密码
$ vagrant ssh
Last login: Fri Jan 13 15:50:24 2017 from 10.0.2.2
[vagrant@default ~]$ su root
[root@default vagrant]# passwd
[root@default vagrant]# exit
[vagrant@default ~]$ exit
默认没有把ssh用的private_key
文件复制到.vagrant
目录下,我们可以手动复制一下:
$ vagrant ssh-config
$ scp ~/.vagrant.d/boxes/dolbager-VAGRANTSLASH-centos-7-docker/0.2/virtualbox/vagrant_private_key .vagrant/machines/default/virtualbox/private_key
如果上次创建失败了,则需要先删除上次安装的虚拟机 default
$ docker-machine rm default
然后就是让虚拟机整合 Docker 功能(也就是在虚拟机中安装Docker)了
# Setup the VM as your Docker machine
$ docker-machine create \
--driver "generic" \
--generic-ip-address 192.168.33.1 \
--generic-ssh-user vagrant \
--generic-ssh-key .vagrant/machines/default/virtualbox/private_key \
--generic-ssh-port 22 \
default
-
--driver "generic"
generic表示通用的
docker-machine create --driver "generic" 意思是通过SSH登录到已经存在的主机后,给该主机整合Docker功能,然后据此创建一个docker machine。
-
会出现一个错误提示,不用管它
(default) Couldn't copy SSH public key : unable to copy ssh key: open .vagrant/machines/default/virtualbox/private_key.pub: no such file or directory
ssh-port 22 default
Running pre-create checks...
Creating machine...
(default) Importing SSH key...
(default) Couldn't copy SSH public key : unable to copy ssh key: open .vagrant/machines/default/virtualbox/private_key.pub: no such file or directory
Waiting for machine to be running, this may take a few minutes...
Detecting operating system of created instance...
Waiting for SSH to be available...
Detecting the provisioner...
Provisioning with centos...
Copying certs to the local machine directory...
Copying certs to the remote machine...
Setting Docker configuration on the remote daemon...
Checking connection to Docker...
Docker is up and running!
To see how to connect your Docker Client to the Docker Engine running on this virtual machine, run: docker-machine env default
通过 vagrant
从虚拟机的 eth0
登录到虚拟机
# Log in to the VM via eth0
$ vagrant ssh
Last login: Fri Dec 8 20:04:34 2017 from 10.0.2.2
[vagrant@default ~]$
[vagrant@default ~]$ ip -4 addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UNKNOWN qlen 1000
inet 10.0.2.15/24 brd 10.0.2.255 scope global dynamic eth0
valid_lft 84972sec preferred_lft 84972sec
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UNKNOWN qlen 1000
inet 192.168.33.1/24 brd 192.168.33.255 scope global eth1
valid_lft forever preferred_lft forever
4: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN
inet 172.17.0.1/16 scope global docker0
valid_lft forever preferred_lft forever
我们可以看到
- Docker 在虚拟机内创建了网桥
docker0
- 网桥
docker0
地址范围为172.17.0.1/16
- 网卡
eth1
的IP地址为192.168.33.1/24
我们接下来要做的是在虚拟机中创建一个新的网桥 docker1
,并将虚拟机的eth1
绑定到网桥docker0
上,并且让这些配置在虚拟机重启也有效。
创建 docker network br
# Create network "br" with a bridge name "docker1"
[vagrant@default ~]$ sudo docker network create \
--driver bridge \
--subnet=192.168.33.0/24 \
--gateway=192.168.33.1 \
--opt "com.docker.network.bridge.enable_icc"="true" \
--opt "com.docker.network.bridge.enable_ip_masquerade"="true" \
--opt "com.docker.network.bridge.name"="docker1" \
--opt "com.docker.network.driver.mtu"="1500" \
br
创建网桥配置文件docker1
[vagrant@default ~]$ sudo vi /etc/sysconfig/network-scripts/ifcfg-docker1
DEVICE=docker1
TYPE=Bridge
BOOTPROTO=static
ONBOOT=yes
STP=on
IPADDR=
NETMASK=
GATEWAY=
DNS1=
vi 保存文件并退出:按ESC键 跳到命令模式,然后输入 “:wq” 回车
修改网卡配置 eth1
:
[vagrant@default ~]$ sudo vi /etc/sysconfig/network-scripts/ifcfg-eth1
DEVICE=eth1
BOOTPROTO=static
HWADDR=
ONBOOT=yes
NETMASK=
GATEWAY=
BRIDGE=docker1
TYPE=Ethernet
重启虚拟机
[vagrant@default ~]$ sudo reboot now
通过 vagrant
从虚拟机的 eth0
登录到虚拟机
# Log in to the VM via eth0
$ vagrant ssh
Last login: Fri Dec 8 20:04:34 2017 from 10.0.2.2
[vagrant@default ~]$
[vagrant@default ~]$ ip -4 addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UNKNOWN qlen 1000
inet 10.0.2.15/24 brd 10.0.2.255 scope global dynamic eth0
valid_lft 86106sec preferred_lft 86106sec
4: docker1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP qlen 1000
inet 192.168.33.1/24 scope global docker1
valid_lft forever preferred_lft forever
5: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN
inet 172.17.0.1/16 scope global docker0
valid_lft forever preferred_lft forever
我们可以看到已经起作用了。
然后退出虚拟机,在Mac主机里设置一下环境变量:
$ eval $(docker-machine env default) # Setup the environment
我们就可以测试是否一切正常了。
不指定IP启动容器,IP 将从192.168.33.2
开始逐个递增:
$ docker run -d --net=br --name=c1 nginx
$ docker run -d --net=br --name=c2 nginx
通过可以docker network inspect
命令查看给容器分配的IP地址
$ docker network inspect bridge
"Containers": {
"79e804aa864cde4c919d85ba7a9ce273055dfd827b7216787c849192f46b753d": {
"Name": "c2",
"EndpointID": "b7c1cf0c0169bac07f8dba37152b0ac3515117655e35a948638a59c9e8ddf841",
"MacAddress": "02:42:ac:11:00:03",
"IPv4Address": "192.168.33.3/16",
"IPv6Address": ""
},
"c95ce3c20b8fd96f1fbef513c60f3b2c0d5547f9a325a741cdffda51bf24d048": {
"Name": "c1",
"EndpointID": "e6db861d48f1ec20945af9dcbac6438cbc2c4c3dcdf59cac6c6e74664b6456ce",
"MacAddress": "02:42:ac:11:00:02",
"IPv4Address": "192.168.33.2/16",
"IPv6Address": ""
}
},
都让,你也可以直接指定静态IP地址
$ docker run -d --net=br --name=c6 --ip=192.168.33.6 nginx
然后你应该可以在Mac中直接通过IP访问这些容器实例了:
$ ping -c 3 192.168.33.2
$ ping -c 3 192.168.33.3
$ ping -c 3 192.168.33.6
也可以通过浏览器来确认是否可以访问(第一次访问会稍微需要等一会)
http://192.168.33.2
http://192.168.33.3
http://192.168.33.6
因为安全的原因,Docker容器是不允许映射 /etc/hosts
的,所以只能临时给容器实例增加IP映射或让容器使用统一的 DNS 服务器
$ docker run -it --net=br --add-host zk:192.168.33.2 --name c4 -h c4 busybox cat /etc/hosts
127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
192.168.33.2 zk
192.168.33.3 c4
###方法二:指定DNS服务器
我采用方法二:给虚拟机安装DNS服务器dnsmasq
,并且让所有容器实例缺省指向这个DNS服务器。
通过 vagrant
从虚拟机的 eth0
登录到虚拟机
# Log in to the VM via eth0
$ vagrant ssh
Last login: Fri Dec 8 20:04:34 2017 from 10.0.2.2
[vagrant@default ~]$
安装 dnsmasq
[vagrant@default ~]$ sudo yum -y install dnsmasq
[vagrant@default ~]$ sudo systemctl start dnsmasq
[vagrant@default ~]$ sudo systemctl enable dnsmasq
增加一条记录
[vagrant@default ~]$ sudo vi /etc/hosts
192.168.33.2 zk
修改Docker的启动配置,指定DNS服务器,顺便加上registry-mirrors
[vagrant@default ~]$ sudo vi /etc/docker/daemon.json
{
"dns" : ["192.168.33.1"],
"registry-mirrors" : ["https://fnfrb3qa.mirror.aliyuncs.com"]
}
重启 Docker 虚拟机
[vagrant@default ~]$ sudo systemctl daemon-reload
[vagrant@default ~]$ sudo systemctl restart docker
[vagrant@default ~]$ exit
确认DNS是否工作正常
$ docker run -d --net=br --name c5 busybox top
$ docker exec c5 ping -c 3 zk
PING zk (192.168.33.2): 56 data bytes
64 bytes from 192.168.33.2: seq=0 ttl=64 time=0.018 ms
64 bytes from 192.168.33.2: seq=1 ttl=64 time=0.042 ms
64 bytes from 192.168.33.2: seq=2 ttl=64 time=0.042 ms
Vargrant前面下载了虚拟机镜像文件,如果将来不再需要了闲它占用空间,则可以清理一下
$ vagrant box list
$ vagrant box remove dolbager/centos-7-docker
注意:Docker 好像有个Bug,如果你在自定义网络的Docker容器中启动阶段访问另外一个容器实例,则可能不通。换句话说就是自定义网络启动需要一点时间(大概40秒),需要等dmesg中出现下列消息的时候才能正常访问Bridge中的其他容器实例
[ 1077.847733] docker1: topology change detected, propagating
解决:可以用类似下面的脚本检测服务是否就绪
until nc -z zk 2181; do echo "waiting for zk to be ready"; sleep 0.5; done