端口映射
如果容器中运行的网络应用需要让外部网络也可以访问,可以通过 -P 或 -p 参数来指定端口映射。
当使用 -P 标记时,Docker 会随机映射一个宿主机的端口到内部容器开放的网络端口。
当使用 -p 标记时,需要指定一个宿主服务器端口到内部容器中需要映射的端口。
如:
后台运行一个容器并且随机分配端口 运行niginx:
➜ ~ docker run -P -d nginx
95a7a265d8316001038e19c09eda1f37ae5d257e50c42ce989558e009760470a
➜ ~ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
95a7a265d831 nginx "nginx -g 'daemon of…" 7 seconds ago Up 5 seconds 0.0.0.0:32772->80/tcp elegant_faraday
后台运行一个 Tomcat 容器并且把容器 8080 端口映射到宿主机器上的 8010 端口,这样外部可以通过 8010 端口访问到该容器服务程序。
docker run -d -p 8010:8080 tomcat:7.0
网络类型
none
故名思议,none 网络就是什么都没有的网络。挂在这个网络下的容器除了 lo,没有其他任何网卡。容器创建时,可以通过 —network=none 指定使用 none 网络。
$ docker run -it --rm --network=none busybox
/ # ifcongig
sh: ifcongig: not found
/ # ifconfig
lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
UP LOOPBACK RUNNING MTU:65536 Metric:1
RX packets:8 errors:0 dropped:0 overruns:0 frame:0
TX packets:8 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:672 (672.0 B) TX bytes:672 (672.0 B)
/ # ping 39.156.69.79
PING 39.156.69.79 (39.156.69.79): 56 data bytes
ping: sendto: Network is unreachable
host
连接到 host 网络的容器共享 Docker host 的网络栈,容器的网络配置与 host 完全一样。可以通过 —network=host 指定使用 host 网络。使用 host 网络,容器和宿主机共用 host 网络,当启动服务时应该避免端口冲突。
$ docker run -it --network=host alpine /bin/sh
/ # ipaddr show docker0
6: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN
link/ether 02:42:ee:e3:fa:e7 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
valid_lft forever preferred_lft forever
/ #
创建Mysql
# docker run --name mysql -e MYSQL_ROOT_PASSWORD=123456 --network=host -d mysql:5.7
查看本地端口映射
# netstat -lnp|grep :3306
tcp6 0 0 :::3306 :::* LISTEN 7866/mysqld
创建nginx
# docker run -itd --network=host --name nginx nginx
查看端口占用
netstat -lnp|grep :80
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 10771/nginx: master
Bridge
Docker Daemon启动后默认创建 docker0 网桥,起初 docker0 启动时网桥上没有任何端口,
$ ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
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 fq_codel state UP group default qlen 1000
link/ether 52:54:00:95:a9:db brd ff:ff:ff:ff:ff:ff
inet 172.17.0.11/20 brd 172.17.15.255 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::5054:ff:fe95:a9db/64 scope link
valid_lft forever preferred_lft forever
3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
link/ether 02:42:88:02:b6:91 brd ff:ff:ff:ff:ff:ff
inet 172.18.0.1/16 brd 172.18.255.255 scope global docker0
valid_lft forever preferred_lft forever
当启动容器后,docker0 网桥上也创建了一个端口,他跟容器内的 eth0 网卡形成一个 veth pair。
# docker run -it -d alpine /bin/sh
865774dcdcb9867baac182b1775bd28fb528a6cb85bc1b6cca19791957e87d73
# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
865774dcdcb9 alpine "/bin/sh" 9 seconds ago Up 7 seconds distracted_volhard
查看当前容器的ip地址信息
# docker exec -it 865774dcdcb9 /bin/sh
/ # ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
14: eth0@if15: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP
link/ether 02:42:ac:12:00:02 brd ff:ff:ff:ff:ff:ff
inet 172.18.0.2/16 brd 172.18.255.255 scope global eth0
valid_lft forever preferred_lft forever
/ #
再次查看当前宿主机上的ip地址变化
# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
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 fq_codel state UP group default qlen 1000
link/ether 52:54:00:95:a9:db brd ff:ff:ff:ff:ff:ff
inet 172.17.0.11/20 brd 172.17.15.255 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::5054:ff:fe95:a9db/64 scope link
valid_lft forever preferred_lft forever
3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:88:02:b6:91 brd ff:ff:ff:ff:ff:ff
inet 172.18.0.1/16 brd 172.18.255.255 scope global docker0
valid_lft forever preferred_lft forever
15: veth8e50a21@if14: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default
link/ether 96:ba:da:b0:fd:9e brd ff:ff:ff:ff:ff:ff link-netnsid 0
那么如何证明veth8e50a21是挂在docker0
上的呢?我们需要安装一个工具
# apt install -y bridge-utils
安装完成后,执行
# brctl show
bridge name bridge id STP enabled interfaces
docker0 8000.02428802b691 no veth8e50a21
当创建一个 Docker 容器的时候,同时会创建了一对 veth pair
接口(当数据包发送到一个接口时,另外一个接口也可以收到相同的数据包)。这对接口一端在容器内,即 eth0
;另一端在本地并被挂载到 docker0
网桥,名称以 veth
开头(例如 vethAQI2QT
)。通过这种方式,主机可以跟容器通信,容器之间也可以相互通信。Docker 就创建了在主机和所有容器之间一个虚拟共享网络。
查看网络bridg情况
# docker network inspect bridge
[
{
"Name": "bridge",
"Id": "36ffc7a66bd5793a596158cf29e73b5d3e4f31ba5f9bdda64e87c0b70cb5159b",
"Created": "2019-10-22T15:43:39.113178165+08:00",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": null,
"Config": [
{
"Subnet": "172.18.0.0/16"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {
"865774dcdcb9867baac182b1775bd28fb528a6cb85bc1b6cca19791957e87d73": {
"Name": "distracted_volhard",
"EndpointID": "b5550359f1c5571564d73e94a2f2f68608fe0b966161e8f2e886dc5514111e1b",
"MacAddress": "02:42:ac:12:00:02",
"IPv4Address": "172.18.0.2/16",
"IPv6Address": ""
}
},
"Options": {
"com.docker.network.bridge.default_bridge": "true",
"com.docker.network.bridge.enable_icc": "true",
"com.docker.network.bridge.enable_ip_masquerade": "true",
"com.docker.network.bridge.host_binding_ipv4": "0.0.0.0",
"com.docker.network.bridge.name": "docker0",
"com.docker.network.driver.mtu": "1500"
},
"Labels": {}
}
]
自定义网络
查看网络
# docker network ls
NETWORK ID NAME DRIVER SCOPE
36ffc7a66bd5 bridge bridge local
419568f09c61 host host local
54cdbd2cc4f6 none null local
创建网络
docker network create -d bridge my-net
创建自定义网络时 设置子网段
docker network create -d bridge --subnet 172.10.0.0/24 --gateway 172.10.0.1 my_net
-d bridge表示自定义网络的驱动为bridge,–subnet 172.10.0.0/24 –gateway 172.10.0.1分别指定网段和网关。
docker network inspect my_net
[
{
"Name": "my_net",
"Id": "c60c0b105a1c6bb021657f5646f3a621574532d5893ca6a7217258ea5db40692",
"Created": "2019-10-24T18:43:37.888512005+08:00",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": {},
"Config": [
{
"Subnet": "172.10.0.0/24",
"Gateway": "172.10.0.1"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {},
"Options": {},
"Labels": {}
}
]
会得到此网络的配置信息,my_net是刚刚创建的网络名称,如果为bridge就是查看docker创建的默认bridge网络信息。
每创建一个自定义网络便会在宿主机中创建一个网桥(docker0是创建的默认网桥,其实原理是一致的,而且也是对等的。)。名字为br-<网络短ID>,可以通过brctl show命令查看全部网桥信息。
在宿主机上也会有
168: br-c60c0b105a1c: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
link/ether 02:42:52:90:d6:64 brd ff:ff:ff:ff:ff:ff
inet 172.10.0.1/24 brd 172.10.0.255 scope global br-c60c0b105a1c
valid_lft forever preferred_lft forever
通过以下命令为容器指定自定义网络:
$ docker run -it --network my_net --ip 172.10.0.3 busybox
/ # ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
169: eth0@if170: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue
link/ether 02:42:ac:0a:00:03 brd ff:ff:ff:ff:ff:ff
inet 172.10.0.3/24 brd 172.10.0.255 scope global eth0
valid_lft forever preferred_lft forever
/ #
其实这与使用docker默认网络是一致的,都是添加–network参数参数,此处也添加了–ip参数来指定容器的ip地址。
连接容器到用户自定义网桥
创建新容器时可以指定一个或多个 --network
标志。下面的例子将 Nginx 容器连接到 my-net
网络。同时还将容器的 80 端口发布到 Docker 主机的 8080 端口,这样外部的客户端就可以访问这个端口了。任何其他连接到 my-net
的网络的容器都可以访问这个网络中其他容器的所有端口,反之亦然。
$ docker create --name my-nginx \
--network my-net \
--publish 8080:80 \
nginx:latest
使用 docker network connect
命令将运行中的容器连接到已经存在的用户自定义网桥。下面的命令将运行中的 my-nginx 容器连接到已经存在的 my-net 网络:
$ docker network connect my-net my-nginx
断开自定义网络的连接
使用 docker network disconnect
命令断开运行中的容器到一个用户自定义网桥的连接。下面的命令将会断开 my-nginx 容器到 my-net 网络的连接:
$ docker network disconnect my-net my-nginx
容器互联
Link
$docker run --name mysql -d -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 mysql:5.7
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
c3038d682ca1 mysql:5.7 "docker-entrypoint.s…" 8 seconds ago Up 5 seconds 0.0.0.0:3306->3306/tcp, 33060/tcp mysql
创建student 数据库
mycli -uroot -hlocalhost
Password:
Version: 1.8.1
Chat: https://gitter.im/dbcli/mycli
Mail: https://groups.google.com/forum/#!forum/mycli-users
Home: http://mycli.net
Thanks to the contributor - www.mysqlfanboy.com
mysql root@localhost:(none)> CREATE DATABASE student
Query OK, 1 row affected
Time: 0.000s
创建
package main
import (
"fmt"
_ "github.com/go-sql-driver/mysql"
"github.com/jinzhu/gorm"
)
type Student struct {
gorm.Model
Name string
Score uint
}
func main() {
// 注意 需要提前创建数据库create DATABASE student mysql的docker是 mysql
db, err := gorm.Open("mysql", "root:123456@tcp(mysql:3306)/student?charset=utf8&parseTime=True&loc=Local")
if err != nil {
fmt.Println(err.Error())
panic("failed to connect database")
}
defer db.Close()
// Migrate the schema
db.AutoMigrate(&Student{})
// create
db.Create(&Student{Name: "tony", Score: 90})
// read
var s Student
db.First(&s, 1)
// update
db.Model(&s).Update("score", 100)
select {}
}
创建Dockerfile 文件
FROM golang:latest as build
WORKDIR /go/src/github.com/baxiang/mysql-go
COPY . .
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go install -v
FROM scratch
WORKDIR /app
COPY --from=build /go/bin/mysql-go .
CMD ["./mysql-go"]
创建 baxiang/mysql-go 的镜像
$ docker build ./ -t baxiang/mysql-go
创建容器
docker run --name mysql-go --link mysql baxiang/mysql-go
最终执行结果
mycli -uroot -hlocalhost
Password:
Version: 1.8.1
Chat: https://gitter.im/dbcli/mycli
Mail: https://groups.google.com/forum/#!forum/mycli-users
Home: http://mycli.net
Thanks to the contributor - Jonathan Slenders
mysql root@localhost:(none)> use student
You are now connected to database "student" as user "root"
Time: 0.002s
mysql root@localhost:student> select * from students
+------+---------------------+---------------------+--------------+--------+---------+
| id | created_at | updated_at | deleted_at | name | score |
|------+---------------------+---------------------+--------------+--------+---------|
| 1 | 2019-10-24 08:39:13 | 2019-10-24 08:39:13 | <null> | tony | 100 |
+------+---------------------+---------------------+--------------+--------+---------+
1 row in set
Time: 0.002s
mysql root@localhost:student>
纯粹的通过容器名来打开容器间的网络通道缺乏一定的灵活性,在 Docker 里还支持连接时使用别名来使我们摆脱容器名的限制。
$docker run --name mysql-go --link mysql:mysqlDB baxiang/mysql-go
在这里,我们使用 --link <name>:<alias>
的形式,连接到 MySQL 容器,并设置它的别名为 mysqlDB。当我们要在 Web 应用中使用 MySQL 连接时,我们就可以使用 mysqlDB 来代替连接地址了。
db, err := gorm.Open("mysql", "root:123456@tcp(mysqlDB:3306)/student?charset=utf8&parseTime=True&loc=Local")
if err != nil {
fmt.Println(err.Error())
panic("failed to connect database")
}
defer db.Close()
自定义容器网络
$ docker network create mysql-net
创建连接容器
$ docker run --name mysql -d -e MYSQL_ROOT_PASSWORD=123456 --network mysql-net mysql:5.7
docker exec -it mysql mysql -p
Enter password:
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 2
Server version: 5.7.28 MySQL Community Server (GPL)
Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| sys |
+--------------------+
4 rows in set (0.00 sec)
mysql> create database student;
Query OK, 1 row affected (0.00 sec)
打开新的终端,再运行一个容器并加入到 mysql-net 网络
$ docker run --name mysql-go --network mysql-net baxiang/mysql-go
再次查看当前数据库数据情况 会看到当前数据执行的结果
docker exec -it mysql mysql -p
从 Docker 1.10 版本开始,docker daemon 实现了一个内嵌的 DNS server,使容器可以直接通过“容器名”通信。方法很简单,只要在启动时用 —name 为容器命名就可以了。
因此 在mysql 可以直接使用容器的名字直接当做域名地址访问
joined 容器
joined 容器是另一种实现容器间通信的方式。joined
容器非常特别,它可以使两个或多个容器共享一个网络栈,共享网卡和配置信息,joined容器之间可以通过127.0.0.1直接通信。host网络使得容器与宿主机共用同一个网络,而jointed是使得两个容器共用同一个网络。
修改代码
// 注意 需要提前创建数据库create DATABASE student mysql的docker是 mysql
db, err := gorm.Open("mysql", "root:123456@tcp(127.0.0.1:3306)/student?charset=utf8&parseTime=True&loc=Local")
if err != nil {
fmt.Println(err.Error())
panic("failed to connect database")
}
defer db.Close()
docker run --name mysql-go --network=container:mysql -d baxiang/mysql-go
当然也可以直接设置 —network=host 但是需要myql 容器在宿主机上暴露3306端口
Connect
connect 主要解决不同网络下的通信,同一个网络(默认网络或者自定义网络)下的容器之间是能ping通的,但是不同网络之间的容器由于网络独立性的要求是无法ping通的。原因是iptables-save DROP掉了docker之间的网络,大概如下:
-A DOCKER-ISOLATION -i docker0 -o br-ac4fe2d72b18 -j DROP
-A DOCKER-ISOLATION -i br-ac4fe2d72b18 -o docker0 -j DROP
-A DOCKER-ISOLATION -i br-62f17c363f02 -o br-ac4fe2d72b18 -j DROP
-A DOCKER-ISOLATION -i br-ac4fe2d72b18 -o br-62f17c363f02 -j DROP
-A DOCKER-ISOLATION -i br-62f17c363f02 -o docker0 -j DROP
-A DOCKER-ISOLATION -i docker0 -o br-62f17c363f02 -j DROP
如果是实体机我们很容易理解,只需要为其中一台服务器添加一块网卡连接到另一个网络就可以了。容器同理,只需要为其中一个容器添加另外一个容器的网络就可以了。使用如下命令:
docker network connect my_net httpd
connect命令能够为httpd容器再添加一个my_net网络(假设httpd原来只有默认的bridge网络)。这样上面创建的busybox容器就能与此次connect的httpd容器进行通信。
配置Overlay网络
可以参考 https://istone.dev/2018/07/02/Docker-Network/
容器访问宿主机网络
使用nginx作反向代理,其中nginx是使用docker方式运行:
$ docker run -d --name nginx $PWD:/etc/nginx -p 80:80 -p 443:443 nginx:1.15
需要代理的API服务运行在宿主机的 8080端口, nginx.conf 相关配置如下:
server {
...
location /api {
proxy_pass http://localhost:8080
}
...
}
由于nginx是运行在docker容器中的,这个 localhost 是容器的localhost,而不是宿主机的localhost。
容器中访问到宿主机的网络:
使用宿主机IP
使用host网络
Docker容器运行的默认是 bridge ,即桥接网络,以桥接模式连接到宿主机; host 是宿主网络,即与宿主机共用网络; 当容器使用 host 网络时,容器中可以直接访问宿主机网络,那么容器的 localhost 就是宿主机的 localhost 。在docker中使用 —network host 来为容器配置 host 网络:
$ docker run -d --name nginx --network host nginx
容器与宿主机共用了网络,容器中暴露端口等同于宿主机暴露端口。使用host网络不需要修改 nginx.conf
**
参考文章
https://juejin.im/post/5b6c26946fb9a04fbf273c66
https://istone.dev/2018/07/02/Docker-Network/