孙宇的技术专栏 大数据/机器学习

Docker 虚拟化

2016-02-18

阅读:


各种虚拟机技术开启了云计算时代;而Docker,作为新一代虚拟化技术,正在改变我们开发、测试、部署应用的方式。开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的Linux机器上,也可以实现虚拟化。

Docker容器不是虚拟机

虚拟机

我们将多个应用使用虚拟机分别部署时,逻辑结构如下:

从下到上理解上图:

  1. 基础设施(Infrastructure)。它可以是你的个人电脑,数据中心的服务器,或者是云主机。
  2. 主操作系统(Host Operating System)。你的个人电脑之上,运行的可能是MacOS,Windows或者某个Linux发行版。
  3. 虚拟机管理系统(Hypervisor)。利用Hypervisor,可以在主操作系统之上运行多个不同的从操作系统。类型1的Hypervisor有支持MacOS的HyperKit,支持Windows的Hyper-V以及支持Linux的KVM。类型2的Hypervisor有VirtualBox和VMWare。
  4. 从操作系统(Guest Operating System)。假设你需要运行3个相互隔离的应用,则需要使用Hypervisor启动3个从操作系统,也就是3个虚拟机。这些虚拟机都非常大,也许有700MB,这就意味着它们将占用2.1GB的磁盘空间。更糟糕的是,它们还会消耗很多CPU和内存。
  5. 各种依赖。每一个从操作系统都需要安装许多依赖。如果你的的应用需要连接PostgreSQL的话,则需要安装libpq-dev;如果你使用Ruby的话,应该需要安装gems;如果使用其他编程语言,比如Python或者Node.js,都会需要安装对应的依赖库。
  6. 应用。安装依赖之后,就可以在各个从操作系统分别运行应用了,这样各个应用就是相互隔离的。

Docker

使用Docker容器运行多个相互隔离的应用时,如下图:

从下到上理解图上的组件分别是:

  1. 基础设施(Infrastructure)。它可以是你的个人电脑,数据中心的服务器,或者是云主机。
  2. 主操作系统(Host Operating System)。所有主流的Linux发行版都可以运行Docker。对于MacOS和Windows,也有一些办法”运行”Docker。
  3. Docker守护进程(Docker Daemon)。Docker守护进程取代了Hypervisor,它是运行在操作系统之上的后台进程,负责管理Docker容器。
  4. 各种依赖。对于Docker,应用的所有依赖都打包在Docker镜像中,Docker容器是基于Docker镜像创建的。
  5. 应用。应用的源代码与它的依赖都打包在Docker镜像中,不同的应用需要不同的Docker镜像。不同的应用运行在不同的Docker容器中,它们是相互隔离的。

Docker守护进程可以直接与主操作系统进行通信,为各个Docker容器分配资源;它还可以将容器与主操作系统隔离,并将各个容器互相隔离。

由于没有臃肿的从操作系统,Docker可以节省大量的磁盘空间以及其他系统资源。

Docker通常用于隔离不同的应用,例如前端,后端以及数据库。或者用来发布SOA架构中的各个微服务。

安装Docker

Docker 运行在 CentOS 7.X. 及以上的版本(只针对Centos 来说) Docker 需要 64 位系统. 并且 kernel 版本至少是 3.10

检查 kernel 版本可以用如下命令:

$ uname -r
3.10.0-229.el7.x86_64

有两种方法安装Docker 引擎。一种是通过 yum 方式;另一种是通过下载源码安装。

yum 安装

1.先以 root 身份登录或者登录后 sudo 到 root,并保证系统当前是最新的

yum update

2.添加 yum 库

tee /etc/yum.repos.d/docker.repo <<-'EOF'
[dockerrepo]
name=Docker Repository
baseurl=https://yum.dockerproject.org/repo/main/centos/$releasever/
enabled=1
gpgcheck=1
gpgkey=https://yum.dockerproject.org/gpg
EOF

3.安装 Docker 包

yum install docker-engine

4.启动 Docker daemon.

service docker start

5.验证是否安装正确 启动一个测试镜像

