端口映射

如果容器中运行的网络应用需要让外部网络也可以访问,可以通过 -P 或 -p 参数来指定端口映射。
当使用 -P 标记时,Docker 会随机映射一个宿主机的端口到内部容器开放的网络端口。
当使用 -p 标记时,需要指定一个宿主服务器端口到内部容器中需要映射的端口。
如:
后台运行一个容器并且随机分配端口 运行niginx:

  1. ~ docker run -P -d nginx
  2. 95a7a265d8316001038e19c09eda1f37ae5d257e50c42ce989558e009760470a
  3. ~ docker ps
  4. CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
  5. 95a7a265d831 nginx "nginx -g 'daemon of…" 7 seconds ago Up 5 seconds 0.0.0.0:32772->80/tcp elegant_faraday

图片.png

后台运行一个 Tomcat 容器并且把容器 8080 端口映射到宿主机器上的 8010 端口,这样外部可以通过 8010 端口访问到该容器服务程序。

  1. docker run -d -p 8010:8080 tomcat:7.0

图片.png

网络类型

none

故名思议,none 网络就是什么都没有的网络。挂在这个网络下的容器除了 lo,没有其他任何网卡。容器创建时,可以通过 —network=none 指定使用 none 网络。

  1. $ docker run -it --rm --network=none busybox
  2. / # ifcongig
  3. sh: ifcongig: not found
  4. / # ifconfig
  5. lo Link encap:Local Loopback
  6. inet addr:127.0.0.1 Mask:255.0.0.0
  7. UP LOOPBACK RUNNING MTU:65536 Metric:1
  8. RX packets:8 errors:0 dropped:0 overruns:0 frame:0
  9. TX packets:8 errors:0 dropped:0 overruns:0 carrier:0
  10. collisions:0 txqueuelen:1000
  11. RX bytes:672 (672.0 B) TX bytes:672 (672.0 B)
  12. / # ping 39.156.69.79
  13. PING 39.156.69.79 (39.156.69.79): 56 data bytes
  14. ping: sendto: Network is unreachable

host

连接到 host 网络的容器共享 Docker host 的网络栈,容器的网络配置与 host 完全一样。可以通过 —network=host 指定使用 host 网络。使用 host 网络,容器和宿主机共用 host 网络,当启动服务时应该避免端口冲突。

  1. $ docker run -it --network=host alpine /bin/sh
  2. / # ipaddr show docker0
  3. 6: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN
  4. link/ether 02:42:ee:e3:fa:e7 brd ff:ff:ff:ff:ff:ff
  5. inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
  6. valid_lft forever preferred_lft forever
  7. / #

创建Mysql

  1. # docker run --name mysql -e MYSQL_ROOT_PASSWORD=123456 --network=host -d mysql:5.7

查看本地端口映射

  1. # netstat -lnp|grep :3306
  2. tcp6 0 0 :::3306 :::* LISTEN 7866/mysqld

创建nginx

  1. # docker run -itd --network=host --name nginx nginx

查看端口占用

  1. netstat -lnp|grep :80
  2. tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 10771/nginx: master

Bridge

Docker Daemon启动后默认创建 docker0 网桥,起初 docker0 启动时网桥上没有任何端口,

  1. $ ip a
  2. 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
  3. link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
  4. inet 127.0.0.1/8 scope host lo
  5. valid_lft forever preferred_lft forever
  6. 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
  7. link/ether 52:54:00:95:a9:db brd ff:ff:ff:ff:ff:ff
  8. inet 172.17.0.11/20 brd 172.17.15.255 scope global eth0
  9. valid_lft forever preferred_lft forever
  10. inet6 fe80::5054:ff:fe95:a9db/64 scope link
  11. valid_lft forever preferred_lft forever
  12. 3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
  13. link/ether 02:42:88:02:b6:91 brd ff:ff:ff:ff:ff:ff
  14. inet 172.18.0.1/16 brd 172.18.255.255 scope global docker0
  15. valid_lft forever preferred_lft forever

