实验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

此处输入图片的描述

如上图所示,三种默认的网络,分别为 bridgehostnone。由于实验环境可能预置其他镜像,你也可以看到其他网络,但类别就这三种。

桥接网络

bridge 即桥接网络,在安装 Docker 后会创建一个桥接网络,该桥接网络的名称为 docker0。我们可以通过下面两条命令去查看该值。

# 查看 bridge 网络的详细信息,并通过 grep 获取名称项
docker network inspect bridge | grep name

# 使用 ifconfig 查看 docker0 网络
ifconfig

image

在上图中,我们可以查看到对应的值。默认情况下,我们创建一个新的容器都会自动连接到 bridge 网络。使用 docker network inspect bridge 查看网桥网络的详细信息,结果如下所示:

image

可以看到 docker0 的默认网段 Subnet 项。我们可以尝试创建一个容器,该容器会自动连接到 bridge 网络,例如我们创建一个名为 shiyanlou001 的容器:

docker container run --name shiyanlou001 -itd ubuntu /bin/bash

上述命令中默认使用 --network bridge ,即指定 bridge 网络。创建后,再次查看 bridge 的信息:

image

这时可以查看到相应的容器的网络信息,该容器在连接到 bridge 网络后,会从子网的地址池中获得一个 IP 地址。

使用 docker container attach shiyanlou001 命令,也可查看相应的地址信息:

如果提示无 ifconfig 命令,可以在容器中执行 apt update && apt install net-tools 安装。

image

并且对于连接到默认的 bridge 之间的容器可以通过 IP 地址互相通信注意是IP地址,不能通过容器名)。例如我们启动一个 shiyanlou002 的容器,它可以与 shiyanlou001 通过 IP 地址进行通信。

如果提示无 ping 命令,可以在容器中执行 apt update && apt install -y iputils-ping 安装。

image

其具体的实现原理可以参考链接 Linux 上的基础网络设备,以及涉及到 网桥的工作原理

上述的操作我们通过 ping 命令演示了 IP 相关的内容。但是对于应用程序来讲,如果需要在外部进行访问,我们还会涉及到端口的使用,而 Docker 对于 bridge 网络使用端口的方式为设置端口映射,通过 iptables 实现。

下面我们通过 iptables 来为大家演示在 docker 中实现端口映射的方式,主要针对 nat 表和 filter 表:

首先删除掉上面创建的两个容器,这里不再给出具体的命令。

此时,我们查看 nat 表的转发规则,使用如下命令:

sudo iptables -t nat -nvL

image

由于此时并未创建 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 访问到容器 shiyanlou001apache 服务。

图片描述

查看此时 iptablesnat 表和 filter 表的规则,其中分别新增了一条比较重要的内容,如下图所示:

image

image

自定义网络

对于默认的 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

image

image

如果此时 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 文件的解析依旧不变,所以不能获取到正确的解析:

image

image

如上所示,旧的容器 shiyanlou002 通过 --link 连接到 shiyanlou001。而在 shiyanlou001 重启后,由于 IP 地址的变化,此时 shiyanlou002 并不能正确的访问到 shiyanlou001

除了使用 --link 链接的方式来达到容器间互联的效果,在 Docker 中,容器间通信的推荐方式为自定义网络。

自定义网络

Docker 在安装时会默认创建一个桥接网络,除了使用默认网络之外,我们还可以创建自己的 bridge 或 overlay 网络。

如下所示,我们创建一个名为 network1 的桥接网络,简单命令如下:

docker network create network1
docker network ls

创建成功后,可以使用 ifconfig 或者 ip addr show 命令查看该桥接网络的网络接口信息,如下所示:

image

而对于该网络的详细信息可以通过 docker network inspect network1 命令来查看,如下图所示:

image

其相应的网络接口名称和子网都是由 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

image

使用 exit 退出该容器使其自动删除,这时我们再次创建该容器,但是不指定其 --network

docker run -it --name shiyanlou001 --rm busybox /bin/sh

image

此时,该容器连接到默认的 bridge 网络,这时,可以新打开一个终端,在其中运行如下命令,将 shiyanlou001 连接到 network1 网络中:

# 在新打开的终端中运行,将容器 shiyanlou001 连接到 network1 网络中
docker network connect network1 shiyanlou001

这时再次在容器 shiyanlou001 中使用 ifconfig 命令:

image

如上图中所示,出现了一个 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 通:

image

除此之外,在用户自定义的网络中,是可以通过 --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

image

host 和 none

当容器的网络为 host 时,容器可以直接访问主机上的网络。

例如,我们启动一个容器,指定网络为 host

docker run -it --network host --rm busybox /bin/sh

如下所示,该容器可以直接访问主机上的网络:

image

none 网络,容器中不提供其它网络接口。none 网络的容器创建之后还可以自己 connect 一个网络,比如使用 docker network connect bridge 容器名 可以将这个容器添加到 bridge 网络中。

docker run -it --network none --rm busybox /bin/sh

image

实验总结

本节实验中我们学习了以下内容:

  • 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 中可以指定一个用户,后续的 RUNCMD 以及 ENTRYPOINT 指令都会使用该用户去执行,但是该用户必须提前存在。

USER shiyanlou

WORKDIR

除了指定用户之外,还可以使用 WORKDIR 指定工作目录,对于 RUNCMDCOPYADD 指令将会在指定的工作目录中去执行。也可以理解为命令执行时的当前目录。

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也是可以开放端口的

image-20220419133721232

要将容器端口暴露出来,需要在 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 创建镜像流程

请务必保证自己能够动手完成整个实验,只看文字很简单,真正操作的时候会遇到各种各样的问题,解决问题的过程才是收获的过程。


昨日的月光悄然退场,曦阳已经渐亮