[toc]
实验5 Docker 网络管理
实验介绍
本次试验将了解并学习 Docker 网络管理相关知识,将会涉及容器端口映射,实现容器互联等知识点。
知识点
本节中,我们需要依次完成下面几项任务:
- docker 容器端口映射
- 自定义网络实现容器互联
- host 和 none 网络的使用
网络
在开始下面的内容之前,为了不出现命名上的冲突,也为了显示更为直观,首先需要将前面创建或启动的容器全部删除。
# 暂停所有运行中的容器
docker container ls -q | xargs docker container stop
# 删除所有的容器
docker container ls -aq | xargs docker container rm
当我们安装 Docker 后,默认会自动创建三个网络。我们可以使用下面的命令来查看这些网络:
docker network ls
如上图所示,三种默认的网络,分别为 bridge
,host
,none
。由于实验环境可能预置其他镜像,你也可以看到其他网络,但类别就这三种。
桥接网络
bridge 即桥接网络,在安装 Docker 后会创建一个桥接网络,该桥接网络的名称为 docker0
。我们可以通过下面两条命令去查看该值。
# 查看 bridge 网络的详细信息,并通过 grep 获取名称项
docker network inspect bridge | grep name
# 使用 ifconfig 查看 docker0 网络
ifconfig
在上图中,我们可以查看到对应的值。默认情况下,我们创建一个新的容器都会自动连接到 bridge 网络。使用 docker network inspect bridge
查看网桥网络的详细信息,结果如下所示:
可以看到 docker0
的默认网段 Subnet 项。我们可以尝试创建一个容器,该容器会自动连接到 bridge 网络,例如我们创建一个名为 shiyanlou001
的容器:
docker container run --name shiyanlou001 -itd ubuntu /bin/bash
上述命令中默认使用 --network bridge
,即指定 bridge 网络。创建后,再次查看 bridge 的信息:
这时可以查看到相应的容器的网络信息,该容器在连接到 bridge 网络后,会从子网的地址池中获得一个 IP 地址。
使用 docker container attach shiyanlou001
命令,也可查看相应的地址信息:
如果提示无
ifconfig
命令,可以在容器中执行apt update && apt install net-tools
安装。
并且对于连接到默认的 bridge 之间的容器可以通过 IP 地址互相通信(注意是IP地址,不能通过容器名)。例如我们启动一个 shiyanlou002
的容器,它可以与 shiyanlou001
通过 IP 地址进行通信。
如果提示无
ping
命令,可以在容器中执行apt update && apt install -y iputils-ping
安装。
其具体的实现原理可以参考链接 Linux 上的基础网络设备,以及涉及到 网桥的工作原理
上述的操作我们通过 ping
命令演示了 IP 相关的内容。但是对于应用程序来讲,如果需要在外部进行访问,我们还会涉及到端口的使用,而 Docker 对于 bridge 网络使用端口的方式为设置端口映射,通过 iptables
实现。
下面我们通过 iptables
来为大家演示在 docker 中实现端口映射的方式,主要针对 nat
表和 filter
表:
首先删除掉上面创建的两个容器,这里不再给出具体的命令。
此时,我们查看 nat
表的转发规则,使用如下命令:
sudo iptables -t nat -nvL
由于此时并未创建 docker 容器,nat 表中没有什么特殊的规则。接下来,我们使用之前在 Docker 镜像管理中构建的 shiyanlou:1.0
镜像创建一个容器 shiyanlou001
。构建镜像 shiyanlou:1.0
的 DockerFile 如下,具体的构建过程请参考之前的内容:
# 指定基础镜像
FROM ubuntu:14.04
# 维护者信息
MAINTAINER shiyanlou/shiyanlou001@simplecloud.cn
# 镜像操作命令
RUN \
apt-get -yqq update && \
apt-get install -yqq apache2
# 容器启动命令
CMD ["/usr/sbin/apache2ctl", "-D", "FOREGROUND"]
构建好 shiyanlou:1.0
镜像后,启动一个容器 shiyanlou001
,并将本机的端口 10001
映射到容器中的 80
端口上。
docker run -d -p 10001:80 --name shiyanlou001 shiyanlou:1.0
其中 docker container run
命令的 -p
参数是通过端口映射的方式,将容器的端口发布到主机的端口上。其使用格式为 -p ip:hostPort:containerPort
。并且还可以指定范围,例如 -p 10001-10100:1-100
,代表将容器 1-100
的端口映射到主机上的 10001-10100
端口上,两者一一对应。
创建成功后,我们可以在浏览器中输入 localhost:10001
访问到容器 shiyanlou001
的 apache
服务。
查看此时 iptables
中 nat
表和 filter
表的规则,其中分别新增了一条比较重要的内容,如下图所示:
自定义网络
对于默认的 bridge 网络来说,使用端口可以通过端口映射的方式来实现,并且在上面的内容中我们也演示了容器之间通过 IP 地址互相进行通信。但是对于默认的 bridge 网络来说,每次重启容器,容器的 IP 地址都是会发生变化的,因为对于默认的 bridge 网络来说,并不能在启动容器的时候指定 ip 地址,在启动单个容器时并不容易看到这一区别。
旧版的容器互联
容器间都是通过在 /etc/hosts
文件中添加相应的解析,通过容器名,别名,服务名等来识别需要通信的容器。
我们先把之前的 shiyanlou001
容器删除,这里不再给出具体的命令。
现在启动两个容器,来演示旧的容器互联:
首先启动一个名为 shiyanlou001
的容器,使用镜像 busybox
:
docker run -it --rm --name shiyanlou001 busybox /bin/sh
然后,打开一个新的终端,启动一个名为 shiyanlou002
的容器,并使用 --link
参数与容器 shiyanlou001
互联。
docker run -it --rm --name shiyanlou002 --link shiyanlou001 busybox /bin/sh
docker run 命令的 --link
参数的格式为 --link <name or id>:alias
。格式中的 name
为容器名,alias
为别名。即可以通过 alias
访问到该容器。
如下图所示,上面的环境为 shiyanlou001
,下面的为 shiyanlou002
:
如果此时 shiyanlou001
容器退出,这时我们启动一个 shiyanlou003
,再次启动一个 shiyanlou001
:
docker run -itd --name shiyanlou003 --rm busybox /bin/sh
docker run -it --name shiyanlou001 --rm busybox /bin/sh
按照顺序分配的原则,此时 shiyanlou003
的 IP 地址为 172.17.0.2
,容器 shiyanlou001
的 IP 地址为 172.17.0.4
。并且此时容器 shiyanlou002
中 /etc/hosts
文件的解析依旧不变,所以不能获取到正确的解析:
如上所示,旧的容器 shiyanlou002
通过 --link
连接到 shiyanlou001
。而在 shiyanlou001
重启后,由于 IP 地址的变化,此时 shiyanlou002
并不能正确的访问到 shiyanlou001
。
除了使用 --link
链接的方式来达到容器间互联的效果,在 Docker 中,容器间通信的推荐方式为自定义网络。
自定义网络
Docker 在安装时会默认创建一个桥接网络,除了使用默认网络之外,我们还可以创建自己的 bridge 或 overlay
网络。
如下所示,我们创建一个名为 network1
的桥接网络,简单命令如下:
docker network create network1
docker network ls
创建成功后,可以使用 ifconfig
或者 ip addr show
命令查看该桥接网络的网络接口信息,如下所示:
而对于该网络的详细信息可以通过 docker network inspect network1
命令来查看,如下图所示:
其相应的网络接口名称和子网都是由 docker 随机生成,当然,我们也可以手动指定:
# 首先删除掉刚刚创建的 network1
docker network rm network1
# 再次创建 network1,指定子网
docker network create -d bridge --subnet=192.168.16.0/24 --gateway=192.168.16.1 network1
此时,我们可以运行一个容器 shiyanlou001
(需要把之前创建运行的 shiyanlou001
容器删除),指定其网络为 network1
,使用 --network network1
:
docker run -it --name shiyanlou001 --network network1 --rm busybox /bin/sh
使用 exit
退出该容器使其自动删除,这时我们再次创建该容器,但是不指定其 --network
:
docker run -it --name shiyanlou001 --rm busybox /bin/sh
此时,该容器连接到默认的 bridge 网络,这时,可以新打开一个终端,在其中运行如下命令,将 shiyanlou001
连接到 network1
网络中:
# 在新打开的终端中运行,将容器 shiyanlou001 连接到 network1 网络中
docker network connect network1 shiyanlou001
这时再次在容器 shiyanlou001
中使用 ifconfig
命令:
如上图中所示,出现了一个 eth1
接口,此时,eth0
连接到默认的 bridge 网络,eth1
连接到 network1
网络。
对于自定义的网络来说,docker 嵌入的 DNS
服务支持连接到该网络的容器名的解析。这意味着连接到同一个网络的容器都可以通过容器名去 ping
另一个容器。
如下所示,启动两个容器,连接到 network1
:
docker run -itd --name shiyanlou_1 --network network1 --rm busybox /bin/sh
docker run -it --name shiyanlou_2 --network network1 --rm busybox /bin/sh
启动之后,由于上述的两个容器都是连接到 network1
网络,所以可以通过容器名 ping
通:
除此之外,在用户自定义的网络中,是可以通过 --ip
指定 IP 地址的,但在默认的 bridge 网络不能指定 IP 地址:
# 连接到 network1 网络,运行成功
docker run -it --network network1 --ip 192.168.16.100 --rm busybox /bin/sh
# 连接到默认的 bridge 网络,下面的命令运行失败
docker run -it --rm busybox --ip 192.168.16.100 --rm busybox /bin/sh
host 和 none
当容器的网络为 host
时,容器可以直接访问主机上的网络。
例如,我们启动一个容器,指定网络为 host
:
docker run -it --network host --rm busybox /bin/sh
如下所示,该容器可以直接访问主机上的网络:
none
网络,容器中不提供其它网络接口。none 网络的容器创建之后还可以自己 connect 一个网络,比如使用 docker network connect bridge 容器名
可以将这个容器添加到 bridge 网络中。
docker run -it --network none --rm busybox /bin/sh
实验总结
本节实验中我们学习了以下内容:
- docker 容器端口映射
- 自定义网络实现容器互联
- host 和 none 网络的使用
请务必保证自己能够动手完成整个实验,只看文字很简单,真正操作的时候会遇到各种各样的问题,解决问题的过程才是收获的过程。
实验6 编写 DockerFile
实验介绍
在前面的实验中我们多次用到的 Dockerfile,在本实验里我们将通过完成一个实例来学习 Dockerfile 的编写。
知识点
本节中,我们需要依次完成下面两项任务:
- Dockerfile 基本语法
- Dockerfile 创建镜像流程
Dockerfile
Dockerfile 是一个文本文件,其中包含我们为了构建 Docker 镜像而手动执行的所有命令。Docker 可以从 Dockerfile 中读取指令来自动构建镜像。我们可以使用 docker build
命令来创建一个自动构建。
上下文
在 Docker 容器及镜像管理一节中我们有提到构建镜像的一些知识。
构建镜像时,该过程的第一件事是将 Dockerfile 文件所在目录下的所有内容递归的发送到守护进程。所以在大多数情况下,最好是创建一个新的目录,在其中保存 Dockerfile,并在其中添加构建 Dockerfile 所需的文件。而 Dockerfile 文件所在的路径也被称为上下文(context)。首先创建一个目录,以便开始后面的实验过程:
mkdir dir1 && cd dir1
下面我们简单介绍 Dockerfile 中常用的指令。
FROM
使用 FROM 指令指定一个基础镜像,后续指令将在此镜像的基础上运行:
FROM ubuntu:14.04
USER
在 Dockerfile 中可以指定一个用户,后续的 RUN
,CMD
以及 ENTRYPOINT
指令都会使用该用户去执行,但是该用户必须提前存在。
USER shiyanlou
WORKDIR
除了指定用户之外,还可以使用 WORKDIR
指定工作目录,对于 RUN
,CMD
,COPY
,ADD
指令将会在指定的工作目录中去执行。也可以理解为命令执行时的当前目录。
WORKDIR /
RUN,CMD,ENTRYPOINT
RUN 指令用于执行命令,该指令有两种形式:
RUN <command>
,使用 shell 去执行指定的命令command
,一般默认的 shell 为/bin/sh -c
。RUN ["executable", "param1", "param2", ...]
,使用可执行的文件或程序executable
,给予相应的参数param
。
例如我们执行更新命令:
RUN apt-get update
CMD 的使用方式跟 RUN 类似,不过在一个 Dockerfile 文件中只能有一个 CMD 指令,如果有多个 CMD 指令,则只有最后一个会生效。该指令为我们运行容器时提供默认的命令,例如:
CMD echo "hello shiyanlou"
在构建镜像时使用了上面的 CMD
指令,则可以直接使用 docker run image
,该命令等同于 docker run image echo "hello shiyanlou"
。即作为默认执行容器时默认使用的命令,也可在 docker run
中指定需要运行的命令来覆盖默认的 CMD
指令。
除此之外,该指令还有一种特殊的用法,在 Dockerfile 中,如果使用了 ENTRYPOINT 指令,则 CMD 指令的值会作为 ENTRYPOINT 指令的参数:
CMD ["param1", "param2"]
ENTRYPOINT 指令会覆盖 CMD 指令作为容器运行时的默认指令,并且不会在 docker run
时被覆盖,如下示例:
FROM ubuntu:latest
ENTRYPOINT ["ls", "-a"]
CMD ["-l"]
上述构建的镜像,在我们使用 docker run <image>
时等同于 docker run <image> ls -a -l
命令。使用 docker run <image> -i -s
命令等同于 docker run <image> ls -a -i -s
指令。即 CMD 指令的值会被当作 ENTRYPOINT 指令的参数附加到 ENTRYPOINT 指令的后面,并且如果 docker run
中指定了参数,会覆盖 CMD
中给出的参数。
COPY 和 ADD
COPY 和 ADD 都用于将文件,目录等复制到镜像中。使用方式如下:
ADD <src>... <dest>
ADD ["<SRC>",... "<dest>"]
COPY <src>... <dest>
COPY ["<src>",... "<dest>"]
<src>
可以指定多个,但是其路径不能超出上下文的路径,即必须在跟 Dockerfile 同级或子目录中。
<dest>
不需要预先存在,不存在路径时会自动创建,如果没有使用绝对路径,则 <dest>
为相对于工作目录的相对路径。
COPY 和 ADD 的不同之处在于,ADD 可以添加远程路径的文件,并且 <src>
为可识别的压缩格式,如 gzip 或 tar 归档文件等,ADD 会自动将其解压缩为目录。
ENV
ENV 指令用于设置环境变量:
ENV <key> <value>
ENV <key>=<value> <key>=<value>...
VOLUME
VOLUME 指令将会创建指定的挂载目录,在容器运行时,将创建相应的匿名卷:
VOLUME /data1 /data2
上述指令将会在容器运行时,创建两个匿名卷,并挂载到容器中的 /data1
和 /data2
目录上。
EXPOSE
EXPOSE 指定在容器运行时监听指定的网络端口,它与 docker run
命令的 -p
参数不一样,并不实际映射端口,只是将该端口暴露出来,允许外部或其它的容器进行访问(???)。
个人认为EXPOSE仅仅只是声明一个端口,可以在run时参数加-P的时候被映射到宿主机的随机端口,如果没有在run的时候使用-P或-p映射,并不会被外部访问。如果没有EXPOSE暴露端口,run的时候使用-p也是可以开放端口的
要将容器端口暴露出来,需要在 dcoker run
命令中使用 -p
或者 --publish
参数。如果采用 -P
随机映射端口的方式,Docker 会将在 DockerFile 中声明的所有 EXPOSE 的端口随机映射。
EXPOSE port
从 Dockerfile 创建镜像
了解了上面一些常用于构建 Dockerfile 的指令之后,可以通过这些指令来构建一个镜像。如下所示,搭建一个 ssh 服务:
# 指定基础镜像
FROM ubuntu:14.04
# 安装软件
RUN apt-get update && apt-get install -y openssh-server && mkdir /var/run/sshd
# 添加用户 shiyanlou 及设定密码
RUN useradd -g root -G sudo shiyanlou && echo "shiyanlou:123456" | chpasswd shiyanlou
# 暴露 SSH 端口
EXPOSE 22
CMD ["/usr/sbin/sshd", "-D"]
首先,我们在之前创建的一个空目录 dir1
中编辑 Dockerfile 文件,并将上面的内容复制到该文件中,相关的命令如下所示:
# 创建目录
mkdir dir1 && cd dir1
# 编辑 Dockerfile,将上面的内容写入
vim Dockerfile
# 最后执行构建命令
docker build -t sshd:test .
在上面的命令执行完成之后,该镜像就构建成功了,直接使用该镜像启动一个容器就可以运行一个 ssh 的服务,如下所示:
docker run -itd -p 10001:22 sshd:test
这时就可以通过公网的 IP 地址,以及端口 10001,并且使用用户 shiyanlou
,密码 123456
,远程通过 ssh
连接到该容器中了。
这里我们使用回环地址来进行测试,即自己请求自己的 ssh 连接。
首先安装 openssh 客户端,对应的命令为 apt-get install openssh-client
。然后连接本机的 ssh-server,使用的命令为 ssh -p 10001 shiyanlou@127.0.0.1
,这里的 -p 10001
即使用端口 10001,也就是我们刚刚映射的端口。
实验的结果如下图,可以看到,我们成功连接到了容器。
实验总结
本节实验中我们学习了以下内容:
- Dockerfile 基本语法
- Dockerfile 创建镜像流程
请务必保证自己能够动手完成整个实验,只看文字很简单,真正操作的时候会遇到各种各样的问题,解决问题的过程才是收获的过程。