当启动容器后,docker0 网桥上也创建了一个端口,他跟容器内的 eth0 网卡形成一个 veth pair。

  1. # docker run -it -d alpine /bin/sh
  2. 865774dcdcb9867baac182b1775bd28fb528a6cb85bc1b6cca19791957e87d73
  3. # docker ps
  4. CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
  5. 865774dcdcb9 alpine "/bin/sh" 9 seconds ago Up 7 seconds distracted_volhard

查看当前容器的ip地址信息

  1. # docker exec -it 865774dcdcb9 /bin/sh
  2. / # ip a
  3. 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1000
  4. link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
  5. inet 127.0.0.1/8 scope host lo
  6. valid_lft forever preferred_lft forever
  7. 14: eth0@if15: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP
  8. link/ether 02:42:ac:12:00:02 brd ff:ff:ff:ff:ff:ff
  9. inet 172.18.0.2/16 brd 172.18.255.255 scope global eth0
  10. valid_lft forever preferred_lft forever
  11. / #

再次查看当前宿主机上的ip地址变化

  1. # ip a
  2. 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
  3. link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
  4. inet 127.0.0.1/8 scope host lo
  5. valid_lft forever preferred_lft forever
  6. 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
  7. link/ether 52:54:00:95:a9:db brd ff:ff:ff:ff:ff:ff
  8. inet 172.17.0.11/20 brd 172.17.15.255 scope global eth0
  9. valid_lft forever preferred_lft forever
  10. inet6 fe80::5054:ff:fe95:a9db/64 scope link
  11. valid_lft forever preferred_lft forever
  12. 3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
  13. link/ether 02:42:88:02:b6:91 brd ff:ff:ff:ff:ff:ff
  14. inet 172.18.0.1/16 brd 172.18.255.255 scope global docker0
  15. valid_lft forever preferred_lft forever
  16. 15: veth8e50a21@if14: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default
  17. link/ether 96:ba:da:b0:fd:9e brd ff:ff:ff:ff:ff:ff link-netnsid 0

那么如何证明veth8e50a21是挂在docker0上的呢?我们需要安装一个工具

  1. # apt install -y bridge-utils

安装完成后,执行

  1. # brctl show
  2. bridge name bridge id STP enabled interfaces
  3. docker0 8000.02428802b691 no veth8e50a21

Docker 桥接网络拓扑图如下:
Docker—— Network - 图4

当创建一个 Docker 容器的时候,同时会创建了一对 veth pair 接口(当数据包发送到一个接口时,另外一个接口也可以收到相同的数据包)。这对接口一端在容器内,即 eth0;另一端在本地并被挂载到 docker0 网桥,名称以 veth 开头(例如 vethAQI2QT)。通过这种方式,主机可以跟容器通信,容器之间也可以相互通信。Docker 就创建了在主机和所有容器之间一个虚拟共享网络。
图片.png
查看网络bridg情况

  1. # docker network inspect bridge
  2. [
  3. {
  4. "Name": "bridge",
  5. "Id": "36ffc7a66bd5793a596158cf29e73b5d3e4f31ba5f9bdda64e87c0b70cb5159b",
  6. "Created": "2019-10-22T15:43:39.113178165+08:00",
  7. "Scope": "local",
  8. "Driver": "bridge",
  9. "EnableIPv6": false,
  10. "IPAM": {
  11. "Driver": "default",
  12. "Options": null,
  13. "Config": [
  14. {
  15. "Subnet": "172.18.0.0/16"
  16. }
  17. ]
  18. },
  19. "Internal": false,
  20. "Attachable": false,
  21. "Ingress": false,
  22. "ConfigFrom": {
  23. "Network": ""
  24. },
  25. "ConfigOnly": false,
  26. "Containers": {
  27. "865774dcdcb9867baac182b1775bd28fb528a6cb85bc1b6cca19791957e87d73": {
  28. "Name": "distracted_volhard",
  29. "EndpointID": "b5550359f1c5571564d73e94a2f2f68608fe0b966161e8f2e886dc5514111e1b",
  30. "MacAddress": "02:42:ac:12:00:02",
  31. "IPv4Address": "172.18.0.2/16",
  32. "IPv6Address": ""
  33. }
  34. },
  35. "Options": {
  36. "com.docker.network.bridge.default_bridge": "true",
  37. "com.docker.network.bridge.enable_icc": "true",
  38. "com.docker.network.bridge.enable_ip_masquerade": "true",
  39. "com.docker.network.bridge.host_binding_ipv4": "0.0.0.0",
  40. "com.docker.network.bridge.name": "docker0",
  41. "com.docker.network.driver.mtu": "1500"
  42. },
  43. "Labels": {}
  44. }
  45. ]

