Docker —— 从入门到实践(续)
6. 仓库
仓库(Repository)是集中存放镜像的地方。
一个容易混淆的概念是注册服务器(Registry)。实际上注册服务器是管理仓库的具体服务器,每个服务器上可以有多个仓库,而每个仓库下面有多个镜像。从这方面来说,仓库可以被认为是一个具体的项目或目录。例如对于仓库地址 dl.dockerpool.com/ubuntu 来说,dl.dockerpool.com 是注册服务器地址,ubuntu 是仓库名。
大部分时候,并不需要严格区分这两者的概念。
6.1. Docker Hub
目前 Docker 官方维护了一个公共仓库 Docker Hub,其中已经包括了超过 15,000 的镜像。大部分需求,都可以通过在 Docker Hub 中直接下载镜像来实现。
登录
可以通过执行 docker login 命令来输入用户名、密码和邮箱来完成注册和登录。 注册成功后,本地用户目录的 .dockercfg 中将保存用户的认证信息。
基本操作
用户无需登录即可通过 docker search 命令来查找官方仓库中的镜像,并利用 docker pull 命令来将它下载到本地。
例如以 centos 为关键词进行搜索:
$ sudo docker search centos NAME DESCRIPTION STARS OFFICIAL AUTOMATED centos The official build of CentOS. 465 [OK] tianon/centos CentOS 5 and 6, created using rinse instea... 28 blalor/centos Bare-bones base CentOS 6.5 image 6 [OK] saltstack/centos-6-minimal 6 [OK] tutum/centos-6.4 DEPRECATED. Use tutum/centos:6.4 instead. ... 5 [OK] ...
可以看到返回了很多包含关键字的镜像,其中包括镜像名字、描述、星级(表示该镜像的受欢迎程度)、是否官方创建、是否自动创建。 官方的镜像说明是官方项目组创建和维护的,automated 资源允许用户验证镜像的来源和内容。
根据是否是官方提供,可将镜像资源分为两类。 一种是类似 centos 这样的基础镜像,被称为基础或根镜像。这些基础镜像是由 Docker 公司创建、验证、支持、提供。这样的镜像往往使用单个单词作为名字。 还有一种类型,比如 tianon/centos 镜像,它是由 Docker 的用户创建并维护的,往往带有用户名称前缀。可以通过前缀 user_name/ 来指定使用某个用户提供的镜像,比如 tianon 用户。
另外,在查找的时候通过 -s N 参数可以指定仅显示评价为 N 星以上的镜像。
下载官方 centos 镜像到本地。
$ sudo docker pull centos Pulling repository centos 0b443ba03958: Download complete 539c0211cd76: Download complete 511136ea3c5a: Download complete 7064731afe90: Download complete
用户也可以在登录后通过 docker push 命令来将镜像推送到 Docker Hub。
自动创建
自动创建(Automated Builds)功能对于需要经常升级镜像内程序来说,十分方便。 有时候,用户创建了镜像,安装了某个软件,如果软件发布新版本则需要手动更新镜像。。
而自动创建允许用户通过 Docker Hub 指定跟踪一个目标网站(目前支持 GitHub 或 BitBucket)上的项目,一旦项目发生新的提交,则自动执行创建。
要配置自动创建,包括如下的步骤:
- 创建并登陆 Docker Hub,以及目标网站;
- 在目标网站中连接帐户到 Docker Hub;
- 在 Docker Hub 中 配置一个自动创建;
- 选取一个目标网站中的项目(需要含 Dockerfile)和分支;
- 指定 Dockerfile 的位置,并提交创建。
之后,可以 在Docker Hub 的 自动创建页面 中跟踪每次创建的状态。
6.2. 私有仓库
有时候使用 Docker Hub 这样的公共仓库可能不方便,用户可以创建一个本地仓库供私人使用。
本节介绍如何使用本地仓库。
安装运行 docker-registry
容器运行
在安装了 Docker 后,可以通过获取官方 registry 镜像来运行。
$ sudo docker run -d -p 5000:5000 registry
这将使用官方的 registry 镜像来启动本地的私有仓库。 用户可以通过指定参数来配置私有仓库位置,例如配置镜像存储到 Amazon S3 服务。
$ sudo 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 )下的配置文件。
$ sudo 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/da
$ sudo docker run -d -p 5000:5000 -v /opt/da ta/registry:/tmp/registry registry
本地安装
对于 Ubuntu 或 CentOS 等发行版,可以直接通过源安装。
- Ubuntu
$ sudo apt-get install -y build-essential python-dev libevent-dev python-pip liblzma-dev $ sudo pip install docker-registry - CentOS
$ sudo yum install -y python-devel libevent-devel python-pip gcc xz-devel $ sudo python-pip install docker-registry
也可以从 docker-registry 项目下载源码进行安装。
$ sudo 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 服务。
$ sudo gunicorn -c contrib/gunicorn.py docker_registry.wsgi:application
或者
$ sudo gunicorn --access-logfile - --error-logfile - -k gevent -b 0.0.0.0:5000 -w 4 --max-requests 100 docker_registry.wsgi:application
此时使用访问本地的 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 MB
使用 docker push 上传标记的镜像。
$ sudo 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}
用 curl 查看仓库中的镜像。
$ curl http://192.168.7.26:5000/v1/search {"num_results": 7, "query": "", "results": [{"description": "", "name": "library/miaxis_j2ee"}, {"description": "", "name": "library/tomcat"}, {"description": "", "name": "library/ubuntu"}, {"description": "", "name": "library/ubuntu_office"}, {"description": "", "name": "library/desktop_ubu"}, {"description": "", "name": "dockerfile/ubuntu"}, {"description": "", "name": "library/test"}]}
这里可以看到 {“description”: “”, “name”: “library/test”},表明镜像已经被成功上传了。
现在可以到另外一台机器去下载这个镜像。
$ sudo docker pull 192.168.7.26:5000/test Pulling repository 192.168.7.26:5000/test ba5877dc9bec: Download complete 511136ea3c5a: Download complete 9bad880da3d2: Download complete 25f11f5fb0cb: Download complete ebc34468f71d: Download complete 2318d26665ef: Download complete $ sudo docker images REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE 192.168.7.26:5000/test latest ba5877dc9bec 6 weeks ago 192.7 MB
可以使用 这个脚本 批量上传本地的镜像到注册服务器中,默认是本地注册服务器 127.0.0.1:5000。例如:
$ wget https://github.com/yeasy/docker_practice/raw/master/_local/push_images.sh; sudo chmod a+x push_images.sh $ ./push_images.sh ubuntu:latest centos:centos7 The registry server is 127.0.0.1 Uploading ubuntu:latest... The push refers to a repository [127.0.0.1:5000/ubuntu] (len: 1) Sending image list Pushing repository 127.0.0.1:5000/ubuntu (1 tags) Image 511136ea3c5a already pushed, skipping Image bfb8b5a2ad34 already pushed, skipping Image c1f3bdbd8355 already pushed, skipping Image 897578f527ae already pushed, skipping Image 9387bcc9826e already pushed, skipping Image 809ed259f845 already pushed, skipping Image 96864a7d2df3 already pushed, skipping Pushing tag for rev [96864a7d2df3] on {http://127.0.0.1:5000/v1/repositories/ubuntu/tags/latest} Untagged: 127.0.0.1:5000/ubuntu:latest Done Uploading centos:centos7... The push refers to a repository [127.0.0.1:5000/centos] (len: 1) Sending image list Pushing repository 127.0.0.1:5000/centos (1 tags) Image 511136ea3c5a already pushed, skipping 34e94e67e63a: Image successfully pushed 70214e5d0a90: Image successfully pushed Pushing tag for rev [70214e5d0a90] on {http://127.0.0.1:5000/v1/repositories/centos/tags/centos7} Untagged: 127.0.0.1:5000/centos:centos7 Done
6.3. 仓库配置文件
Docker 的 Registry 利用配置文件提供了一些仓库的模板(flavor),用户可以直接使用它们来进行开发或生产部署。
模板
在 config_sample.yml 文件中,可以看到一些现成的模板段:
common:基础配置 local:存储数据到本地文件系统 s3:存储数据到 AWS S3 中 dev:使用 local 模板的基本配置 test:单元测试使用 prod:生产环境配置(基本上跟s3配置类似) gcs:存储数据到 Google 的云存储 swift:存储数据到 OpenStack Swift 服务 glance:存储数据到 OpenStack Glance 服务,本地文件系统为后备 glance-swift:存储数据到 OpenStack Glance 服务,Swift 为后备 elliptics:存储数据到 Elliptics key/value 存储
用户也可以添加自定义的模版段。
默认情况下使用的模板是dev,要使用某个模板作为默认值,可以添加 SETTINGS_FLAVOR 到环境变量中,例如
export SETTINGS_FLAVOR=dev
另外,配置文件中支持从环境变量中加载值,语法格式为 _env:VARIABLENAME[:DEFAULT]。
示例配置
common: loglevel: info search_backend: "_env:SEARCH_BACKEND:" sqlalchemy_index_database: "_env:SQLALCHEMY_INDEX_DATABASE:sqlite:////tmp/docker-registry.db" prod: loglevel: warn storage: s3 s3_access_key: _env:AWS_S3_ACCESS_KEY s3_secret_key: _env:AWS_S3_SECRET_KEY s3_bucket: _env:AWS_S3_BUCKET boto_bucket: _env:AWS_S3_BUCKET storage_path: /srv/docker smtp_host: localhost from_addr: docker@myself.com to_addr: my@myself.com dev: loglevel: debug storage: local storage_path: /home/myself/docker test: storage: local storage_path: /tmp/tmpdockertmp
选项
7. Docker 数据管理
这一章介绍如何在 Docker 内部以及容器之间管理数据,在容器中管理数据主要有两种方式:
- 数据卷(Da
ta volumes) - 数据卷容器(Da
ta volume containers)
7.1. 数据卷
数据卷是一个可供一个或多个容器使用的特殊目录,它绕过 UFS,可以提供很多有用的特性:
- 数据卷可以在容器之间共享和重用
- 对数据卷的修改会立马生效
- 对数据卷的更新,不会影响镜像
- 卷会一直存在,直到没有容器使用
*数据卷的使用,类似于 Linux 下对目录或文件进行 mount。
创建一个数据卷
在用 docker run 命令的时候,使用 -v 标记来创建一个数据卷并挂载到容器里。在一次 run 中多次使用可以挂载多个数据卷。
下面创建一个 web 容器,并加载一个数据卷到容器的 /webapp 目录。
$ sudo docker run -d -P --name web -v /webapp training/webapp python app.py
*注意:也可以在 Dockerfile 中使用 VOLUME 来添加一个或者多个新的卷到由该镜像创建的任意容器。
挂载一个主机目录作为数据卷
使用 -v 标记也可以指定挂载一个本地主机的目录到容器中去。
$ sudo docker run -d -P --name web -v /src/webapp:/opt/webapp training/webapp python app.py
上面的命令加载主机的 /src/webapp 目录到容器的 /opt/webapp 目录。这个功能在进行测试的时候十分方便,比如用户可以放置一些程序到本地目录中,来查看容器是否正常工作。本地目录的路径必须是绝对路径,如果目录不存在 Docker 会自动为你创建它。
*注意:Dockerfile 中不支持这种用法,这是因为 Dockerfile 是为了移植和分享用的。然而,不同操作系统的路径格式不一样,所以目前还不能支持。
Docker 挂载数据卷的默认权限是读写,用户也可以通过 :ro 指定为只读。
$ sudo docker run -d -P --name web -v /src/webapp:/opt/webapp:ro training/webapp python app.py
加了 :ro 之后,就挂载为只读了。
挂载一个本地主机文件作为数据卷
$ sudo docker run --rm -it -v ~/.bash_history:/.bash_history ubuntu /bin/bash
这样就可以记录在容器输入过的命令了。
*注意:如果直接挂载一个文件,很多文件编辑工具,包括 vi 或者 sed –in-place,可能会造成文件 inode 的改变,从 Docker 1.1 .0起,这会导致报错误信息。所以最简单的办法就直接挂载文件的父目录。
7.2. 数据卷容器
如果你有一些持续更新的数据需要在容器之间共享,最好创建数据卷容器。
数据卷容器,其实就是一个正常的容器,专门用来提供数据卷供其它容器挂载的。
首先,创建一个命名的数据卷容器 dbdata:
$ sudo docker run -d -v /dbdata --name dbdata training/postgres echo Da ta-on ly container for postgres
然后,在其他容器中使用 –volumes-from 来挂载 dbdata 容器中的数据卷。
$ sudo docker run -d --volumes-from dbdata --name db1 training/postgres $ sudo docker run -d --volumes-from dbdata --name db2 training/postgres
还可以使用多个 –volumes-from 参数来从多个容器挂载多个数据卷。 也可以从其他已经挂载了容器卷的容器来挂载数据卷。
$ sudo docker run -d --name db3 --volumes-from db1 training/postgres
*注意:使用 –volumes-from 参数所挂载数据卷的容器自己并不需要保持在运行状态。
如果删除了挂载的容器(包括 dbdata、db1 和 db2),数据卷并不会被自动删除。如果要删除一个数据卷,必须在删除最后一个还挂载着它的容器时使用 docker rm -v 命令来指定同时删除关联的容器。 这可以让用户在容器之间升级和移动数据卷。具体的操作将在下一节中进行讲解。
7.3. 利用数据卷容器来备份、恢复、迁移数据卷
可以利用数据卷对其中的数据进行进行备份、恢复和迁移。
备份
首先使用 –volumes-from 标记来创建一个加载 dbdata 容器卷的容器,并从本地主机挂载当前到容器的 /backup 目录。命令如下:
$ sudo docker run --volumes-from dbdata -v $(pwd):/backup ubuntu tar cvf /backup/backup.tar /dbdata
容器启动后,使用了tar 命令来将 dbdata 卷备份为本地的 /backup/backup.tar。
恢复
如果要恢复数据到一个容器,首先创建一个带有数据卷的容器 dbdata2。
$ sudo docker run -v /dbdata --name dbdata2 ubuntu /bin/bash
然后创建另一个容器,挂载 dbdata2 的容器,并使用 untar 解压备份文件到挂载的容器卷中。
$ sudo docker run --volumes-from dbdata2 -v $(pwd):/backup busybox tar xvf /backup/backup.tar
8. Docker 中的网络功能介绍
Docker 允许通过外部访问容器或容器互联的方式来提供网络服务。
8.1. 外部访问容器
容器中可以运行一些网络应用,要让外部也可以访问这些应用,可以通过 -P 或 -p 参数来指定端口映射。
当使用 -P 标记时,Docker 会随机映射一个 49000~49900 的端口到内部容器开放的网络端口。
使用 docker ps 可以看到,本地主机的 49155 被映射到了容器的 5000 端口。此时访问本机的 49115 端口即可访问容器内 web 应用提供的界面。
$ sudo docker run -d -P training/webapp python app.py $ sudo 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
同样的,可以通过 docker logs 命令来查看应用的信息。
$ sudo 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 端口,可以执行
$ sudo docker run -d -p 5000:5000 training/webapp python app.py
此时默认会绑定本地所有接口上的所有地址。
映射到指定地址的指定端口
可以使用 ip:hostPort:containerPort 格式指定映射使用一个特定地址,比如 localhost 地址 127.0.0.1
$ sudo docker run -d -p 127.0.0.1:5000:5000 training/webapp python app.py
映射到指定地址的任意端口
使用 ip::containerPort 绑定 localhost 的任意端口到容器的 5000 端口,本地主机会自动分配一个端口。
$ sudo docker run -d -p 127.0.0.1::5000 training/webapp python app.py
还可以使用 udp 标记来指定 udp 端口
$ sudo docker run -d -p 127.0.0.1:5000:5000/udp training/webapp python app.py
查看映射端口配置
使用 docker port 来查看当前映射的端口配置,也可以查看到绑定的地址
$ docker port nostalgic_morse 5000 127.0.0.1:49155.
注意:
- 容器有自己的内部网络和 ip 地址(使用 docker inspect 可以获取所有的变量,Docker 还可以有一个可变的网络配置。)
- -p 标记可以多次使用来绑定多个端口
例如
$ sudo docker run -d -p 5000:5000 -p 3000:80 training/webapp python app.py
8.2. 容器互联
容器的连接(linking)系统是除了端口映射外,另一种跟容器中应用交互的方式。
该系统会在源和接收容器之间创建一个隧道,接收容器可以看到源容器指定的信息。
自定义容器命名
连接系统依据容器的名称来执行。因此,首先需要自定义一个好记的容器命名。
虽然当创建容器的时候,系统默认会分配一个名字。自定义命名容器有2个好处:
- 自定义的命名,比较好记,比如一个web应用容器我们可以给它起名叫web
- 当要连接其他容器时候,可以作为一个有用的参考点,比如连接web容器到db容器
使用 –name 标记可以为容器自定义命名。
$ sudo docker run -d -P --name web training/webapp python app.py
使用 docker ps 来验证设定的命名。
$ sudo 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 来查看容器的名字
$ sudo docker inspect -f "{{ .Name }}" aed84ee21bde /web
注意:容器的名称是唯一的。如果已经命名了一个叫 web 的容器,当你要再次使用 web 这个名称的时候,需要先用docker rm 来删除之前创建的同名容器。
在执行 docker run 的时候如果添加 –rm 标记,则容器在终止后会立刻删除。注意,–rm 和 -d 参数不能同时使用。
容器互联
使用 –link 参数可以让容器之间安全的进行交互。
下面先创建一个新的数据库容器。
$ sudo docker run -d --name db training/postgres
删除之前创建的 web 容器
$ docker rm -f web
然后创建一个新的 web 容器,并将它连接到 db 容器
$ sudo docker run -d -P --name web --link db:db training/webapp python app.py
此时,db 容器和 web 容器建立互联关系。
使用 docker ps 来查看容器的连接
$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 349169744e49 training/postgres:latest su postgres -c '/usr About a minute ago Up About a minute 5432/tcp db, web/db aed84ee21bde training/webapp:latest python app.py 16 hours ago Up 2 minutes 0.0.0.0:49154->5000/tcp web
可以看到自定义命名的容器,db 和 web,db 容器的 names 列有 db 也有 web/db。这表示 web 容器链接到 db 容器,web 容器将被允许访问 db 容器的信息。
Docker 在两个互联的容器之间创建了一个安全隧道,而且不用映射它们的端口到宿主主机上。在启动 db 容器的时候并没有使用 -p 和 -P 标记,从而避免了暴露数据库端口到外部网络上。
Docker 通过 2 种方式为容器公开连接信息:
- 环境变量
- 更新 /etc/hosts 文件
使用 env 命令来查看 web 容器的环境变量
$ sudo docker run --rm --name web2 --link db:db training/webapp env . . . DB_NAME=/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 文件
$ sudo docker run -t -i --rm --link db:db training/webapp /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 da ta 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 容器上。
9. 高级网络配置
本章将介绍 Docker 的一些高级网络配置和选项。
当 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 所有的网络定制配置。以及通过 Linux 命令来调整、补充、甚至替换 Docker 默认的网络配置。
9.1. 快速配置指南
下面是一个跟 Docker 网络相关的命令列表。
其中有些命令选项只有在 Docker 服务启动的时候才能配置,而且不能马上生效。
-b BRIDGE or –bridge=BRIDGE –指定容器挂载的网桥 –bip=CIDR –定制 docker0 的掩码 -H SOCKET… or –host=SOCKET… –Docker 服务端接收命令的通道 –icc=true|false –是否支持容器之间进行通信 –ip-forward=true|false –请看下文容器之间的通信 –iptables=true|false –禁止 Docker 添加 iptables 规则 –mtu=BYTES –容器网络中的 MTU
下面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 –映射容器所有端口到宿主主机
9.2. 配置 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 文件立刻得到更新。
如果用户想要手动指定容器的配置,可以利用下面的选项。
9.3. 容器访问控制
容器的访问控制,主要通过 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/0 0.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.2 172.17.0.3 tcp spt:80 ACCEPT tcp -- 172.17.0.3 172.17.0.2 tcp dpt:80 DROP all -- 0.0.0.0/0 0.0.0.0/0
注意:–link=CONTAINER_NAME:ALIAS 中的 CONTAINER_NAME 目前必须是 Docker 分配的名字,或使用 –name参数指定的名字。主机名则不会被识别。
9.4. 映射容器端口到宿主主机的实现
默认情况下,容器可以主动访问到外部网络的连接,但是外部网络无法访问到容器。
容器访问外部实现
容器所有到外部网络的连接,源地址都会被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/0 0.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/0 0.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 服务即可生效。
9.5. 配置 docker0 网桥
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 vethdda6
*注: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
9.6. 自定义网桥
除了默认的 docker0 网桥,用户也可以指定网桥来连接各个容器。
在启动 Docker 服务的时候,使用 -b BRIDGE或–bridge=BRIDGE 来指定使用的网桥。
如果服务已经运行,那需要先停止服务,并删除旧的网桥。
$ sudo service docker stop $ sudo ip link set dev docker0 down $ sudo brctl delbr docker0
然后创建一个网桥 bridge0。
$ sudo brctl addbr bridge0 $ sudo ip addr add 192.168.5.1/24 dev bridge0 $ sudo ip link set dev bridge0 up
查看确认网桥创建并启动。
$ 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 地址配置和路由信息。
9.7. 工具和示例
在介绍自定义网络拓扑之前,你可能会对一些外部工具和例子感兴趣:
pipework
Jér?me Petazzoni 编写了一个叫 pipework 的 shell 脚本,可以帮助用户在比较复杂的场景中完成容器的连接。
playground
Brandon Rhodes 创建了一个提供完整的 Docker 容器网络拓扑管理的 Python库,包括路由、NAT 防火墙;以及一些提供 HTTP, SMTP, POP, IMAP, Telnet, SSH, FTP 的服务器。
9.8. 编辑网络配置文件
Docker 1.2.0 开始支持在运行中的容器里编辑 /etc/hosts, /etc/hostname 和 /etc/resolve.conf 文件。
但是这些修改是临时的,只在运行的容器中保留,容器终止或重启后并不会被保存下来。也不会被 docker commit 提交。
9.9. 示例:创建一个点到点连接
默认情况下,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 来关闭容器之间的通信。