Docker 学习笔记(CentOS 7.1)
基本概念
Docker 包括三个基本概念
- 镜像(Image)
- 容器(Container)
- 仓库(Repository)
理解了这三个概念,就理解了 Docker 的整个生命周期。
Docker 镜像
Docker 镜像就是一个只读的模板。
例如:一个镜像可以包含一个完整的 ubuntu 操作系统环境,里面仅安装了 Apache 或用户需要的其它应用程序。镜像可以用来创建 Docker 容器。
Docker 提供了一个很简单的机制来创建镜像或者更新现有的镜像,用户甚至可以直接从其他人那里下载一个已经做好的镜像来直接使用。
容器是从镜像创建的运行实例。它可以被启动、开始、停止、删除。每个容器都是相互隔离的、保证安全的平台。
可以把容器看做是一个简易版的 Linux 环境(包括root用户权限、进程空间、用户空间和网络空间等)和运行在其中的应用程序。
注:镜像是只读的,容器在启动的时候创建一层可写层作为最上层Docker 容器
Docker 利用容器来运行应用。它可以被启动、开始、停止、删除。每个容器都是相互隔离的、保证安全的平台。
可以把容器看做是一个简易版的 Linux 环境(包括root用户权限、进程空间、用户空间和网络空间等)和运行在其中的应用程序。
*注:镜像是只读的,容器在启动的时候创建一层可写层作为最上层。Docker 仓库
仓库是集中存放镜像文件的场所。有时候会把仓库和仓库注册服务器(Registry)混为一谈,并不严格区分。实际上,仓库注册服务器上往往存放着多个仓库,每个仓库中又包含了多个镜像,每个镜像有不同的标签(tag)。
仓库分为公开仓库(Public)和私有仓库(Private)两种形式。最大的公开仓库是 Docker Hub,存放了数量庞大的镜像供用户下载。 国内的公开仓库包括 Docker Pool等,可以提供大陆用户更稳定快速的访问。
当然,用户也可以在本地网络内创建一个私有仓库。当用户创建了自己的镜像之后就可以使用 push 命令将它上传到公有或者私有仓库,这样下次在另外一台机器上使用这个镜像时候,只需要从仓库上 pull 下来就可以了。
注:Docker 仓库的概念跟 Git 类似,注册服务器可以理解为 GitHub 这样的托管服务。
安装方式
安装EPEL
EPEL 是yum的一个软件源,里面包含了许多基本源里没有的软件。(安装需要连接网络)
安装了这个源后,就可以使用yum来安装 docker了。yum install epel-release-7
安装过程需要交互,输入“y”按回车就可以了。
验证是否安装成功:yum repolist
安装Docker-io
yum -y install docker-io
配置开机自启动:chkconfig docker on
完成后,启动服务:service docker start
应用Docker
通过下面命令来验证doecker是否安装成功:
docker info
出现下面类似内容,则表示安装成功:
使用下面命令网络仓库下载需要镜像:docker pull centos:latest
#下载centos
docker pull ubutun:latest
#下载ubutun
使用下面命令查看当前镜像:docker images
在列出信息中,可以看到几个字段信息
- 来自于哪个仓库,比如 docker.io/ubuntu
- 镜像的标记,比如latest
- 它的 ID 号(唯一)07f8e8c5e660
- 创建时间
- 镜像大小
使用docker完成“hello world":docker run ubuntu:07f8e8c5e660 /bin/echo 'hello world'
docker run centos:fd44297e2ddb /bin/echo 'hello docker'
进入交互式容器:
sudo docker run -t -i ubuntu:07f8e8c5e660 /bin/bash
-t 选项让Docker分配一个伪终端(pseudo-tty)并绑定到容器的标准输入上 -i 则让容器的标准输入保持打开。
修改已有镜像:
使用交互模式在已有的centos镜像中安装 gem 应用,exit退出后,使用 docker commit来提交更新后的副本docker commit -m "add gem" -a "Ray" 032c501e000f docker.io/centos:v2
- -m 来指定提交的说明信息,跟我们使用的版本控制工具一样;
- -a 可以指定更新的用户信息;
- 之后是用来创建镜像的容器的 ID;
- 最后指定目标镜像的仓库名和 tag 信息,创建成功后会返回这个镜像的 ID 信息。
可以通过 docker images 查看新创建的镜像:
可以再通过 docker run 打开新创建的镜像:docker run -t -i centos:v2 /bin/bash
利用dockerfile来创建镜像
使用 docker commit 来扩展一个镜像比较简单,但是不方便在一个团队中分享。
我们可以使用 docker build来创建一个新的镜像。
为此,首先需要创建一个 Dockerfile,包含一些如何创建镜像的指令,新建一个目录和一个 Dockerfile[root@localhost ~]# cd /opt/
[root@localhost opt]#
[root@localhost opt]# mkdir ubuntu
[root@localhost opt]# cd ubuntu/
[root@localhost ubuntu]# touch Dockerfile
Dockerfile 中每一条指令都创建镜像的一层,例如:
# This is a comment
MAINTAINER Ray<tsbc@docker.com>
FROM ubuntu:latest
RUN apt-get -qqy install ruby ruby-dev
RUN apt-get -qq update
Dockerfile 基本的语法是
使用#来注释
FROM 指令告诉 Docker 使用哪个镜像作为基础 RUN开头的指令会在创建中运行,比如安装一个软件包,在这里使用 apt-get 来安装了一些软件编写完成 Dockerfile 后可以使用 docker build 来生成镜像。docker build -t="ubuntu:latest" .
其中 -t 标记来添加 tag,指定新的镜像的用户信息。
“.” 是 Dockerfile 所在的路径(当前目录),也可以替换为一个具体的 Dockerfile 的路径。
可以看到 build 进程在执行操作。
注意一个镜像不能超过 127 层
它要做的第一件事情就是上传这个 Dockerfile 内容,因为所有的操作都要依据 Dockerfile 来进行。
然后,Dockerfile 中的指令被一条一条的执行,每一步都创建了一个新的容器,在容器中执行指令并提交修改(就跟之前介绍过的 docker commit 一样)。
当所有的指令都执行完毕之后,返回了最终的镜像 id。所有的中间步骤所产生的容器都被删除和清理了。
此外,还可以利用 ADD 命令复制本地文件到镜像; (需要再做尝试)
用 EXPOSE 命令来向外部开放端口;
* 用 CMD 命令来描述容器启动后运行的程序等。
从本地文件系统导入
要从本地文件系统导入一个镜像,可以使用 openvz(容器虚拟化的先锋技术)的模板来创建: openvz 的模板下载地址为 templates 。
比如,先下载了一个 centos-7-x86_64-minimal.tar.gz 的镜像,之后使用以下命令导入:cat centos-7-x86_64-minimal.tar.gz | docker import - centos:v7.mini
然后查看导入的镜像:
上传镜像
用户可以通过 docker push 命令,把自己创建的镜像上传到仓库中来共享。
docker push docker.io/ubuntu
存出镜像
如果要导出镜像到本地文件,可以使用 docker save 命令。
docker save -o ubuntu.14.0.4.tar docker.io/ubuntu:latest
载入镜像
可以使用 docker load 从导出的本地文件中再导入到本地镜像库,例如
docker load --input ubuntu.14.0.4.tar
或者docker load < ubuntu.14.0.4.tar
移除本地镜像
可以用 docker rmi 移除, 注意 docker rm 是移除容器
docker rmi docker.io/ubuntu
docker rmi 07f8e8c5e660
Docker 容器
容器是 Docker 又一核心概念。
简单的说,容器是独立运行的一个或一组应用,以及它们的运行态环境。对应的,虚拟机可以理解为模拟运行的一整套操作系统(提供了运行态环境和其他系统环境)和跑在上面的应用。
守护态运行docker run -d ubuntu:ruby /bin/sh -c "while true; do echo hello world; sleep 1; done"
容器启动后会返回一个唯一的 id,也可以通过 docker ps 命令来查看容器信息。docker ps
要获取容器的输出信息,可以通过 docker logs 命令。docker logs goofy_cori
终止容器
可以使用 docker stop 来终止一个运行中的容器。
此外,当Docker容器中指定的应用终结时,容器也自动终止。 用户通过 exit 命令或 Ctrl+d 来退出终端时,所创建的容器立刻终止。
终止状态的容器可以用 docker ps -a 命令看到。例如docker ps -a
处于终止状态的容器,可以通过 docker start 命令来重新启动。docker start 6fb00e62cfe9 #id
此外,docker restart 命令会将一个运行态的容器终止,然后再重新启动它.docker restart 6fb00e62cfe9 #id
进入容器
在使用 -d 参数时,容器启动后会进入后台。 某些时候需要进入容器进行操作,有很多种方法,包括使用docker attach 命令或 nsenter 工具等。
attach 命令
docker attach 是Docker自带的命令。docker run -idt ubuntu #后台启动容器
docker ps #查看容器进程NAMES
docker attach stoic_newton #通过 NAMES(stoic_newton)进入容器
nsenter 命令
但是使用 attach 命令有时候并不方便。当多个窗口同时 attach 到同一个容器的时候,所有窗口都会同步显示。
当某个窗口因命令阻塞时,其他窗口也无法执行操作了。安装nsenter
nsenter 工具在 util-linux 包2.23版本后包含,可以先使用 nsenter -version 进行查看。 如果系统中 util-linux 包没有该命令,可以按照下面的方法从源码安装。
cd /tmp
curl https://www.kernel.org/pub/linux/utils/util-linux/v2.24/util-linux-2.24.tar.gz
tar -zxf util-linux-2.24.tar.gz
cd util-linux-2.24;
./configure --without-ncurses
make nsenter && sudo cp nsenter /usr/local/bin
wget -P ~ https://github.com/yeasy/docker_practice/raw/master/_local/.bashrc_docker;
echo "[ -f ~/.bashrc_docker ] && . ~/.bashrc_docker" >> ~/.bashrc; source ~/.bashrc
同时建议大家下载 .bashrc_docker,并将内容放到 .bashrc 中。
这个文件中定义了很多方便使用 Docker 的命令,例如 docker-pid 可以获取某个容器的 PID;
而 docker-enter 可以进入容器或直接在容器内执行命令。
下面给出一个完整的例子:docker ps
echo $(docker-pid 10f839063336) #通过容器ID查看,容器中的首进程ID
docker-enter 10f839063336 ls #可以docker-enter [容器id] 直接操作容器
nsenter --target 838 --mount --uts --ipc --net --pid #通过首进程ID(838)进入容器
导入和导出容器
如果要导出本地某个容器,可以使用 docker export 命令,导入某个容器快照使用 docker import命令:
docker export 7691a814370e > ubuntu.tar #导出容器到本地
cat ubuntu.tar | docker import - test/ubuntu:v1.0 #导入容器快照到镜像
docker import http://example.com/exampleimage.tgz example/imagerepo #从URL导入容器快照
注:用户既可以使用 docker load 来导入镜像存储文件到本地镜像库,也可以使用 docker import 来导入一个容器快照到本地镜像库。 这两者的区别在于容器快照文件将丢弃所有的历史记录和元数据信息(即仅保存容器当时的快照状态),而镜像存储文件将保存完整记录,体积也要大。 此外,从容器快照文件导入时可以重新指定标签等元数据信息。
删除容器
可以使用 docker rm 来删除一个处于终止状态的容器。 例如
docker rm trusting_newton
如果要删除一个运行中的容器,可以添加 -f 参数。Docker 会发送 SIGKILL 信号给容器。
仓库
仓库(Repository)是集中存放镜像的地方。
一个容易混淆的概念是注册服务器(Registry)。实际上注册服务器是管理仓库的具体服务器,每个服务器上可以有多个仓库,而每个仓库下面有多个镜像。
从这方面来说,仓库可以被认为是一个具体的项目或目录。
例如对于仓库地址 dl.dockerpool.com/ubuntu 来说,dl.dockerpool.com 是注册服务器地址,ubuntu 是仓库名。
大部分时候,并不需要严格区分这两者的概念。
Docker Hub
基本操作
目前 Docker 官方维护了一个公共仓库 Docker Hub,其中已经包括了超过 15,000 的镜像。
大部分需求,都可以通过在 Docker Hub 中直接下载镜像来实现。
用户无需登录即可通过 docker search 命令来查找官方仓库中的镜像,并利用 docker pull 命令来将它下载到本地。
例如以 centos 为关键词进行搜索:docker search centos
可以看到返回了很多包含关键字的镜像,其中包括镜像名字、描述、星级(表示该镜像的受欢迎程度)、是否官方创建、是否自动创建。
官方的镜像说明是官方项目组创建和维护的,automated 资源允许用户验证镜像的来源和内容。
根据是否是官方提供,可将镜像资源分为两类。
一种是类似 centos 这样的基础镜像,被称为基础或根镜像。这些基础镜像是由 Docker 公司创建、验证、支持、提供。这样的镜像往往使用单个单词作为名字。
还有一种类型,比如 tianon/centos 镜像,它是由 Docker 的用户创建并维护的,往往带有用户名称前缀。
可以通过前缀 user_name/ 来指定使用某个用户提供的镜像,比如 tianon 用户。
另外,在查找的时候通过 -s N 参数可以指定仅显示评价为 N 星以上的镜像。
下载官方 centos 镜像到本地:docker pull centos:latest
私有仓库
本节介绍如何使用本地仓库。docker-registry 是官方提供的工具,可以用于构建私有的镜像仓库。
安装运行 docker-registry
有时候使用 Docker Hub 这样的公共仓库可能不方便,用户可以创建一个本地仓库供私人使用。
容器运行
在安装了 Docker 后,可以通过获取官方 registry 镜像来运行。
docker run -d -p 5000:5000 registry
这将使用官方的 registry 镜像来启动本地的私有仓库。 用户可以通过指定参数来配置私有仓库位置,例如配置镜像存储到 Amazon S3 服务。docker run \
-e SETTINGS_FLAVOR=s3 \
-e AWS_BUCKET=acme-docker \
-e STORAGE_PATH=/registry \
-e AWS_KEY=AKIAHSHB43HS3J92MXZ \
-e AWS_SECRET=xdDowwlK7TJajV1Y7EoOZrmuPEJlHYcNP2k4j49T \
-e SEARCH_BACKEND=sqlalchemy \
-p 5000:5000 \
registry
此外,还可以指定本地路径(如 /home/user/registry-conf )下的配置文件。
docker run -d -p 5000:5000 -v /home/user/registry-conf:/registry-conf -e DOCKER_REGISTRY_CONFIG=/registry-conf/config.yml registry
默认情况下,仓库会被创建在容器的 /tmp/registry 下。可以通过 -v 参数来将镜像文件存放在本地的指定路径。 例如下面的例子将上传的镜像放到 /opt/data/registry 目录。
docker run -d -p 5000:5000 -v /opt/data/registry:/tmp/registry registry
本地安装
对于 Ubuntu 或 CentOS 等发行版,可以直接通过源安装。
Ubuntusudo apt-get install -y build-essential python-dev libevent-dev python-pip liblzma-dev
sudo pip install docker-registry
CentOSyum install -y python-devel libevent-devel python-pip gcc xz-devel
python-pip install docker-registry
也可以从 docker-registry 项目下载源码进行安装。apt-get install build-essential python-dev libevent-dev python-pip libssl-dev liblzma-dev libffi-dev
git clone https://github.com/docker/docker-registry.git
cd docker-registry
sudo python setup.py install
然后修改配置文件,主要修改 dev 模板段的 storage_path 到本地的存储仓库的路径。cp config/config_sample.yml config/config.yml
之后启动 Web 服务。gunicorn -c contrib/gunicorn.py docker_registry.wsgi:application
或者gunicorn --access-logfile --error-logfile --k gevent -b 0.0.0.0:5000 -w 4 --max-requests 100 docker_registry.wsgi:application
此时使用 curl 访问本地的 5000 端口,看到输出 docker-registry 的版本信息说明运行成功。
注:config/config_sample.yml 文件是示例配置文件。
在私有仓库上传、下载、搜索镜像
创建好私有仓库之后,就可以使用 docker tag 来标记一个镜像,然后推送它到仓库,别的机器上就可以下载下来了。例如私有仓库地址为 192.168.7.26:5000。
先在本机查看已有的镜像。$ sudo docker images
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
ubuntu latest ba5877dc9bec 6 weeks ago 192.7 MB
ubuntu 14.04 ba5877dc9bec 6 weeks ago 192.7 MB使用docker tag 将 ba58 这个镜像标记为 192.168.7.26:5000/test(格式为 docker tag IMAGE[:TAG] [REGISTRYHOST/][USERNAME/]NAME[:TAG])。
$ sudo docker tag ba58 192.168.7.26:5000/test
root ~ # docker images
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
ubuntu 14.04 ba5877dc9bec 6 weeks ago 192.7 MB
ubuntu latest ba5877dc9bec 6 weeks ago 192.7 MB
192.168.7.26:5000/test latest ba5877dc9bec 6 weeks ago 192.7 M使用 docker push 上传标记的镜像。
docker push 192.168.7.26:5000/test
The push refers to a repository [192.168.7.26:5000/test] (len: 1)
Sending image list
Pushing repository 192.168.7.26:5000/test (1 tags)
Image 511136ea3c5a already pushed, skipping
Image 9bad880da3d2 already pushed, skipping
Image 25f11f5fb0cb already pushed, skipping
Image ebc34468f71d already pushed, skipping
Image 2318d26665ef already pushed, skipping
Image ba5877dc9bec already pushed, skipping
Pushing tag for rev [ba5877dc9bec] on {http://192.168.7.26:5000/v1/repositories/test/tags/latest}
Docker 数据管理
如何在 Docker 内部以及容器之间管理数据,在容器中管理数据主要有两种方式:
- 数据卷(Data volumes)
- 数据卷容器(Data volume containers
数据卷
数据卷是一个可供一个或多个容器使用的特殊目录,它绕过 UFS,可以提供很多有用的特性:
- 数据卷可以在容器之间共享和重用
- 对数据卷的修改会立马生效
- 对数据卷的更新,不会影响镜像
- 卷会一直存在,直到没有容器使用
数据卷的使用,类似于 Linux 下对目录或文件进行 mount。
创建一个数据卷
在用 docker run 命令的时候,使用 -v 标记来创建一个数据卷并挂载到容器里。在一次 run 中多次使用可以挂载多个数据卷。
下面创建一个 web 容器,并加载一个数据卷到容器的 /webapp 目录docker run -d -P --name web -v /webapp training/webapp python app.py
*注意:也可以在 Dockerfile 中使用 VOLUME 来添加一个或者多个新的卷到由该镜像创建的任意容器。
挂载一个主机目录作为数据卷
使用 -v 标记也可以指定挂载一个本地主机的目录到容器中去。
docker run -it -P --name webapp -v /webapp:/opt/webapp docker.io/ubuntu
上面的命令加载主机的 /webapp 目录到容器的 /opt/webapp 目录。
这个功能在进行测试的时候十分方便,比如用户可以放置一些程序到本地目录中,来查看容器是否正常工作。
本地目录的路径必须是绝对路径,如果目录不存在 Docker 会自动为你创建它。
*注意:Dockerfile 中不支持这种用法,这是因为 Dockerfile 是为了移植和分享用的。然而,不同操作系统的路径格式不一样,所以目前还不能支持。
Docker 挂载数据卷的默认权限是读写,用户也可以通过 :ro 指定为只读。docker run -it -P --name webapp -v /webapp:/opt/webapp:ro docker.io/ubuntu
加了 :ro 之后,就挂载为只读了。
挂载一个本地主机文件作为数据卷
-v 标记也可以从主机挂载单个文件到容器中
docker run --rm -it -v ~/.bash_history:/.bash_history ubuntu /bin/bash
这样就可以记录在容器输入过的命令了。
*注意:如果直接挂载一个文件,很多文件编辑工具,包括 vi 或者 sed --in-place,可能会造成文件 inode 的改变,从 Docker 1.1 .0起,这会导致报错误信息。
所以最简单的办法就直接挂载文件的父目录。
数据卷容器
如果你有一些持续更新的数据需要在容器之间共享,最好创建数据卷容器。
数据卷容器,其实就是一个正常的容器,专门用来提供数据卷供其它容器挂载的。
首先,创建一个命名的数据卷容器 dbdata:docker run -d -v /dbdata --name dbdata docker.io/centos echo Data-only container for postgres
然后,在其他容器中使用 --volumes-from 来挂载 dbdata 容器中的数据卷。docker run -d --volumes-from dbdata --name db1 docker/centos
docker run -d --volumes-from dbdata --name db2 docker/centos
还可以使用多个 --volumes-from 参数来从多个容器挂载多个数据卷。 也可以从其他已经挂载了数据卷的容器来挂载数据卷。docker run -d --name db3 --volumes-from db1 docker.io/centos
注意:使用 --volumes-from 参数所挂载数据卷的容器自己并不需要保持在运行状态。
如果删除了挂载的容器(包括 dbdata、db1 和 db2),数据卷并不会被自动删除。
如果要删除一个数据卷,必须在删除最后一个还挂载着它的容器时使用 docker rm -v 命令来指定同时删除关联的容器。 这可以让用户在容器之间升级和移动数据卷。
利用数据卷容器来备份、恢复、迁移数据卷
可以利用数据卷对其中的数据进行进行备份、恢复和迁移。
备份
首先使用 --volumes-from 标记来创建一个加载 dbdata 容器卷的容器,并从本地主机挂载当前到容器的 /backup 目录。命令如下:
docker run --volumes-from dbdata -v $(pwd):/backup ubuntu tar cvf /backup/backup.tar dbdata
容器启动后,使用了 tar 命令来将 dbdata 卷备份为本地的 /backup/backup.tar。恢复
如果要恢复数据到一个容器,首先创建一个带有数据卷的容器 dbdata2。
docker run -v /dbdata --name dbdata2 ubuntu /bin/bash
然后创建另一个容器,挂载 dbdata2 的容器,并使用 untar 解压备份文件到挂载的容器卷中。docker run --volumes-from dbdata2 -v $(pwd):/backup ubuntu tar xvf /backup/backup.tar
使用网络
Docker 允许通过外部访问容器或容器互联的方式来提供网络服务。
外部访问容器
容器中可以运行一些网络应用,要让外部也可以访问这些应用,可以通过 -P 或 -p 参数来指定端口映射。
当使用 -P 标记时,Docker 会随机映射一个 49000~49900 的端口到内部容器开放的网络端口。
使用 docker ps 可以看到,本地主机的 49155 被映射到了容器的 5000 端口。此时访问本机的 49155 端口即可访问容器内 web 应用提供的界面。
docker run -d -P docker.io/ubuntu python app.py
docker ps -l
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
bc533791f3f5 docker.io/ubuntu:latest python app.py 5 seconds ago Up2 seconds 0.0.0.0:49155->5000/tcp nostalgic_morse
同样的,可以通过 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 -
-p(小写的)则可以指定要映射的端口,并且,在一个指定端口上只可以绑定一个容器。
支持的格式有ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort。映射所有接口地址
使用 hostPort:containerPort 格式本地的 5000 端口映射到容器的 5000 端口,可以执行
docker run -d -p 5000:5000 docker.io/ubuntu python app.py
此时默认会绑定本地所有接口上的所有地址。映射到指定地址的指定端口
可以使用 ip:hostPort:containerPort 格式指定映射使用一个特定地址,比如 localhost 地址 127.0.0.1
sudo docker run -d -p 127.0.0.1:5000:5000 docker.io/ubuntu python app.py
映射到指定地址的任意端口
使用 ip::containerPort 绑定 localhost 的任意端口到容器的 5000 端口,本地主机会自动分配一个端口。
docker run -d -p 127.0.0.1::5000 docker.io/ubuntu python app.py
还可以使用 udp 标记来指定 udp 端口docker run -d -p 127.0.0.1:5000:5000/udp docker.io/ubuntu python app.py
查看映射端口配置
使用 docker port 来查看当前映射的端口配置,也可以查看到绑定的地址
docker port nostalgic_morse 5000
注意:
*容器有自己的内部网络和 ip 地址(使用 docker inspect 可以获取所有的变量,Docker还可以有一个可变的网络配置。)
*-p 标记可以多次使用来绑定多个端口
例如:
docker run -d -p 5000:5000-p 3000:80 docker.io/ubuntu python app.py
容器互联
容器的连接(linking)系统是除了端口映射外,另一种跟容器中应用交互的方式。
该系统会在源和接收容器之间创建一个隧道,接收容器可以看到源容器指定的信息。自定义容器名连接系统依据容器的名称来执行。因此,首先需要自定义一个好记的容器命名。
虽然当创建容器的时候,系统默认会分配一个名字。自定义命名容器有2个好处:
- 自定义的命名,比较好记,比如一个web应用容器我们可以给它起名叫web 当要连接其他容器时候,可以作为一个有用的参考点,比如连接web容器到db容器
* 使用 --name 标记可以为容器自定义命名。docker run -d -P --name web docker.io/ubuntu python app.py
使用 docker ps 来验证设定的命名。sudo docker ps -l
- CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
- aed84ee21bde docker.io/ubuntu:latest python app.py 12 hours ago Up 2 seconds 0.0.0.0:49154->5000/tcp web
也可以使用 docker inspect 来查看容器的名字ocker inspect -f "{{ .Name }}" aed84ee21bde
/web
在执行 docker run 的时候如果添加 --rm 标记,则容器在终止后会立刻删除。注意,--rm 和 -d 参数不能同时使用。
注意:容器的名称是唯一的。如果已经命名了一个叫 web 的容器,当你要再次使用 web 这个名称的时候,需要先用docker rm 来删除之前创建的同名容器。容器互联
使用 --link 参数可以让容器之间安全的进行交互。
下面先创建一个新的数据库容器。docker run -d --name db docker.io/ubuntu
删除之前创建的 web 容器docker rm -f web
然后创建一个新的 web 容器,并将它连接到 db 容器docker run -d -P --name web --link db:db docker.io/ubuntu python app.py
此时,db 容器和 web 容器建立互联关系。
--link 参数的格式为 --link name:alias,其中 name 是要链接的容器的名称,alias 是这个连接的别名。
使用 docker ps 来查看容器的连接
可以看到自定义命名的容器,db 和 web,db 容器的 names 列有 db 也有 web/db。
这表示 web 容器链接到 db 容器,web 容器将被允许访问 db 容器的信息。
Docker 在两个互联的容器之间创建了一个安全隧道,而且不用映射它们的端口到宿主主机上。
在启动 db 容器的时候并没有使用 -p 和 -P 标记,从而避免了暴露数据库端口到外部网络上。
Docker 通过 2 种方式为容器公开连接信息:
- 环境变量
- 更新 /etc/hosts 文件
使用 env 命令来查看 web 容器的环境变量docker run --rm --name web2 --link db:db docker.io/ubuntu env
. . .
DBNAME=/web2/db
DB_PORT=tcp://172.17.0.5:5432
DB_PORT_5000_TCP=tcp://172.17.0.5:5432
DB_PORT_5000_TCP_PROTO=tcp
DB_PORT_5000_TCP_PORT=5432
DB_PORT_5000_TCP_ADDR=172.17.0.5
. . .
其中 DB 开头的环境变量是供 web 容器连接 db 容器使用,前缀采用大写的连接别名。
除了环境变量,Docker 还添加 host 信息到父容器的 /etc/hosts 的文件。下面是父容器 web 的 hosts 文件docker run -t -i --rm --link db:db docker.io/ubuntu /bin/bash
root@aed84ee21bde:/opt/webapp#cat /etc/hosts
172.17.0.7 aed84ee21bde
. . .
172.17.0.5 db
这里有 2 个 hosts,第一个是 web 容器,web 容器用 id 作为他的主机名,第二个是 db 容器的 ip 和主机名。
可以在 web 容器中安装 ping 命令来测试跟db容器的连通。
root@aed84ee21bde:/opt/webapp#apt-get install -yqq inetutils-ping
root@aed84ee21bde:/opt/webapp#ping db
PING db (172.17.0.5): 48 data bytes
56 bytes from 172.17.0.5: icmp_seq=0 ttl=64 time=0.267 ms
56 bytes from 172.17.0.5: icmp_seq=1 ttl=64 time=0.250 ms
56 bytes from 172.17.0.5: icmp_seq=2 ttl=64 time=0.256 ms
用 ping 来测试db容器,它会解析成 172.17.0.5。 * 注意:官方的 ubuntu 镜像默认没有安装 ping,需要自行安装。
用户可以链接多个父容器到子容器,比如可以链接多个 web 到 db 容器上。
高级网络配置
当 Docker 启动时,会自动在主机上创建一个 docker0 虚拟网桥,实际上是 Linux 的一个 bridge,可以理解为一个软件交换机。
它会在挂载到它的网口之间进行转发。同时,Docker 随机分配一个本地未占用的私有网段(在 RFC1918 中定义)中的一个地址给 docker0 接口。
比如典型的 172.17.42.1,掩码为 255.255.0.0。此后启动的容器内的网口也会自动分配一个同一网段(172.17.0.0/16)的地址。
当创建一个 Docker 容器的时候,同时会创建了一对 veth pair 接口(当数据包发送到一个接口时,另外一个接口也可以收到相同的数据包)。
这对接口一端在容器内,即 eth0;另一端在本地并被挂载到 docker0 网桥,名称以 veth 开头(例如 vethAQI2QT)。
通过这种方式,主机可以跟容器通信,容器之间也可以相互通信。Docker 就创建了在主机和所有容器之间一个虚拟共享网络。
快速配置指南
下面是一个跟 Docker 网络相关的命令列表。
其中有些命令选项只有在 Docker 服务启动的时候才能配置,而且不能马上生效。
- --bip=CIDR --定制 docker0 的掩码
- -H SOCKET... or --host=SOCKET... --Docker 服务端接收命令的通道
- --icc=true|false --是否支持容器之间进行通信
- --ip-forward=true|false --请看下文容器之间的通信
- --iptables=true|false --禁止 Docker 添加 iptables 规则
- --mtu=BYTES --容器网络中的 MTU
- -b BRIDGE or --bridge=BRIDGE --指定容器挂载的网桥
下面2个命令选项既可以在启动服务时指定,也可以 Docker 容器启动(docker run)时候指定。在 Docker 服务启动的时候指定则会成为默认值,后面执行 docker run 时可以覆盖设置的默认值。
- --dns=IP_ADDRESS... --使用指定的DNS服务器
- --dns-search=DOMAIN... --指定DNS搜索域
最后这些选项只有在 docker run 执行时使用,因为它是针对容器的特性内容。
- -h HOSTNAME or --hostname=HOSTNAME --配置容器主机名
- --link=CONTAINER_NAME:ALIAS --添加到另一个容器的连接
- --net=bridge|none|container:NAME_or_ID|host --配置容器的桥接模式
- -p SPEC or --publish=SPEC --映射容器端口到宿主主机
- -P or --publish-all=true|false --映射容器所有端口到宿主主机
配置 DNS
Docker 没有为每个容器专门定制镜像,那么怎么自定义配置容器的主机名和 DNS 配置呢? 秘诀就是它利用虚拟文件来挂载到来容器的 3 个相关配置文件。
在容器中使用 mount 命令可以看到挂载信息:
$ mount
...
/dev/disk/by-uuid/1fec...ebdf on /etc/hostname type ext4 ...
/dev/disk/by-uuid/1fec...ebdf on /etc/hosts type ext4 ...
tmpfs on /etc/resolv.conf type tmpfs ...
...
这种机制可以让宿主主机 DNS 信息发生更新后,所有 Docker 容器的 dns 配置通过 /etc/resolv.conf 文件立刻得到更新。
如果用户想要手动指定容器的配置,可以利用下面的选项。
- -h HOSTNAME or --hostname=HOSTNAME 设定容器的主机名,它会被写到容器内的 /etc/hostname 和/etc/hosts。
但它在容器外部看不到,既不会在 docker ps 中显示,也不会在其他的容器的 /etc/hosts 看到。- --link=CONTAINER_NAME:ALIAS 选项会在创建容器的时候,添加一个其他容器的主机名到 /etc/hosts 文件中,
让新容器的进程可以使用主机名 ALIAS 就可以连接它。- --dns=IP_ADDRESS 添加 DNS 服务器到容器的 /etc/resolv.conf 中,让容器用这个服务器来解析所有不在/etc/hosts 中的主机名。
- --dns-search=DOMAIN 设定容器的搜索域,当设定搜索域为 .example.com 时,在搜索一个名为 host 的主机时,
DNS 不仅搜索host,还会搜索 host.example.com。
注意:如果没有上述最后 2 个选项,Docker 会默认用主机上的 /etc/resolv.conf 来配置容器。
容器访问控制
容器的访问控制,主要通过 Linux 上的 iptables 防火墙来进行管理和实现。iptables 是 Linux 上默认的防火墙软件,在大部分发行版中都自带。
容器访问外部网络
容器要想访问外部网络,需要本地系统的转发支持。在Linux 系统中,检查转发是否打开。
$sysctl net.ipv4.ip_forward
net.ipv4.ip_forward =1
#如果为 0,说明没有开启转发,则需要手动打开。
$sysctl -w net.ipv4.ip_forward=1
如果在启动 Docker 服务的时候设定 --ip-forward=true, Docker 就会自动设定系统的 ip_forward 参数为 1。
容器之间访问
容器之间相互访问,需要两方面的支持。
- 容器的网络拓扑是否已经互联。默认情况下,所有容器都会被连接到 docker0 网桥上。
- 本地系统的防火墙软件 -- iptables 是否允许通过。
访问所有端口
当启动 Docker 服务时候,默认会添加一条转发策略到 iptables 的 FORWARD 链上。
策略为通过(ACCEPT)还是禁止(DROP)取决于配置--icc=true(缺省值)还是 --icc=false。
当然,如果手动指定 --iptables=false 则不会添加 iptables 规则。
可见,默认情况下,不同容器之间是允许网络互通的。
如果为了安全考虑,可以在 /etc/default/docker 文件中配置 DOCKER_OPTS=--icc=false 来禁止它。访问指定端口
在通过 -icc=false 关闭网络访问后,还可以通过 --link=CONTAINER_NAME:ALIAS 选项来访问容器的开放端口。
例如,在启动 Docker 服务时,可以同时使用 icc=false --iptables=true 参数来关闭允许相互的网络访问,并让 Docker 可以修改系统中的 iptables 规则。
此时,系统中的 iptables 规则可能是类似
$ sudo iptables -nL
...
Chain FORWARD (policy ACCEPT)
target prot opt source destination
DROP all --0.0.0.0/00.0.0.0/0
...
之后,启动容器(docker run)时使用 --link=CONTAINER_NAME:ALIAS 选项。
Docker 会在 iptable 中为 两个容器分别添加一条 ACCEPT 规则,允许相互访问开放的端口(取决于 Dockerfile 中的 EXPOSE 行)。
当添加了 --link=CONTAINER_NAME:ALIAS 选项后,添加了 iptables 规则。
$ sudo iptables -nL
...
Chain FORWARD (policy ACCEPT)
target prot opt source destination
ACCEPT tcp --172.17.0.2172.17.0.3 tcp spt:80
ACCEPT tcp --172.17.0.3172.17.0.2 tcp dpt:80
DROP all --0.0.0.0/00.0.0.0/0
注意:--link=CONTAINER_NAME:ALIAS 中的 CONTAINER_NAME 目前必须是 Docker 分配的名字,或使用 --name参数指定的名字。主机名则不会被识别。
映射容器端口到宿主主机的实现
默认情况下,容器可以主动访问到外部网络的连接,但是外部网络无法访问到容器。
容器访问外部实现
容器所有到外部网络的连接,源地址都会被NAT成本地系统的IP地址。这是使用 iptables 的源地址伪装操作实现的。
查看主机的 NAT 规则。
$ sudo iptables -t nat -nL
...
Chain POSTROUTING (policy ACCEPT)
target prot opt source destination
MASQUERADE all --172.17.0.0/16!172.17.0.0/16
...
其中,上述规则将所有源地址在 172.17.0.0/16 网段,目标地址为其他网段(外部网络)的流量动态伪装为从系统网卡发出。
MASQUERADE 跟传统 SNAT 的好处是它能动态从网卡获取地址。外部访问容器实现
容器允许外部访问,可以在 docker run 时候通过 -p 或 -P 参数来启用。
不管用那种办法,其实也是在本地的 iptable 的 nat 表中添加相应的规则。
使用 -P 时:
$ iptables -t nat -nL
...
Chain DOCKER (2 references)
target prot opt source destination
DNAT tcp --0.0.0.0/00.0.0.0/0 tcp dpt:49153 to:172.17.0.2:80
使用 -p 80:80 时:
$ iptables -t nat -nL
Chain DOCKER (2 references)
target prot opt source destination
DNAT tcp --0.0.0.0/00.0.0.0/0 tcp dpt:80 to:172.17.0.2:80
注意:
- 这里的规则映射了 0.0.0.0,意味着将接受主机来自所有接口的流量。
用户可以通过 -p IP:host_port:container_port 或 -p IP::port 来指定允许访问容器的主机上的 IP、接口等,以制定更严格的规则。- 如果希望永久绑定到某个固定的 IP 地址,可以在 Docker 配置文件 /etc/default/docker 中指定DOCKER_OPTS="--ip=IP_ADDRESS",
之后重启 Docker 服务即可生效。
配置dokcer0网桥
Docker 服务默认会创建一个 docker0 网桥(其上有一个 docker0 内部接口),
它在内核层连通了其他的物理或虚拟网卡,这就将所有容器和本地主机都放到同一个物理网络。
Docker 默认指定了 docker0 接口 的 IP 地址和子网掩码,让主机和容器之间可以通过网桥相互通信,它还给出了 MTU(接口允许接收的最大传输单元),
通常是 1500 Bytes,或宿主主机网络路由上支持的默认值。这些值都可以在服务启动的时候进行配置。
- --bip=CIDR -- IP 地址加掩码格式,例如 192.168.1.5/24
- --mtu=BYTES -- 覆盖默认的 Docker mtu 配置
也可以在配置文件中配置 DOCKER_OPTS,然后重启服务。 由于目前 Docker 网桥是 Linux 网桥,用户可以使用 brctl show 来查看网桥和端口连接信息。
$ sudo brctl show
bridge name bridge id STP enabled interfaces
docker0 8000.3a1d7362b4ee no veth65f9
*注:brctl 命令在 Debian、Ubuntu 中可以使用 sudo apt-get install bridge-utils 来安装。
每次创建一个新容器的时候,Docker 从可用的地址段中选择一个空闲的 IP 地址分配给容器的 eth0 端口。
使用本地主机上 docker0 接口的 IP 作为所有容器的默认网关。
$ sudo docker run -i -t --rm base /bin/bash
$ ip addr show eth0
24: eth0:<BROADCAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether 32:6f:e0:35:57:91 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.3/16 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::306f:e0ff:fe35:5791/64 scope link
valid_lft forever preferred_lft forever
$ ip route
default via 172.17.42.1 dev eth0
172.17.0.0/16 dev eth0 proto kernel scope link src 172.17.0.3
$ exit
自定义网桥
除了默认的 docker0 网桥,用户也可以指定网桥来连接各个容器。
在启动 Docker 服务的时候,使用 -b BRIDGE或--bridge=BRIDGE 来指定使用的网桥。
如果服务已经运行,那需要先停止服务,并删除旧的网桥。
$ sudo service docker stop
$ sudo ip link set dev docker0 down
$ sudo brctl delbr docker0
```
查看确认网桥创建并启动。
```sh
$ ip addr show bridge0
4: bridge0:<BROADCAST,MULTICAST> mtu 1500 qdisc noop state UP group default
link/ether 66:38:d0:0d:76:18 brd ff:ff:ff:ff:ff:ff
inet 192.168.5.1/24 scope global bridge0
valid_lft forever preferred_lft forever
配置 Docker 服务,默认桥接到创建的网桥上。
$ echo 'DOCKER_OPTS="-b=bridge0"'>>/etc/default/docker
$ sudo service docker start
启动 Docker 服务。 新建一个容器,可以看到它已经桥接到了 bridge0 上。
可以继续用 brctl show 命令查看桥接的信息。
另外,在容器中可以使用 ip addr 和 ip route 命令来查看 IP 地址配置和路由信息。
工具和示例
pipework
Jérôme Petazzoni 编写了一个叫 pipework 的 shell 脚本,可以帮助用户在比较复杂的场景中完成容器的连接。
playground
Brandon Rhodes 创建了一个提供完整的 Docker 容器网络拓扑管理的 Python库,包括路由、NAT 防火墙;
以及一些提供 HTTP, SMTP, POP, IMAP, Telnet, SSH, FTP 的服务器。
编辑网络配置文件
Docker 1.2.0 开始支持在运行中的容器里编辑 /etc/hosts, /etc/hostname 和 /etc/resolve.conf 文件。
但是这些修改是临时的,只在运行的容器中保留,容器终止或重启后并不会被保存下来。也不会被 docker commit 提交。
实例:创建一个点到点的连接
默认情况下,Docker 会将所有容器连接到由 docker0 提供的虚拟子网中。
用户有时候需要两个容器之间可以直连通信,而不用通过主机网桥进行桥接。
解决办法很简单:创建一对 peer 接口,分别放到两个容器中,配置成点到点链路类型即可。
首先启动 2 个容器:
$ sudo docker run -i -t --rm --net=none base /bin/bash
root@1f1f4c1f931a:/#
$ sudo docker run -i -t --rm --net=none base /bin/bash
root@12e343489d2f:/#
找到进程号,然后创建网络名字空间的跟踪文件。
$ sudo docker inspect -f '{{.State.Pid}}'1f1f4c1f931a
2989
$ sudo docker inspect -f '{{.State.Pid}}'12e343489d2f
3004
$ sudo mkdir -p /var/run/netns
$ sudo ln -s /proc/2989/ns/net /var/run/netns/2989
$ sudo ln -s /proc/3004/ns/net /var/run/netns/3004
创建一对 peer 接口,然后配置路由
$ sudo ip link add A type veth peer name B$ sudo ip link set A netns 2989
$ sudo ip netns exec 2989 ip addr add 10.1.1.1/32 dev A
$ sudo ip netns exec 2989 ip link set A up
$ sudo ip netns exec 2989 ip route add 10.1.1.2/32 dev A$ sudo ip link set B netns 3004
$ sudo ip netns exec 3004 ip addr add 10.1.1.2/32 dev B
$ sudo ip netns exec 3004 ip link set B up
$ sudo ip netns exec 3004 ip route add 10.1.1.1/32 dev B
现在这 2 个容器就可以相互 ping 通,并成功建立连接。点到点链路不需要子网和子网掩码。
此外,也可以不指定 --net=none 来创建点到点链路。这样容器还可以通过原先的网络来通信。
利用类似的办法,可以创建一个只跟主机通信的容器。但是一般情况下,更推荐使用 --icc=false 来关闭容器之间的通信。
实战案例
使用Supervisor来管理进程
创建tomcat/weblogic集群
多台物理主机之间的容器互联
标准化开发测试和生产环境
Docker 是个伟大的项目,它彻底释放了虚拟化的威力,极大降低了云计算资源供应的成本,同时让应用的分发、
测试、部署和分发都变得前所未有的高效和轻松!本书既适用于具备基础 Linux 知识的 Docker 初学者,也希望可供理解原理和实现的高级用户参考。同时,书中给出的实践案例,可供在进行实际部署时借鉴。前六章为基础内容,供用户理解 Docker 的基本概念和操作;7 ~ 9 章介绍一些高级操作;第 10 章给出典型的应用场景和实践案例;11 ~ 13 章介绍关于 Docker 实现的相关技术。14 ~ 17章介绍相关的一些开源项目。
来源: http://dockerpool.com/static/books/docker_practice/index.html