自定义网络

查看网络

  1. # docker network ls
  2. NETWORK ID NAME DRIVER SCOPE
  3. 36ffc7a66bd5 bridge bridge local
  4. 419568f09c61 host host local
  5. 54cdbd2cc4f6 none null local

创建网络

  1. docker network create -d bridge my-net

创建自定义网络时 设置子网段

  1. 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分别指定网段和网关。

  1. docker network inspect my_net
  2. [
  3. {
  4. "Name": "my_net",
  5. "Id": "c60c0b105a1c6bb021657f5646f3a621574532d5893ca6a7217258ea5db40692",
  6. "Created": "2019-10-24T18:43:37.888512005+08:00",
  7. "Scope": "local",
  8. "Driver": "bridge",
  9. "EnableIPv6": false,
  10. "IPAM": {
  11. "Driver": "default",
  12. "Options": {},
  13. "Config": [
  14. {
  15. "Subnet": "172.10.0.0/24",
  16. "Gateway": "172.10.0.1"
  17. }
  18. ]
  19. },
  20. "Internal": false,
  21. "Attachable": false,
  22. "Ingress": false,
  23. "ConfigFrom": {
  24. "Network": ""
  25. },
  26. "ConfigOnly": false,
  27. "Containers": {},
  28. "Options": {},
  29. "Labels": {}
  30. }
  31. ]

会得到此网络的配置信息,my_net是刚刚创建的网络名称,如果为bridge就是查看docker创建的默认bridge网络信息。
每创建一个自定义网络便会在宿主机中创建一个网桥(docker0是创建的默认网桥,其实原理是一致的,而且也是对等的。)。名字为br-<网络短ID>,可以通过brctl show命令查看全部网桥信息。
在宿主机上也会有

  1. 168: br-c60c0b105a1c: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
  2. link/ether 02:42:52:90:d6:64 brd ff:ff:ff:ff:ff:ff
  3. inet 172.10.0.1/24 brd 172.10.0.255 scope global br-c60c0b105a1c
  4. valid_lft forever preferred_lft forever

通过以下命令为容器指定自定义网络:

  1. $ docker run -it --network my_net --ip 172.10.0.3 busybox
  2. / # ip a
  3. 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000
  4. link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
  5. inet 127.0.0.1/8 scope host lo
  6. valid_lft forever preferred_lft forever
  7. 169: eth0@if170: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue
  8. link/ether 02:42:ac:0a:00:03 brd ff:ff:ff:ff:ff:ff
  9. inet 172.10.0.3/24 brd 172.10.0.255 scope global eth0
  10. valid_lft forever preferred_lft forever
  11. / #

其实这与使用docker默认网络是一致的,都是添加–network参数参数,此处也添加了–ip参数来指定容器的ip地址。

连接容器到用户自定义网桥

创建新容器时可以指定一个或多个 --network 标志。下面的例子将 Nginx 容器连接到 my-net 网络。同时还将容器的 80 端口发布到 Docker 主机的 8080 端口,这样外部的客户端就可以访问这个端口了。任何其他连接到 my-net 的网络的容器都可以访问这个网络中其他容器的所有端口,反之亦然。

  1. $ docker create --name my-nginx \
  2. --network my-net \
  3. --publish 8080:80 \
  4. nginx:latest

使用 docker network connect 命令将运行中的容器连接到已经存在的用户自定义网桥。下面的命令将运行中的 my-nginx 容器连接到已经存在的 my-net 网络:

  1. $ docker network connect my-net my-nginx

断开自定义网络的连接

使用 docker network disconnect 命令断开运行中的容器到一个用户自定义网桥的连接。下面的命令将会断开 my-nginx 容器到 my-net 网络的连接:

  1. $ docker network disconnect my-net my-nginx

容器互联