docker run hello-world
Unable to find image 'hello-world:latest' locally
    latest: Pulling from hello-world
    a8219747be10: Pull complete
    91c95931e552: Already exists
    hello-world:latest: The image you are pulling has been verified. Important: image verification is a tech preview feature and should not be relied on to provide security.
    Digest: sha256:aa03e5d0d5553b4c3473e89c8619cf79df368babd1.7.1cf5daeb82aab55838d
    Status: Downloaded newer image for hello-world:latest
    Hello from Docker.
    This message shows that your installation appears to be working correctly.

    To generate this message, Docker took the following steps:
     1. The Docker client contacted the Docker daemon.
     2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
            (Assuming it was not already locally available.)
     3. The Docker daemon created a new container from that image which runs the
            executable that produces the output you are currently reading.
     4. The Docker daemon streamed that output to the Docker client, which sent it
            to your terminal.

    To try something more ambitious, you can run an Ubuntu container with:
     $ docker run -it ubuntu bash

    For more examples and ideas, visit:
     http://docs.docker.com/userguide/

源码脚本安装

1.确保拥有 root 权限并保证所有的包都已更新

yum update

2.下载并安装

curl -fsSL https://get.docker.com/ | sh

该脚本会自动安装 Docker

3.启动 Docker

service docker start

4.验证是否安装成功 运行测试镜像

docker run hello-world

创建 docker 组

Docker 不是通过开辟TCP端口来进行的,而是通过一个 Unix socket 文件,而这个文件通常是属于 root,所以 Docker 通常是以 root 用户来执行。

为了避免每次运行的时候都要 sudo,可以建一个 docker 的用户组,把其它用户添加进去。

注意: docker 组里的用户和 root 等效

创建组并添加用户:

1.登录系统,并且必须要有 sudo 权限

2.创建 docker 组并添加用户

sudo usermod -aG docker your_username

3.退出登录并再重新登录

保证用户权限的更新

4.验证运行权限

docker run hello-world

开机自启动 — 为保证 docker 服务自动启动可以如下:

sudo chkconfig docker on

卸载

列出已经安装的包

yum list installed | grep docker
docker-engine.x86_64   1.7.1-1.el7 @/docker-engine-1.7.1-1.el7.x86_64.rpm

移除安装

sudo yum -y remove docker-engine.x86_64

这个命令不会删除 docker 的镜像,配置文件等,如果要这样,可以如下:

rm -rf /var/lib/docker

使用

hello world

docker run ubuntu /bin/echo 'Hello world'
Hello world

上面命令的执行过程如下:

  1. 首先我们指出要执行 docker 命令来运行一个镜像 (run 参数)
  2. 然后,我们指出要运行的镜像名:ubuntu.
  3. 当我们指定镜像名后,Docker 会先在我们主机上找该名称的镜像,如果未找到,会从公开的镜像库里找: https://hub.docker.com/
  4. 最后,在启动的容器里执行:
    /bin/echo 'Hello world'
    

    命令行中会显示:

Hello world

当 hello world 显示后,容器就停止了。

可交互式容器

有了上面的基础 ,我们可以尝试在容器中执行更多的脚本:

$ docker run -t -i ubuntu /bin/bash
root@af8bae53bdd3:/#

在这里,我们在容器启动后执行命令时,加入了 -t 和 -i 参数。

-t 在容器中开启伪终端
-i 允许我们和容器的标准输出交互

于是,当容器启动后我们就可以在宿主机器上和容器提供的输入通道里进行交互:

root@af8bae53bdd3:/#

往容器里输入一些 sh 命令:

root@af8bae53bdd3:/# pwd
/
root@af8bae53bdd3:/# ls
bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var

当交互完成时,可以输入 exit命令或按 Ctrl-D 结束交互。

root@af8bae53bdd3:/# exit

当退出后,相当于 /bin/bash 脚本结束了。于是,容器关闭了。

守护 Hello world 容器

前者的容器都是在命令执行完后自动退出。我们也可以创建一直运行的容器。当然正常线上使用时肯定是要一直运行。

$ docker run -d ubuntu /bin/sh -c "while true; do echo hello world; sleep 1; done"
1e5535038e285177d5214659a068137486f96ee5c2e85a4ac52dc83f2ebe4147

-d 参数可以让容器一直在后台运行

同时,执行后面一段 sh 命令,每秒钟输出一次 hello world

但容器启动后并没有输出 hello world 而是返回一长串字符,这串字符就是容器的唯一ID。

我们可以通过容器的ID来观察容器里的情况。 首先要确认容器正在运行,可以通过 ps命令查看当前正在运行的容器。

