Docker学习笔记(三):网络和数据卷


网络

之前有个web程序的例子已经介绍过了端口, 这里说下网络

列出网络

每个Docker引擎默认都会包含三个默认的网络, 我们可以这样查看:

$ docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
5fa52100eeb6        bridge              bridge              local
e3b7da4f14e9        host                host                local
6ddc3eda6788        none                null                local
  • NAMEbridge的网络比较特殊, 新运行的容器默认都是在这个网络中, 除非指定是用别的网络

查看网络信息

$ docker run -itd --name=networktest ubuntu
408cf6d9f25b9757c5d5fdfcd8c9a7133c7b888095c02b7b85d46f8df404b847
$ docker network inspect bridge

我们会得到类似于下面的描述

[
    {
        "Name": "bridge",
        "Id": "5fa52100eeb64d58d5ec97b2f2267c78d700265d310030b0bc61e203367172d2",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
                {
                    "Subnet": "172.17.0.0/16",
                    "Gateway": "172.17.0.1"
                }
            ]
        },
        "Internal": false,
        "Containers": {
            "408cf6d9f25b9757c5d5fdfcd8c9a7133c7b888095c02b7b85d46f8df404b847": {
                "Name": "networktest",
                "EndpointID": "87625c710a97b0e40dd79119d5002edd4c44f162ff1c2d4b85d9c87b0f43968e",
                "MacAddress": "02:42:ac:11:00:02",
                "IPv4Address": "172.17.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 inspect --format='{ {json .NetworkSettings.Networks}}' networktest
{"bridge":{"IPAMConfig":null,"Links":null,"Aliases":null,"NetworkID":"5fa52100eeb64d58d5ec97b2f2267c78d700265d310030b0bc61e203367172d2","EndpointID":"87625c710a97b0e40dd79119d5002edd4c44f162ff1c2d4b85d9c87b0f43968e","Gateway":"172.17.0.1","IPAddress":"172.17.0.2","IPPrefixLen":16,"IPv6Gateway":"","GlobalIPv6Address":"","GlobalIPv6PrefixLen":0,"MacAddress":"02:42:ac:11:00:02"}}
# 直接查看IP地址
$ docker inspect --format='{ {range .NetworkSettings.Networks}}{ {.IPAddress}}{ {end}}' networktest
172.17.0.2

断开网络

$ docker network disconnect bridge networktest

networktest这个容器从bridge网络中断开, 再次查看IP地址就没没有ip了

当然也可以重新连接

$ docker network connect bridge networktest

创建网络

我们可以这样创建一个bridge网络

$ docker network create -d bridge my-bridge
  • -d: driver value, 可以不写, 默认就是bridge; 最后面跟自己的driver名字

使用自定义网络创建容器

在运行容器的时候可以通过传递参数--network=my-bridge来指定使用那个网络, 不传默认使用bridge

花括号之间没有空格!!

$ docker run -d --network=my-bridge --name db training/postgres
$ docker run -d --name web training/webapp python app.py
# db 使用的网络是 my-bridge
$ docker inspect --format='{ {range .NetworkSettings.Networks}}{ {.IPAddress}}{ {end}}' db
172.18.0.2
# web 使用的网络是 bridge
shisong$ docker inspect --format='{ {range .NetworkSettings.Networks}}{ {.IPAddress}}{ {end}}' web
172.17.0.2

我们可以打开容器db的终端, ping一下看看网是否通

$ docker exec -it db bash
root@822d3512fe8d:/# ping 172.17.0.2
PING 172.18.0.2 (172.18.0.2) 56(84) bytes of data.
64 bytes from 172.18.0.2: icmp_seq=1 ttl=64 time=0.114 ms
64 bytes from 172.18.0.2: icmp_seq=2 ttl=64 time=0.070 ms
^C
--- 172.18.0.2 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1006ms

如果我们把web容器也连接到my-bridge网络中, 则网络就通了

$ docker network connect my-bridge web
# 查看新的IP地址, 刚才的网络没断开, 所以会看到有两个网络, 两个IP地址(新的是172.18.0.3)
$ docker inspect web
$ docker exec -it db bash
root@822d3512fe8d:/# ping 172.18.0.3
PING 172.18.0.3 (172.18.0.3) 56(84) bytes of data.
64 bytes from 172.18.0.3: icmp_seq=1 ttl=64 time=0.298 ms
64 bytes from 172.18.0.3: icmp_seq=2 ttl=64 time=0.112 ms
^C
--- 172.18.0.3 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 999ms

数据卷

数据卷(data volume)类似于linux系统中的挂载点

添加数据卷

我们在使用docker createdocker run时可以通过-v flag来添加数据卷, 甚至可以多次使用-v来添加多个数据卷

$ docker run -d -P --name web -v /webapp training/webapp python app.py

上面是挂载了一个数据卷到容器的/webapp目录(必须是绝对路径), /webapp不存在则创建, 存在则覆盖(但不会删除原有的, 卸载时恢复原有的)

通过docker inspect web命令来查看添加数据卷的信息, 会有一段如下:

...
"Mounts":[
    {
        "Name":"e30f9d5c1c8ffe1d02c8f1d758c5b0663c73ba46e8469e1e6ee14a9e5ee1784a",
        "Source":"/var/lib/docker/volumes/e30f9d5c1c8ffe1d02c8f1d758c5b0663c73ba46e8469e1e6ee14a9e5ee1784a/_data",
        "Destination":"/webapp",
        "Driver":"local",
        "Mode":"",
        "RW":true,
        "Propagation":""
    }
]
...

其中的Source就是我们挂载的数据卷,Destination是我们挂载到容器中的位置,RW是否可读写, 默认就是可读写

挂载本地目录

我们可以挂载本地目录, 也可以指定是只读还是可读写模式, -v后面的格式为[local_path:]container-dir[:ro], 除了上面的那种形式, 还可以像下面这样:

$ docker run -d -P --name web -v /src/webapp:/opt/webapp training/webapp python app.py
$ docker run -d -P --name web -v /src/webapp:/opt/webapp:ro training/webapp python app.py

不过在OS Xwindows中, Docker Engine只有/Users (OS X)C:\Users (Windows)的访问权限, 所以只能这样使用了:

# os x
$ docker run -v /Users/<path>:/<container path> ...
# windows
$ docker run -v /c/Users/<path>:/<container path> ...

另外, 还可以挂载某个名字的数据卷, Docker会创建一个给定名字的数据卷, 如:

$ docker run -d -P --name web -v foo:/opt/webapp:ro training/webapp python app.py

通过docker inspect web得到的描述如下:

...
"Mounts": [
    {
        "Name": "foo",
        "Source": "/var/lib/docker/volumes/foo/_data",
        "Destination": "/opt/webapp",
        "Driver": "local",
        "Mode": "ro",
        "RW": false,
        "Propagation": "rprivate"
    }
]
...

挂载文件

我们可以挂载某个文件到容器中:

$ docker run --rm -it -v ~/.bash_history:/root/.bash_history ubuntu /bin/bash
  • --rm: 容器退出时自动删除容器

上面就挂载了bash_history到容器中, 在容器里查看到原来本地机上的历史命令, 当退出容器的时候, 本地机会有刚才在容器里使用过的命令的历史.
不过需要注意的是, 若是挂在了文件, 在容器里使用vi等编辑该挂在文件时, 会引起错误. 因此挂载这个文件的父目录是更好的选择.

挂载容器中的数据卷

挂载数据卷, 可以是匿名的也可以是命名的, 可以挂载本地目录也可以挂载本地文件, 还能挂载另一个容器中的数据卷. 我们可以通过参数--volumes-from指定数据卷来自于哪个容器, 可以多次使用--volumes-from来挂载多个数据卷

# 先创建一个叫 dbstore 的容器, 这个容器添加了数据卷
$ docker create -v /dbdata --name dbstore training/postgres /bin/true
# 创建容器 db1, 指明数据卷来自于 dbstore
$ docker run -d --volumes-from dbstore --name db1 training/postgres
# 创建容器 db2, 指明数据卷来自于 db1
$ docker run -d --volumes-from db1 --name db2 training/postgres

查看删除数据卷

上面例子中, 删除挂载数据卷的容器, 并不会删除挂载的数据卷, 除非在删除最后一个引用该数据卷的容器时指定-v标记

# 删除容器不会删除数据卷
$ docker rm db1
# 添加 -v 由于不是最后一个引用数据卷的容器, 所以数据卷仍没被删除
$ docker rm -v dbstore
# 删除最后一个引用数据卷的容器时,添加 -v , 数据卷被删除
$ docker rm -v db2

如果删除了最后一个引用数据卷的容器时没有加-v, 将会出现dangling状态(悬挂,没人用了)的volume.

volume有匿名和命名之分, 我们可以在创建容器时通过参数--rm指定, 当容器删除时, 对应的所有匿名volume也随着删除

$ docker run --rm -v /foo -v awesome:/bar --name test ubuntu

我们通过-v指定了一个匿名volume, 挂载到容器的/foo下, 还指定了一个命名为awesomevolume挂在到了/bar下, 容器运行完自动删除镜像(--rm)时, 匿名数据卷会被删除,而awesome并没有被删除.

另外我们可以通过 docker volume命令来查看管理所有的数据卷

# 查看所有 volume
$ docker volume ls
# 查看 dangling 的 volume
$ docker volume ls -f dangling=true
# 删除 volume
$ docker volume rm <volume name>

volume的其他用法

我们可以使用数据卷进行数据的备份、存储以及迁移等工作

$ docker run --rm --volumes-from dbstore -v $(pwd):/backup ubuntu tar cvf /backup/backup.tar /dbdata

dbstore挂载了一个数据卷, 并且把本地目录挂在到了/backup下, 最后传递了一个tar命令, 把dbdata volume备份到/backup目录下的backup.tar文件里. 命令执行完, 容器停止并被自动移除了, 但是dbdata volume的数据被备份了.

然后我们可以创建一个新容器, 添加了一个匿名的volume在容器的/dbdata目录下:

$ docker run -v /dbdata --name dbstore2 ubuntu /bin/bash

下面我们可以把刚才的备份文件解压到dbstore2volume中, 从而达到转移的目的

$ docker run --rm --volumes-from dbstore2 -v $(pwd):/backup ubuntu bash -c "cd /dbdata && tar xvf /backup/backup.tar --strip 1"