Link

  1. $docker run --name mysql -d -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 mysql:5.7
  2. $ docker ps
  3. CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
  4. c3038d682ca1 mysql:5.7 "docker-entrypoint.s…" 8 seconds ago Up 5 seconds 0.0.0.0:3306->3306/tcp, 33060/tcp mysql

创建student 数据库

  1. mycli -uroot -hlocalhost
  2. Password:
  3. Version: 1.8.1
  4. Chat: https://gitter.im/dbcli/mycli
  5. Mail: https://groups.google.com/forum/#!forum/mycli-users
  6. Home: http://mycli.net
  7. Thanks to the contributor - www.mysqlfanboy.com
  8. mysql root@localhost:(none)> CREATE DATABASE student
  9. Query OK, 1 row affected
  10. Time: 0.000s

创建

  1. package main
  2. import (
  3. "fmt"
  4. _ "github.com/go-sql-driver/mysql"
  5. "github.com/jinzhu/gorm"
  6. )
  7. type Student struct {
  8. gorm.Model
  9. Name string
  10. Score uint
  11. }
  12. func main() {
  13. // 注意 需要提前创建数据库create DATABASE student mysql的docker是 mysql
  14. db, err := gorm.Open("mysql", "root:123456@tcp(mysql:3306)/student?charset=utf8&parseTime=True&loc=Local")
  15. if err != nil {
  16. fmt.Println(err.Error())
  17. panic("failed to connect database")
  18. }
  19. defer db.Close()
  20. // Migrate the schema
  21. db.AutoMigrate(&Student{})
  22. // create
  23. db.Create(&Student{Name: "tony", Score: 90})
  24. // read
  25. var s Student
  26. db.First(&s, 1)
  27. // update
  28. db.Model(&s).Update("score", 100)
  29. select {}
  30. }

创建Dockerfile 文件

  1. FROM golang:latest as build
  2. WORKDIR /go/src/github.com/baxiang/mysql-go
  3. COPY . .
  4. RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go install -v
  5. FROM scratch
  6. WORKDIR /app
  7. COPY --from=build /go/bin/mysql-go .
  8. CMD ["./mysql-go"]

创建 baxiang/mysql-go 的镜像

  1. $ docker build ./ -t baxiang/mysql-go

创建容器

  1. docker run --name mysql-go --link mysql baxiang/mysql-go

最终执行结果

  1. mycli -uroot -hlocalhost
  2. Password:
  3. Version: 1.8.1
  4. Chat: https://gitter.im/dbcli/mycli
  5. Mail: https://groups.google.com/forum/#!forum/mycli-users
  6. Home: http://mycli.net
  7. Thanks to the contributor - Jonathan Slenders
  8. mysql root@localhost:(none)> use student
  9. You are now connected to database "student" as user "root"
  10. Time: 0.002s
  11. mysql root@localhost:student> select * from students
  12. +------+---------------------+---------------------+--------------+--------+---------+
  13. | id | created_at | updated_at | deleted_at | name | score |
  14. |------+---------------------+---------------------+--------------+--------+---------|
  15. | 1 | 2019-10-24 08:39:13 | 2019-10-24 08:39:13 | <null> | tony | 100 |
  16. +------+---------------------+---------------------+--------------+--------+---------+
  17. 1 row in set
  18. Time: 0.002s
  19. mysql root@localhost:student>

纯粹的通过容器名来打开容器间的网络通道缺乏一定的灵活性,在 Docker 里还支持连接时使用别名来使我们摆脱容器名的限制。

  1. $docker run --name mysql-go --link mysql:mysqlDB baxiang/mysql-go

在这里,我们使用 --link <name>:<alias> 的形式,连接到 MySQL 容器,并设置它的别名为 mysqlDB。当我们要在 Web 应用中使用 MySQL 连接时,我们就可以使用 mysqlDB 来代替连接地址了。

  1. db, err := gorm.Open("mysql", "root:123456@tcp(mysqlDB:3306)/student?charset=utf8&parseTime=True&loc=Local")
  2. if err != nil {
  3. fmt.Println(err.Error())
  4. panic("failed to connect database")
  5. }
  6. defer db.Close()

自定义容器网络

  1. $ docker network create mysql-net