$ docker ps
CONTAINER ID  IMAGE         COMMAND               CREATED        STATUS       PORTS NAMES
1e5535038e28  ubuntu  /bin/sh -c 'while tr  2 minutes ago  Up 1 minute        insane_babbage

在这里可以看到所有在后台运行的容器。第一列的 CONTAINER ID 是前面说的容器ID 的简写:1e5535038e28. 同时可以看到我们的容器是正在运行的状态,以及一个自动加上的 PORTS NAMES: insane_babbage.

对于已经启动的容器,可以使用 docker logs 命令查看容器内的情况:

docker logs insane_babbage
hello world
hello world
hello world
. . .

docker logs命令返回容器的标准输出。所以上面就返回了 hello world

主动关闭容器

docker stop insane_babbage
insane_babbage

docker stop命令告诉 Docker 停止正在运行的容器。如果成功了,就会返回被停止容器的名称。

停止后,再查看当前正在运行的窗口:

$ docker ps
CONTAINER ID  IMAGE         COMMAND               CREATED        STATUS       PORTS NAMES

没有正在运行的了,说明停止成功

WEB服务容器

运行WEB服务

如果我们要运行一个 phthon 的WEB服务,可以如下:

docker run -d -P training/webapp python app.py

-d 是让容器在后台运行

-P 告诉 Docker 和宿主机器做一个端口映射,这样我们就可以在宿主机上观察容器里的 WEB 应用了

窗容器启动后,会运行 python app.py

通过 docker ps 命令观察当前运行的容器:

docker ps -l
CONTAINER ID  IMAGE                   COMMAND       CREATED        STATUS        PORTS                    NAMES
bc533791f3f5  training/webapp:latest  python app.py 5 seconds ago  Up 2 seconds  0.0.0.0:49155->5000/tcp  nostalgic_morse

-l 参数可以列出容器详细的信息

默认情况下 ps 命令只会列出正在运行的容器,如果想列出所有的,则要加 -a 参数 同时还可以看到加了 -P参数后的端口映射情况:

PORTS
0.0.0.0:49155->5000/tcp

现在,容器的 5000 端口和宿主机的 49155 端口连接。上面我们只是单纯的加上了 -P 参数,它实际上是 -p 5000 的简写。

意思是和容器里的 5000 端口作 映射。(宿主机上的端口通常是在 32768 至 61000 之间) .我们也可以指定自己需要的端口,如:

docker run -d -p 80:5000 training/webapp python app.py

所以,现在我们可以通过访问宿主机的 49155 端口来访问容器中的应用,如: http://your_domain:49155/

查看WEB 应用日志

如果我们想要更详细的了解WEB应用的情况,可以通过 docker logs 命令:

docker logs -f nostalgic_morse
* Running on http://0.0.0.0:5000/
10.0.2.2 - - [23/May/2014 20:16:31] "GET / HTTP/1.1" 200 -
10.0.2.2 - - [23/May/2014 20:16:31] "GET /favicon.ico HTTP/1.1" 404 -

这里加入了 -f 参数。它会让 docker logs 命令执行类似 tail -f 的功能,去访问标准输出。

查看容器运行的进程

可以使用docker top 命令。

docker top nostalgic_morse
PID                 USER                COMMAND
854                 root                python app.py

检查应用容器 — 我们可以通过 docker inspect 命令来获取一些容器信息:

docker inspect nostalgic_morse

得到的返回大致如下:


[{
    "ID": "bc533791f3f500b280a9626688bc79e342e3ea0d528efe3a86a51ecb28ea20",
    "Created": "2014-05-26T05:52:40.808952951Z",
    "Path": "python",
    "Args": [
       "app.py"
    ],
    "Config": {
       "Hostname": "bc533791f3f5",
       "Domainname": "",
       "User": "",
. . .

我们也可以指定返回哪些信息,如返回容器IP:

docker inspect -f '' nostalgic_morse
172.17.0.5

关闭容器

docker stop nostalgic_morse

重启容器

关闭容器后,如果要重新启动,有两种选择:重新创建一个容器或者重新启动旧的。使用旧的方式如下:

docker start nostalgic_morse

移除容器

docker rm nostalgic_morse

Error: Impossible to remove a running container, please stop it first or use -f
2014/05/24 08:12:56 Error: failed to remove one or more containers

删除的时候报错了,因为不能删除一个正在运行的容器。要先停止后再删除。

docker stop nostalgic_morse
nostalgic_morse
docker rm nostalgic_morse
nostalgic_morse

创建自定义镜像

docker 镜像是容器的基础。每次运行容器时都必须要拥有镜像。

查看当前主机上的镜像

$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
ubuntu              14.04               1d073211c498        3 days ago          187.9 MB
busybox             latest              2c5ac3f849df        5 days ago          1.113 MB
training/webapp     latest              54bb4e8718e8        5 months ago        348.7 MB

一个镜像可以有多个版本,如 ubuntu 可以有 10.04, 12.04, 12.10, 13.04, 13.10 等。在启动容器里,如果要指定某个名称对应的某个版本,可以如下:

ubuntu:14.04
$ docker run -t -i ubuntu:14.04 /bin/bash

如果未指定具体的版本,而又存在多个版本,默认会使用:ubuntu:latest

注意: 最好是每次都指定版本,这样就可以避免不必要的麻烦

下载镜像

当我们的主机上不存在我们要启动的镜像时,它会自动从公开的库中下载。但这会需要很长时间,因为要下载。如果想在启动容器前就预先下载好镜像,可以用 docker pull 命令。如:

$ docker pull centos
Pulling repository centos
b7de3133ff98: Pulling dependent layers
5cc9e91966f7: Pulling fs layer
511136ea3c5a: Download complete
ef52fb1fe610: Download complete
. . .

Status: Downloaded newer image for centos

可以看到,镜像已经被下载了,但未启动容器。 下面我们可以利用这个镜像去启动容器:

$ docker run -t -i centos /bin/bash
bash-4.1#

搜索外网镜像

有许多人会把自己的镜像公开出来给大家用。我们可以到 Docker Hub 上去找, 或者通过 docker search 命令查找,如:

$ docker search sinatra
NAME                                   DESCRIPTION                                     STARS     OFFICIAL   AUTOMATED
training/sinatra                       Sinatra training image                          0                    [OK]
marceldegraaf/sinatra                  Sinatra test app                                0
mattwarren/docker-sinatra-demo                                                         0                    [OK]
luisbebop/docker-sinatra-hello-world                                                   0                    [OK]
bmorearty/handson-sinatra              handson-ruby + Sinatra for Hands on with D...   0
subwiz/sinatra                                                                         0
bmorearty/sinatra                                                                      0
. . .

该命令返回了许多可以使用的镜像。可以用 pull 命令下载。

$ docker pull training/sinatra

创建自己的镜像

直接下载的镜像可能和自己的业务需求不相符,我们需要做许多调整。调整完后,我们可以将镜像重新上传,更新镜像。或者使用 Dockerfile 创建新的镜像。

更新镜像

在更新镜像之前,必须新建一个容器:

$ docker run -t -i training/sinatra /bin/bash
root@0b2616b0e5a8:/#

注意: 新创建的容器ID会返回回来,后面会用到: 0b2616b0e5a8

上面已经打开了和容器的交互通道。接下来要在容器中运行:

root@0b2616b0e5a8:/# gem install json

命令执行完后,再执行 exit退出交互。

现在,我们已经在容器中做了简单的变更。我们可以通过 docker commit 命令将该容器作为一个镜像提交:

$ docker commit -m "Added json gem" -a "Kate Smith" \
0b2616b0e5a8 ouruser/sinatra:v2
4f177bd27a9ff0f6dc2a830403925b5360bfe0b93d476f7fc3231110e7f71b1c

-m 参数是指相应的描述,注释

-a 参数是指更新镜像的作者

同时,命令还描述了新的镜像是从哪个容器生成的,以及需要更新哪个镜像:

ouruser/sinatra:v2

这时候再次查看当前机器上的镜像:

$ docker images
REPOSITORY          TAG     IMAGE ID       CREATED       SIZE
training/sinatra    latest  5bc342fa0b91   10 hours ago  446.7 MB
ouruser/sinatra     v2      3c59e02ddd1a   10 hours ago  446.7 MB
ouruser/sinatra     latest  5db5f8471261   10 hours ago  446.7 MB

通过该镜像,可以再新建容器:

$ docker run -t -i ouruser/sinatra:v2 /bin/bash
root@78e82f680994:/#

Dockerfile 创建镜像

通过 commit command 可以快速的扩展镜像。但在开发团队中共享却不是很方便,这里我们可以使用新命令:docker build , 创建新的镜像。 在此之前,必须新建一个 Dockerfile, 来告诉 Docker 怎么去创建镜像。

新建目录和 Dockerfile.

$ mkdir sinatra
$ cd sinatra
$ touch Dockerfile

文件内容如下:

# This is a comment
FROM ubuntu:14.04
MAINTAINER Kate Smith <ksmith@example.com>
RUN apt-get update && apt-get install -y ruby ruby-dev
RUN gem install sinatra

如示例所写,每个项的名称必须大写。并且使用 # 来添加注释

第一个描述: FROM 告诉 Docker 从哪里新建镜像:Ubuntu 14.04

MAINTAINER 描述了当前创建镜像人的信息

最后,通过RUN,执行一些内部命令,安装必要的环境

接下来新建镜像:

$ docker build -t ouruser/sinatra:v2 .
Sending build context to Docker daemon 2.048 kB
Sending build context to Docker daemon
Step 1 : FROM ubuntu:14.04
 ---> e54ca5efa2e9
Step 2 : MAINTAINER Kate Smith <ksmith@example.com>
 ---> Using cache
 ---> 851baf55332b
Step 3 : RUN apt-get update && apt-get install -y ruby ruby-dev
 ---> Running in 3a2558904e9b
...
Step 4 : RUN gem install sinatra
 ---> Running in 6b81cb6313e5
...
Successfully built 97feabe5d2ed

创建完后,可以进一步创建容器:

$ docker run -t -i ouruser/sinatra:v2 /bin/bash
root@8196968dac35:/#

给镜像设置标签

$ docker tag 5db5f8471261 ouruser/sinatra:devel

设置完后查看当前所有的镜像:

$ docker images ouruser/sinatra
REPOSITORY          TAG     IMAGE ID      CREATED        SIZE
ouruser/sinatra     latest  5db5f8471261  11 hours ago   446.7 MB
ouruser/sinatra     devel   5db5f8471261  11 hours ago   446.7 MB
ouruser/sinatra     v2      5db5f8471261  11 hours ago   446.7 MB

上传镜像

创建完我们自己的镜像后,我们可以通过 docker push命令,把它上传到 Docker Hub。这样,其它人都可以使用该镜像。

$ docker push ouruser/sinatra
The push refers to a repository [ouruser/sinatra] (len: 1)
Sending image list
Pushing repository ouruser/sinatra (3 tags)
. . .

删除镜像

$ docker rmi training/sinatra
Untagged: training/sinatra:latest
Deleted: 5bc342fa0b91cabf65246837015197eecfa24b2213ed6a51a8974ae250fedd8d
Deleted: ed0fffdcdae5eb2c3a55549857a8be7fc8bc4241fb19ad714364cbfd7a56b22f
Deleted: 5c58979d73ae448df5af1d8142436d81116187a7633082650549c52c3a2418f0

注意: 删除镜像时必须保证当前没有容器是基于该镜像创建的。

网络容器

命名容器

前面已经通过 ps 命令查看容器了,返回信息的最后一列是容器的名称,默认情况下是自动生成的,如:nostalgic_morse, 我们可以通过 --name 命令自己命名容器:

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

这时候再查看容器列表:

$ docker ps -l
CONTAINER ID  IMAGE                  COMMAND        CREATED       STATUS       PORTS                    NAMES
aed84ee21bde  training/webapp:latest python app.py  12 hours ago  Up 2 seconds 0.0.0.0:49154->5000/tcp  web

或者也可以通过 docker inspect 来命名:

$ docker inspect web
[
{
    "Id": "3ce51710b34f5d6da95e0a340d32aa2e6cf64857fb8cdb2a6c38f7c56f448143",
    "Created": "2015-10-25T22:44:17.854367116Z",
    "Path": "python",
    "Args": [
        "app.py"
    ],
    "State": {
        "Status": "running",
        "Running": true,
        "Paused": false,
        "Restarting": false,
        "OOMKilled": false,
  ...

容器名称必须是唯一的。如果要重复使用名称,必须先删除原容器(使用 docker rm 命令)。当然,删除前要先停止容器。

$ docker stop web
web
$ docker rm web
web

用默认网络启动容器

默认情况下,Docker 提供两种网络连接方式:bridge 和 overlay。在高级应用中也可以自己写网络驱动。。 每个容器安装完后,都会自动包括三个网络:

$ docker network ls
NETWORK ID          NAME                DRIVER
18a2866682b8        none                null                
c288470c46f6        host                host                
7b369448dccb        bridge              bridge  

bridge 是常用的网络方式。测试如下:

$ docker run -itd --name=networktest ubuntu
74695c9cea6d9810718fddadc01a727a5dd3ce6a69d09752239736c030599741

通过 docker inspect 获得IP:

$ docker network inspect bridge
[
    {
        "Name": "bridge",
        "Id": "f7ab26d71dbd6f557852c7156ae0574bbf62c42f539b50c8ebde0f728a253b6f",
        "Scope": "local",
        "Driver": "bridge",
        "IPAM": {
            "Driver": "default",
            "Config": [
                {
                    "Subnet": "172.17.0.1/16",
                    "Gateway": "172.17.0.1"
                }
            ]
        },
        "Containers": {
            "3386a527aa08b37ea9232cbcace2d2458d49f44bb05a6b775fba7ddd40d8f92c": {
                "EndpointID": "647c12443e91faf0fd508b6edfe59c30b642abb60dfab890b4bdccee38750bc1",
                "MacAddress": "02:42:ac:11:00:02",
                "IPv4Address": "172.17.0.2/16",
                "IPv6Address": ""
            },
            "94447ca479852d29aeddca75c28f7104df3c3196d7b6d83061879e339946805c": {
                "EndpointID": "b047d090f446ac49747d3c37d63e4307be745876db7f0ceef7b311cbba615f48",
                "MacAddress": "02:42:ac:11:00:03",
                "IPv4Address": "172.17.0.3/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": "9001"
        }
    }
]

我们也可以通过提供网络名和容器名来断开容器网络:

$ docker network disconnect bridge networktest

就算断开了容器网络连接,也不会删除容器里名为 bridge 的网络名。

创建 bridge 网络

Docker 引擎提供两种网络连接方式: bridge 和 overlay。bridge 只允许在一个主机上运行容器。overlay 可以有多个主机。 创建 bridge 网络:

$ docker network create -d bridge my-bridge-network

-d 参数 Docker 使用 bridge 驱动方式。因为默认是使用 bridge,所以也可以只写 -d, 后面不写参数值。

创建完后再查看网络连接:

$ docker network ls
NETWORK ID          NAME                DRIVER
7b369448dccb        bridge              bridge              
615d565d498c        my-bridge-network   bridge              
18a2866682b8        none                null                
c288470c46f6        host                host

这时候再 inspect 刚才这个网络,会发现里面什么都没有:

$ docker network inspect my-bridge-network
[
    {
        "Name": "my-bridge-network",
        "Id": "5a8afc6364bccb199540e133e63adb76a557906dd9ff82b94183fc48c40857ac",
        "Scope": "local",
        "Driver": "bridge",
        "IPAM": {
            "Driver": "default",
            "Config": [
                {
                    "Subnet": "172.18.0.0/16",
                    "Gateway": "172.18.0.1/16"
                }
            ]
        },
        "Containers": {},
        "Options": {}
    }
]

往网络中添加容器 上面我们创建了一个新的网络,但里面没有容器,这里可以往网络里添加容器。

$ docker run -d --net=my-bridge-network --name db training/postgres

这时候再 inspect 网络 my-bridge-network, 会发现里面有容器了:

$ docker inspect --format=''  db
{"my-bridge-network":{"NetworkID":"7d86d31b1478e7cca9ebed7e73aa0fdeec46c5ca29497431d3007d2d9e15ed99",
"EndpointID":"508b170d56b2ac9e4ef86694b0a76a22dd3df1983404f7321da5649645bf7043","Gateway":"172.18.0.1","IPAddress":"172.18.0.2","IPPrefixLen":16,"IPv6Gateway":"","GlobalIPv6Address":"","GlobalIPv6PrefixLen":0,"MacAddress":"02:42:ac:11:00:02"}}

现在再次启动容器里的 web 应用,现在不做端口映射:

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

这时候 inspect 会发现应用运行在 bridge 网络下:

$ docker inspect --format=''  web
{"bridge":{"NetworkID":"7ea29fc1412292a2d7bba362f9253545fecdfa8ce9a6e37dd10ba8bee7129812",
"EndpointID":"508b170d56b2ac9e4ef86694b0a76a22dd3df1983404f7321da5649645bf7043","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='' web
172.17.0.2

打开一个和容器交互的 shell 窗口:

$ docker exec -it db bash
root@a205f0dd33b2:/# ping 172.17.0.2
ping 172.17.0.2
PING 172.17.0.2 (172.17.0.2) 56(84) bytes of data.
^C
--- 172.17.0.2 ping statistics ---
44 packets transmitted, 0 received, 100% packet loss, time 43185ms

CTRL-C 可以结束命令。另外,上面输入的 ping 命令失败了,因为两个容器不在一个网络中。

Docker 可以把容器添加到多个网络中。所以这里可以把容器 web 添加到网络 my-bridge-network 中:

$ docker network connect my-bridge-network web

再打开一个 shell 窗口执行 ping 命令:

$ docker exec -it db bash
root@a205f0dd33b2:/# ping web
PING web (172.18.0.3) 56(84) bytes of data.
64 bytes from web (172.18.0.3): icmp_seq=1 ttl=64 time=0.095 ms
64 bytes from web (172.18.0.3): icmp_seq=2 ttl=64 time=0.060 ms
64 bytes from web (172.18.0.3): icmp_seq=3 ttl=64 time=0.066 ms
^C
--- web ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2000ms
rtt min/avg/max/mdev = 0.060/0.073/0.095/0.018 ms

ping 的结果显示 web 在 my-bridge-network 中的IP和在 bridge 中的不一样。

管理容器的数据

数据卷

数据卷是一种经特殊设计的目录,它可以存在于多个容器中,它可以用来存储或共享数据,它的主要特性有:

  1. 容器创建时,卷就初始化了。如果容器基于的镜像已经包括了挂载点,数据会被复制一份并存入新的卷中(如果挂载点是宿主机,不会这样处理)。
  2. 数据卷可以在多个容器中共享
  3. 针对数据卷的修改会被直接执行
  4. 当更新一个镜像的时候,对数据卷的修改不会执行
  5. 数据卷在容器被删除后还会存在

添加数据卷

可以使用 -v 参数在启动窗口时向容器中添加一个数据卷。可以添加多个-v参数。如:

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

注意: 也可以使用 Dockerfile 方式,在文件里添加 VOLUME 项来添加数据卷 查看数据卷

$ docker inspect web

输出是数据卷的详细信息:

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

可以看到,数据卷是存在于宿主机器上的某个目录上的。RW的值表示该数据卷是可读写的。

挂载宿主目录

-v参数也可以直接将宿主机器上的某一个目录绑定到容器中作为数据卷使用:

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

该命令将宿主机的目录 /src/webapp, 作为数据卷,绑定到容器的 /opt/webapp.

如果路径 /opt/webapp 已经存在于容器对应的镜像中,/src/webapp 再次绑定,但是目录里已经存在的文件是不会被删除的。一旦解绑定,原内容就又可以访问了。

容器的目录通常都是绝对路径,如: /src/docs。但宿主机的目录可以是绝对路径或者一个名称。如果提供的是名称,Docker 会自己用该名称创建一个卷。

这个名字必须以字母开头,后面可以是a-z0-9, _ (下划线), . (点) or - (横线);绝对路径必须是 / 开头。

绑定宿主机目录是非常有用的。比如,可以把代码放在宿主机上,然后直接修改。这样马上就可以看到效果。

默认情况下,绑定的卷都是可读写的,也可以设置成只读:

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

挂载宿主文件

-v 参数也可以用来挂载单个文件:

$ docker run --rm -it -v ~/.bash_history:/root/.bash_history ubuntu /bin/bash

这样会打开新容器的 shell 窗口,当退出容器时,容器将会拥有宿主机器的 bash history

创建容器并挂载数据卷

如果有些数据是要在多个容器间共享的,最好是创建一个数据卷容器,然后把该容器绑定到其它容器上:

$ docker create -v /dbdata --name dbstore training/postgres /bin/true

这样就可以在其它容器上用 --volumes-from绑定 /dbdata 了:

$ docker run -d --volumes-from dbstore --name db1 training/postgres

以及:

$ docker run -d --volumes-from dbstore --name db2 training/postgres

这样,如果镜像 postgres 里已经包含目录 /dbdata,原目录下的文件会被隐藏,只有 dbstore 里的文件能访问。等去除绑定后,原文件才可以再次访问。

可以使用多个 --volumes-from 从不同容器中绑定。也可以如下:

$ docker run -d --name db3 --volumes-from db1 training/postgres

备份,恢复,迁移数据卷

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

上一篇 秒杀系统构建

下一篇 Docker 实战

评论

文章