创建连接容器

  1. $ docker run --name mysql -d -e MYSQL_ROOT_PASSWORD=123456 --network mysql-net mysql:5.7
  1. docker exec -it mysql mysql -p
  2. Enter password:
  3. Welcome to the MySQL monitor. Commands end with ; or \g.
  4. Your MySQL connection id is 2
  5. Server version: 5.7.28 MySQL Community Server (GPL)
  6. Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved.
  7. Oracle is a registered trademark of Oracle Corporation and/or its
  8. affiliates. Other names may be trademarks of their respective
  9. owners.
  10. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
  11. mysql> show databases;
  12. +--------------------+
  13. | Database |
  14. +--------------------+
  15. | information_schema |
  16. | mysql |
  17. | performance_schema |
  18. | sys |
  19. +--------------------+
  20. 4 rows in set (0.00 sec)
  21. mysql> create database student;
  22. Query OK, 1 row affected (0.00 sec)

打开新的终端,再运行一个容器并加入到 mysql-net 网络

  1. $ docker run --name mysql-go --network mysql-net baxiang/mysql-go

再次查看当前数据库数据情况 会看到当前数据执行的结果

  1. 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是使得两个容器共用同一个网络。
修改代码

  1. // 注意 需要提前创建数据库create DATABASE student mysql的docker是 mysql
  2. db, err := gorm.Open("mysql", "root:123456@tcp(127.0.0.1:3306)/student?charset=utf8&parseTime=True&loc=Local")
  3. if err != nil {
  4. fmt.Println(err.Error())
  5. panic("failed to connect database")
  6. }
  7. defer db.Close()
  1. docker run --name mysql-go --network=container:mysql -d baxiang/mysql-go

当然也可以直接设置 —network=host 但是需要myql 容器在宿主机上暴露3306端口

Connect

connect 主要解决不同网络下的通信,同一个网络(默认网络或者自定义网络)下的容器之间是能ping通的,但是不同网络之间的容器由于网络独立性的要求是无法ping通的。原因是iptables-save DROP掉了docker之间的网络,大概如下:

  1. -A DOCKER-ISOLATION -i docker0 -o br-ac4fe2d72b18 -j DROP
  2. -A DOCKER-ISOLATION -i br-ac4fe2d72b18 -o docker0 -j DROP
  3. -A DOCKER-ISOLATION -i br-62f17c363f02 -o br-ac4fe2d72b18 -j DROP
  4. -A DOCKER-ISOLATION -i br-ac4fe2d72b18 -o br-62f17c363f02 -j DROP
  5. -A DOCKER-ISOLATION -i br-62f17c363f02 -o docker0 -j DROP
  6. -A DOCKER-ISOLATION -i docker0 -o br-62f17c363f02 -j DROP

如果是实体机我们很容易理解,只需要为其中一台服务器添加一块网卡连接到另一个网络就可以了。容器同理,只需要为其中一个容器添加另外一个容器的网络就可以了。使用如下命令:

  1. 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方式运行:

  1. $ docker run -d --name nginx $PWD:/etc/nginx -p 80:80 -p 443:443 nginx:1.15

需要代理的API服务运行在宿主机的 8080端口, nginx.conf 相关配置如下:

  1. server {
  2. ...
  3. location /api {
  4. proxy_pass http://localhost:8080
  5. }
  6. ...
  7. }

由于nginx是运行在docker容器中的,这个 localhost 是容器的localhost,而不是宿主机的localhost。
容器中访问到宿主机的网络:
使用宿主机IP
使用host网络
Docker容器运行的默认是 bridge ,即桥接网络,以桥接模式连接到宿主机; host 是宿主网络,即与宿主机共用网络; 当容器使用 host 网络时,容器中可以直接访问宿主机网络,那么容器的 localhost 就是宿主机的 localhost 。在docker中使用 —network host 来为容器配置 host 网络:

  1. $ docker run -d --name nginx --network host nginx

容器与宿主机共用了网络,容器中暴露端口等同于宿主机暴露端口。使用host网络不需要修改 nginx.conf
**

参考文章

https://juejin.im/post/5b6c26946fb9a04fbf273c66
https://istone.dev/2018/07/02/Docker-Network/