嘘~ 正在从服务器偷取页面 . . .

Docker 学习


0. 背景

0.1 虚拟化技术的弊端

随着硬件的大力发展,除了运行一些大型软件之外,大部分情况硬件都处于空闲状态。除此之外,在一些特殊的开发情况下,可能需要使用不同版本的环境进行测试。有些甚至不能使用 Windows 系统,必须使用 Linux 系统。虽然 Windows 已经推出 Linux 的子系统——WSL,但是在不同环境之间的切换是个很大的问题,特别是在一台电脑上配置多环境的情况下。

这样硬件虚拟化技术就出现了,让用户能以单个物理硬件系统为基础创建多个模拟环境或专用资源。称为“Hypervisor” (虚拟机监控程序)的软件可直接连接到硬件,从而将一个系统划分为不同的、单独安全环境,即虚拟机(VM)(也可以称为,Guest OS)。这样我们就能从原有的系统中分配出多个可监控的,安全的子系统。

							 +-----+-----+-----+-----+
                             |App A|App B|App C|App D|
+-----+-----+-----+-----+    +-----+-----+-----+-----+
|App A|App B|App C|App D|    |Guest|Guest|Guest|Guest|
+-----+-----+-----+-----+    | OS0 | OS1 | OS2 | OS3 |
|Guest|Guest|Guest|Guest|    +-----+-----+-----+-----+
| OS0 | OS1 | OS2 | OS3 |    |        Hypervisor     |
+-----+-----+-----+-----+    +-----------------------+
|        Hypervisor     |    |         Host OS       |
+-----------------------+    +-----------------------+
|        Hardware       |    |        Hardware       |
+-----------------------+    +-----------------------+
          Type I                       Type II

Type I 是 Hypervisor 直接访问硬件资源,通常会有另一个操作系统运行于 Hypervisor 之上来对硬件资源,例如 VMware EXSi,Windows的Hyper-V,Linux 的 Xen;Type II 是 Hypervisor 和普通的应用一样,运行在某个操作系统(例如 Windows 或者 Linux 等,这里称之为宿主机操作系统,Host OS)之上,Hypervisor 通过 Host OS 访问硬件资源,例如 VMware Workstation,Virtual Box。

虚拟机的一个缺点在于 Guest OS 通常会占用不少硬件资源,并且性能损失的情况也比较严重。虽然不少 Hypervisor支持 动态内存,但基本上都会降低虚拟机的性能。如果说这样的资源占用少量的虚拟机还可以接受的话,同时运行十数台数十台虚拟机的时候,浪费的硬件资源就相当可观了。通常来说,其中相当大部分甚至全部 Guest OS 都是相同的。

0.2 Docker 问世

+-----+-----+-----+-----+                                   +-----+-----+-----+-----+
|App A|App B|App C|App D|     +-----+-----+-----+-----+     |App A|App B|App C|App D|
+-----+-----+-----+-----+     |App A|App B|App C|App D|     +-----+-----+-----+-----+
|+---------------------+|     +-----+-----+-----+-----+     |Guest|Guest|Guest|Guest|
||   Runtime Library   ||     |Lib A|Lib B|Lib C|Lib D|     | OS0 | OS1 | OS2 | OS3 |
|+---------------------+|     +-----+-----+-----+-----+     +-----+-----+-----+-----+
||       Kernel        ||     |    Container Engine   |     |        Hypervisor     |
|+---------------------+|     +-----------------------+     +-----------------------+
|   Operating System    |     |         Host OS       |     |         Host OS       |
+-----------------------+     +-----------------------+     +-----------------------+
|       Hardware        |     |        Hardware       |     |        Hardware       |
+-----------------------+     +-----------------------+     +-----------------------+
    Physical Machine                  Container                 Type II Hypervisor

上图中,每一个App和Lib的组合,就是一个容器。也就是Docker图标里面的一个集装箱。和虚拟机相比,容器有以下优点:

  1. 迅速启动:没有虚拟机硬件的初始化,没有Guest OS的启动过程,可以节约很多启动时间,这就是容器的“开箱即用”;
  2. 占用资源少:没有运行Guest OS所需的内存开销,无需为虚拟机预留运行内存,无需安装、运行App不需要的运行库/操作系统服务,内存占用、存储空间占用都小的多。相同配置的服务器,如果运行虚拟机只能运行十多台的,通常可以运行上百个容器毫无压力——当然前提是单个容器应用本身不会消耗太多资源。

当然,因为 Docker 公用内核的原因,只靠 cgroup 隔离,如果某一个容器导致内核崩溃,那么所有的容器都会崩溃。而对于彻底隔离应用的虚拟机,除非硬件或者 Hypervisor 出现问题,否则不会出现崩溃的问题。

当然如果还是不懂,可以拿别墅、公寓和胶囊式公寓类比(bushi)。

0.3 Docker 特性

有人说,Docker 的出现就像集装箱一样,所谓的穿着马甲的“标准化”。想要搞懂 Docker,需要明白它的两句口号。

  1. Build, Ship and Run 搭建、发送、运行三板斧

  2. Build once,Run anywhere 一次构建,随处运行

从这个口号我们就能够得到几个信息:

  1. 使用 Docker 时候,我们就可以避免配置文件的问题;
  2. 我们可以构建自己的镜像,并且将其打包到 Docker hub 上。这样别人就可以使用你的镜像了;
  3. 运行的容器可以随时进行更改或者停止。

Docker 技术的核心概念分别是

镜像(Image)别人存放好文档/环境的地方,只需要在 Docker hub 搜索并且下载,就即将可以使用。和 GitHub 共享代码类似,别人也会共享自己配置的镜像;

容器(Container)实现具体操作程序的地方,将本地的镜像放在容器中运行;

仓库(Repository) 自己本地下载别人的镜像,也可以把自己打包的镜像上传到 Docker hub。

就以运维中举例,我们可能会看到多种语言写出来的各种各样的东西,并且可能还有历史遗留的不同版本导致的错误。

这个时候,标准化管理显得尤其重要,这个时候就需要一个统一的操作方法。就算你只用 PHP/Java 写程序,PHP/JDK 的版本不同,加上 SQL 的不同,使用容器的不同,Nginx 或者 Apache,甚至有不知名的 Web 容器,还有自己写的 Web 容器。这样你就不可能在原生的 Linux 服务器中管理多个版本的环境。

0.4 安装 Docker

在 Windows 中,因为 Docker 需要的 Linux 内核没有,所以需要提前安装 WSL 作为 Docker 的内核。

对于 Linux,官方提供一键安装的脚本:

curl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun

# 推荐使用国内 daocloud 一键安装命令
curl -sSL https://get.daocloud.io/docker | sh

# 如果存在旧版本需要提前卸载
sudo apt-get remove docker docker-engine docker.io containerd runc

安装成功之后我们还需要配置 Docker 仓库和 Docker Engine-Community。

# 更新 apt 包索引
sudo apt-get update

# 安装 apt 依赖包,用于通过 HTTPS 来获取仓库
sudo apt-get install \
apt-transport-https \
ca-certificates \
curl \
gnupg-agent \
software-properties-common

# 添加 Docker 的官方 GPG 密钥
curl -fsSL https://mirrors.ustc.edu.cn/docker-ce/linux/ubuntu/gpg | sudo apt-key add -

# 9DC8 5822 9FC7 DD38 854A E2D8 8D81 803C 0EBF CD88 
# 通过搜索指纹的后 8 个字符,验证现在是否拥有带有指纹的密钥
sudo apt-key fingerprint 0EBFCD88

# 设置稳定版仓库
sudo add-apt-repository \
"deb [arch=amd64] https://mirrors.ustc.edu.cn/docker-ce/linux/ubuntu/ \
$(lsb_release -cs) \
stable"

# 安装最新版本的 Docker Engine-Community 和 containerd
sudo apt-get install docker-ce docker-ce-cli containerd.io

1. Docker 镜像

在 Linux 上运行 Docker 需要 root 权限,千万注意。除此之外,Docker 是在外网的服务器上,所以需要进行换源,具体可以参考这个网站

+---------+  +---------+  +---------+    +-----+ +-----+ +-----+
| abc.com |  | def.com |  | xyz.com |    | DB1 | | DB2 | | DB3 |    
+----+----+  +----+----+  +----+----+    +--+--+ +--+--+ +--+--+    
     |            |            |            |       |       |
+----+----+  +----+----+  +----+----+    +--+--+ +--+--+ +--+--+    
| abc.com |  | def.com |  | xyz.com |    | DB1 | | DB2 | | DB3 |
| config  |  | config  |  | config  |    | conf| | conf| | conf|
|  data   |  |  data   |  |  data   |    | data| | data| | data|
+----+----+  +----+----+  +----+----+    +--+--+ +--+--+ +--+--+
     |            |            |            |       |       |
     +------------+------------+            +-------+-------+
                  |                                 |
           +------+------+                   +------+------+          
           | Nginx Image |                   | MySQL Image |
           +------+------+                   +------+------+
                  |                                 |
                  +----------------+----------------+
                                   |
                            +------+-------+ 
                            | Alpine Image |
                            +------+-------+

从这里我们可以看到,Docker 的镜像可以在某个基础上再进行分装,对于我们使用 jar 包项目,我们可以使用 Dockerfile 进行配置。下面的内容会有说到如何使用 Dockerfile 打包一个 Java 项目。

镜像操作

1.1 拉取镜像

我们可以使用pull命令对一个镜像进行拉取

对于同一个镜像,都有一个 tag 对其进行区分,一般都是作为不同版本的管理。对于官方镜像。如果没有指定拉取的 tag,则会默认为 latest。

#默认会拉取最新版本的镜像
docker pull ubuntu
Using default tag:latest

#拉取18.04版本的 ubuntu
docker pull ubuntu:18.04

pull 命令中存在的子选项:

-a:是否获取仓库中的所有镜像,默认为否;

–disable-content-trust:取消镜像的内容校验,默认开启。

1.2 查看镜像

对于已经下载下来的镜像,我们可以使用docker images进行查看。

查看结果

images 命令存在的子选项:

-a:列出所有的镜像文件(包括临时文件),默认为否;

-q:仅输出 ID 信息,默认为否;

-f:过虑列出的镜像。

如果我们需要特别使用某个镜像,可以使用docker tag命令为本地镜像任意添加新的标签。

此时我们就获得了一个 myubuntu:0.1

如果你需要一个镜像的具体信息,可以使用docker inspect,会显示出包括创建时间、仓库 ID 等详细信息。

docker inspect ubuntu
#result
[
    {
        "Id": "sha256:54c9d81cbb440897908abdcaa98674db83444636c300170cfd211e40a66f704f",
        "RepoTags": [
            "chenxinyang621/ubuntu:0.1",
            "myubuntu:0.1",
            "ubuntu:latest"
        ],
        "RepoDigests": [
            "chenxinyang621/ubuntu@sha256:7c9c7fed23def3653a0da5bc9ecb651efe155ebd5802c7ba5d585edaa6c89496",
            "ubuntu@sha256:669e010b58baf5beb2836b253c1fd5768333f0d1dbcb834f7c07a4dc93f474be"
        ],
        "Parent": "",
        "Comment": "",
        "Created": "2022-02-02T02:14:46.177066251Z",
        "Container": "3d4cc5cf7dc1af55a2be4440b5be4f96ea35516b98407a9b9446c218bb43818a",
        "ContainerConfig": {
            "Hostname": "3d4cc5cf7dc1",
            "Domainname": "",
            "User": "",
            "AttachStdin": false,
            "AttachStdout": false,
            "AttachStderr": false,
            "Tty": false,
            "OpenStdin": false,
            "StdinOnce": false,
            "Env": [
                "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
            ],
            "Cmd": [
                "/bin/sh",
                "-c",
                "#(nop) ",
                "CMD [\"bash\"]"
            ],
            "Image": "sha256:b7032458728a84cd355ae42a8f7b323e29af86a22b211f4701363191b25fa805",
            "Volumes": null,
            "WorkingDir": "",
            "Entrypoint": null,
            "OnBuild": null,
            "Labels": {}
        },
        "DockerVersion": "20.10.7",
        "Author": "",
        "Config": {
            "Hostname": "",
            "Domainname": "",
            "User": "",
            "AttachStdin": false,
            "AttachStdout": false,
            "AttachStderr": false,
            "Tty": false,
            "OpenStdin": false,
            "StdinOnce": false,
            "Env": [
                "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
            ],
            "Cmd": [
                "bash"
            ],
            "Image": "sha256:b7032458728a84cd355ae42a8f7b323e29af86a22b211f4701363191b25fa805",
            "Volumes": null,
            "WorkingDir": "",
            "Entrypoint": null,
            "OnBuild": null,
            "Labels": null
        },
        "Architecture": "amd64",
        "Os": "linux",
        "Size": 72775208,
        "VirtualSize": 72775208,
        "GraphDriver": {
            "Data": {
                "MergedDir": "/var/lib/docker/overlay2/2438f9a3ea62d8d4b5f545240e0f21d299807845882f742eddf9042276e618b0/merged",
                "UpperDir": "/var/lib/docker/overlay2/2438f9a3ea62d8d4b5f545240e0f21d299807845882f742eddf9042276e618b0/diff",
                "WorkDir": "/var/lib/docker/overlay2/2438f9a3ea62d8d4b5f545240e0f21d299807845882f742eddf9042276e618b0/work"
            },
            "Name": "overlay2"
        },
        "RootFS": {
            "Type": "layers",
            "Layers": [
                "sha256:36ffdceb4c77bf34325fb695e64ea447f688797f2f1e3af224c29593310578d2"
            ]
        },
        "Metadata": {
            "LastTagTime": "2022-03-04T05:21:55.8145906Z"
        }
    }
]

1.3 搜索镜像

当然,我们可能需要确认我们需要的工具是否存在对应的 Docker 镜像,这个时候就要使用docker search

search 的子选项:

-f:过滤输出内容;

–limit:限制输出结果个数,默认为25个;

–no-trunc:不截断输出结果。

#查询所有 nginx 有关的镜像
docker search nginx

#限制查询10个
docker search --limit 10 nginx

#只查询官方的 nginx 镜像
docker search -f is-official=true nginx

查询结果

1.4 删除镜像

镜像删除使用docker rmi/docker image rm命令。当然如果是在 Windows 上的 Docker 已经提供可视化操作平台,但是我们还是要了解对应的命令。

不管是对于之后的容器删除还是现在的镜像删除,都有两种删除方式:

  1. 使用名称(+标签)删除。每个容器和镜像都有对应的名称,可以使用这个进行区分并进行删除;
  2. 使用 ID 删除,ID 是唯一值,所以使用 ID 也能进行删除操作(P.S. 可以不用输入完整 ID,只要能辨别出是唯一值即可)。

rmi 的子选项:

-f:强制删除镜像,即使存在容器依赖;

-no-prune:不清理未带标签的父镜像。

对于一些没有被使用的镜像,以及产生的残留临时镜像文件,可以使用docker image prune进行清除。

prune 的子选项:

-a:删除所有无用镜像,不只是临时镜像;

-filter:对清理对象进行过滤;

-f:强制删除镜像,不进行提示确认。

2. Docker 容器

如果认为虚拟机是模拟运行的一整套操作系统(包括内核 应用运行态环境和其他系统 环境)和跑在上面的应用 那么 Docker 容器就是独立运行的一个(或一组)应用,以及它们必需的运行环境。

Docker 容器安全问题

2.1 简单的例子

Docker 允许你在容器内运行应用程序, 使用docker run命令来在容器内运行一个应用程序(容器)。当然run其实包含了一个create命令用来创建一个新的容器。

docker run ubuntu:15.10 /bin/echo "Hello world"
Hello world

# 存在镜像之后,就不需要指定 tag 了
docker run ubuntu /bin/echo "Hello world"

docker:Docker 的二进制执行文件;

run:与前面的 Docker 组合来运行一个容器;

ubuntu:15.10:指定要运行的镜像,Docker 首先从本地主机上查找镜像是否存在,如果不存在,Docker 就会从镜像仓库 Docker Hub 下载公共镜像。如果没有指定对应 tag,会使用默认的 latest,如果仓库中不存在 latest 的 tag,命令就会失败;

/bin/echo “Hello world”: 在启动的容器里执行的命令。

当使用 docker run 创建并启动容器时,Docker 后台执行的流程为:

  • Docker 会检查本地是否存在 ubuntu:15.10 镜像,如果镜像不存在则从 Docker Hub 拉取 ubuntu:15.10 镜像;
  • 使用 ubuntu:15.10 镜像创建并启动一个容器;
  • 分配文件系统,并且在镜像只读层外创建一个读写层;
  • 从 Docker IP 池中分配一个 IP 给容器;
  • 执行用户的启动命令运行镜像。

2.2 基本命令

2.2.1 开启

#先拉取一个 ubuntu 的镜像
docker pull ubuntu

#创建一个新的容器
docker create --name ubuntu1 -it ubuntu

#运行容器
docker start ubuntu1

#创建并运行一个容器
docker run --name ubuntu2 -it -d --restart=always ubuntu

-t:在新容器内指定一个伪终端或终端;

-i:允许你对容器内的标准输入 (STDIN) 进行交互;

–name:指定容器的名称,否则 Docker 会随机生成一段字符串;

-d:让容器在启动在启动时在后台运行;

–restart=always:设置容器随着 Docker 自启动。

之后我们可以使用 Ctrl + d/exit/Ctrl + w + d 退出容器,回到宿主机中。

2.2.2 停止容器

可以使用docker pause来暂停一个运行中的容器,使用docker pause来解除暂停一个运行中的容器。

如果需要终止一个容器,我们使用docker stop

docker ps -a让我们可以查看所有容器(默认只能查看运行中的容器);docker ps -qa只会显示 ID。

2.2.3 进入容器

有些时候我们需要进入容器修改一些设置,或者进行一些操作,我们需要使用exec

#参数的设置
docker exec -it ubuntu1 bash

-e:指定环境变量列表(在 MySQL Docker 配置中会有用到);

-u:指明执行的用户名或 ID。

exec 的方式进入容器,会单独启动一个 sh 进程,每个窗口都是独立且互不干扰的,也是使用最多的一种方式。

2.2.4 导出/导出

docker export busybox > busybox.tar
# 在当前文件夹下生成 busybox.tar 文件

docker import busybox.tar busybox:test
# busybox.tar 被导入成为新的镜像,镜像名称为 busybox:test

2.2.5 网络端口映射

在运行容器的过程中,我们需要对其绑定一个端口(Redis/MySQL/MinIO 的 Docker 操作就必需要这个步骤)

  • -P :**是容器内部端口随机**映射到主机的高端口;
  • -p :**是容器内部端口绑定到指定**的主机端口。

在绑定端口之前,还可以指定需要映射的地址,比如主机的127.0.0.1。

docker port命令可以让我们快捷地查看端口的绑定情况(绑定的端口默认都是 TCP,可以在后面使用 /UDP 绑定为 UDP )。

2.2.6 挂载

Docker 容器中的文件如果不做处理,会在容器消失时同时消失。为了持久化,需要将容器内的文件映射到宿主机上,这个过程被称为挂载。

通过 docker run -v 这样的形式,实现容器内文件挂载到宿主机,或者实体文件映射到容器内部。

2.3 解决 docker ps 折行问题

docker ps 默认的显示内容过多,当值过长时就会导致折行,可读性很差,所以希望只显示自己关心的某些列。

可以自己指定显示的模板,例如:

docker ps --format "table {{.ID}}\t{{.Names}}\t{{.Ports}}"

可用的占位符:

名称 含义
.ID 容器ID
.Image 镜像ID
.Command 执行的命令
.CreatedAt 容器创建时间
.RunningFor 运行时长
.Ports 暴露的端口
.Status 容器状态
.Names 容器名称
.Label 分配给容器的所有标签
.Mounts 容器挂载的卷
.Networks 容器所用的网络名称

2.4 生成自制镜像

使用 docker login 命令登录镜像服务器。

docker login 命令默认会请求 Docker Hub,对于第三方镜像仓库比如 Coding 或者阿里云镜像服务,在 docker login 后面加上注册服务器即可。在 Coding 中会有对应令牌。

在本地镜像推送到自定义仓库前,我们需要先把镜像“重命名”一下,才能正确推送到自己创建的镜像仓库中,使用docker tag命令将镜像“重命名”:

docker tag busybox lagoudocker/busybox

镜像“重命名”后使用docker push命令就可以推送镜像到自己创建的仓库中了。

2.5 容器监控

使用 Docker 自带的 docker stats 命令可以很方便地看到主机上所有容器的 CPU、内存、网络 IO、磁盘 IO、PID 等资源的使用情况。

docker status mq

rabbitmq 容器运行情况

2.5.1 cAdvisor

cAdvisor 是谷歌开源的一款通用的容器监控解决方案。cAdvisor 官方提供了 Docker 镜像。

docker run \
  --volume=/:/rootfs:ro \
  --volume=/var/run:/var/run:ro \
  --volume=/sys:/sys:ro \
  --volume=/var/lib/docker/:/var/lib/docker:ro \
  --volume=/dev/disk/:/dev/disk:ro \
  --publish=8080:8080 \
  --detach=true \
  --name=cadvisor \
  --privileged \
  --device=/dev/kmsg \
  lagoudocker/cadvisor:v0.37.0

通过 cAdvisor 我们可以查看对应容器的负载情况。

2.5.2 资源隔离

Docker 是基于 Namespace、Cgroups 和联合文件系统实现的。其中 Cgroups 不仅可以用于容器资源的限制,还可以提供容器的资源使用率。无论何种监控方案的实现,底层数据都来源于 Cgroups。

Namespace 是 Linux 内核的一项功能,该功能对内核资源进行分区,以使一组进程看到一组资源,而另一组进程看到另一组资源。Namespace 的工作方式通过为一组资源和进程设置相同的 Namespace 而起作用,但是这些 Namespace 引用了不同的资源。资源可能存在于多个 Namespace 中。这些资源可以是进程 ID、主机名、用户 ID、文件名、与网络访问相关的名称和进程间通信。

Cgroups 的工作目录为 /sys/fs/cgroup/sys/fs/cgroup 目录下包含了 Cgroups 的所有内容。Cgroups包含很多子系统,可以用来对不同的资源进行限制。例如对CPU、内存、PID、磁盘 IO等资源进行限制和监控。

Cgroups 主要提供了如下功能:

  • 资源限制: 限制资源的使用量,例如我们可以通过限制某个业务的内存上限,从而保护主机其他业务的安全运行;
  • 优先级控制:不同的组可以有不同的资源( CPU 、磁盘 IO 等)使用优先级;
  • 审计:计算控制组的资源使用情况;
  • 控制:控制进程的挂起或恢复。

Cgroups 功能的实现依赖于三个核心概念:子系统、控制组、层级树:

  • 子系统(subsystem):是一个内核的组件,一个子系统代表一类资源调度控制器。例如内存子系统可以限制内存的使用量,CPU 子系统可以限制 CPU 的使用时间;
  • 控制组(cgroup):表示一组进程和一组带有参数的子系统的关联关系。例如,一个进程使用了 CPU 子系统来限制 CPU 的使用时间,则这个进程和 CPU 子系统的关联关系称为控制组;
  • 层级树(hierarchy):是由一系列的控制组按照树状结构排列组成的。这种排列方式可以使得控制组拥有父子关系,子控制组默认拥有父控制组的属性,也就是子控制组会继承于父控制组。比如,系统中定义了一个控制组 c1,限制了 CPU 可以使用 1 核,然后另外一个控制组 c2 想实现既限制 CPU 使用 1 核,同时限制内存使用 2G,那么 c2 就可以直接继承 c1,无须重复定义 CPU 限制。

Cgroups 的三个核心概念中,子系统是最核心的概念,因为子系统是真正实现某类资源的限制的基础。

3. 配置 VSCode 访问 Docker

通过可视化操作对容器内部的文件进行操作。除 VSCode 之外,各种 ide 也提供快速操作 Docker 的方式。

3.1 远程 ssh 连接

首先需要安装一个 remote ssh 插件,之后在侧边栏中选择进行连接。

对应插件

在服务器配置公钥之后,我们就可以直接连接登陆了

在远程安装 Docker 插件

3.2 访问拒绝

因为 Docker 需要 root 身份进行访问,所以正常使用 Docker 插件会出现权限问题。网上提供的方法大多数是开放 root ssh 权限,但是这个行为并不安全,特别是放置在公网上的云服务器。在实际开发中,针对角色权限的管理是十分严格的,一般不会给开发人员直接提供 root 权限。

这个时候可以将普通用户添加到 Docker 组中,之后重启服务器,即可访问。

sudo groupadd docker          # 添加 docker 用户组
sudo gpasswd -a $USER docker  # 将当前用户添加至 docker 用户组
newgrp docker                 # 更新 docker 用户组

成功连接

4. Docker Compose

4.1 配置 Compose

安装最新的 Docker Compose,版本截止至2022.4.5(使用国内镜像源下载):

curl -L https://get.daocloud.io/docker/compose/releases/download/v2.4.1/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose

添加可执行权限:

sudo chmod +x /usr/local/bin/docker-compose

创建软链:

sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose

#检测是否安装成功
docker-compose --version

4.2 Dockerfile 配置

需要注意的是,每一次的执行都会创建一个新的层,过多的命令就会导致过多的层数,使得镜像过于膨胀。

#定制的镜像都是基于 FROM 的镜像,第一行必须
FROM **

#作者和邮箱
MAINTAINER ** <**@**>

# VOLUME 指定了临时文件目录为  /tmp,防止重要数据丢失
# 其效果是在主机 /var/lib/docker 目录下创建了一个临时文件,并链接到容器的/tmp
VOLUME /tmp

#指定工作目录。用 WORKDIR 指定的工作目录,会在构建镜像的每一层中都存在
#因为 docker build 构建镜像每一个命令都是一层,除了通过 workdir 定义
WORKDIR /app

#把项目中的所有东西都复制到工作目录下面
COPY . .

# 将 jar 包添加到容器中并更名为 app.jar(按照 SpringBoot 构建方式为例)
# add 的功能和 copy 类似(在相同的需求下,官方更推荐 copy)
ADD target/*.jar /app/app.jar

#改变容器时区
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
	&& echo 'Asia/Shanghai' >/etc/timezone
#比使用两次 run 命令少一层构建

#仅仅只是声明端口
EXPOSE 10000

#也可以使用 cmd 命令
#如果 Dockerfile 中如果存在多个 CMD 指令,仅最后一个生效。ENTRYPOINT 同理
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app/app.jar"]
#这里的参数表示 `可执行命令`...`各个参数`

runcmd 的区别在于,前者是 docker build 过程中执行的命令,后者是 docker run 的时候执行的命令。

ENTRYPOINT类似于 CMD 指令,但其不会被 docker run 的命令行参数指定的指令所覆盖,而且这些命令行参数会被当作参数送给 ENTRYPOINT 指令指定的程序(entrypoint 可以搭配 cmd 使用,相当于 cmd 给 entrypoint 传参)。

  • 第一种为 CMD/ENTRYPOINT [“command” , “param”]。这种格式是使用 Linux 的 exec 实现的, 一般称为 exec 模式,这种书写格式为 CMD/ENTRYPOINT 后面跟 json 数组,也是 Docker 推荐的使用格式;
  • 另外一种格式为 CMD/ENTRYPOINT command param ,这种格式是基于 shell 实现的, 通常称为 shell 模式。当使用 shell 模式时,Docker 会以 /bin/sh -c command 的方式执行命令。

无论使用 CMD 还是 ENTRYPOINT,都尽量使用 exec 模式。

4.2.1 Dockerfile 常见命令

指令 指令简介
FROM Dockerfile 除了注释第一行必须是 FROM ,FROM 后面跟镜像名称
代表我们要基于哪个基础镜像构建我们的容器。
RUN RUN 后面跟一个具体的命令,类似于 Linux 命令行执行命令。
ADD 拷贝本机文件或者远程文件到镜像内
COPY 拷贝本机文件到镜像内
USER 指定容器启动的用户
ENTRYPOINT 容器的启动命令
CMD CMD 为 ENTRYPOINT 指令提供默认参数,也可以单独使用 CMD 指定容器启动参数
ENV 指定容器运行时的环境变量,格式为 key=value
ARG 定义外部变量,构建镜像时可以使用 build-arg = 的格式传递参数用于构建
EXPOSE 指定容器监听的端口,格式为 [port]/tcp 或者 [port]/udp
WORKDIR 为 Dockerfile 中跟在其后的所有 RUN、CMD、ENTRYPOINT、COPY 和 ADD 命令设置工

ADDCOPY 指令功能类似,都是从外部往容器内添加文件。但是 COPY 指令只支持基本的文件和文件夹拷贝功能,ADD 则支持更多文件来源类型,比如自动提取 tar 包,并且可以支持源文件为 URL 格式。

COPY 指令更加透明,仅支持本地文件向容器拷贝,而且使用 COPY 指令可以更好地利用构建缓存,有效减小镜像体积。

4.2.2 简单示例

FROM nginx

ENTRYPOINT ["nginx", "-c"] # 定参
CMD ["/etc/nginx/nginx.conf"] # 变参 

转换成命令,相当于docker run nginx:test -c /etc/nginx/new.conf

FROM centos:7
COPY nginx.repo /etc/yum.repos.d/nginx.repo
RUN yum install -y nginx
EXPOSE 80
ENV HOST=mynginx
CMD ["nginx","-g","daemon off;"]
  • 第一行表示要基于 centos:7 这个镜像来构建自定义镜像。这里需要注意,每个 Dockerfile 的第一行除了注释都必须以 FROM 开头;
  • 第二行表示拷贝本地文件 nginx.repo 文件到容器内的 /etc/yum.repos.d 目录下。这里拷贝 nginx.repo 文件是为了添加 nginx 的安装源;
  • 第三行表示在容器内运行yum install -y nginx命令,安装 nginx 服务到容器内,执行完第三行命令,容器内的 nginx 已经安装完成;
  • 第四行声明容器内业务(nginx)使用 80 端口对外提供服务;
  • 第五行定义容器启动时的环境变量 HOST=mynginx,容器启动后可以获取到环境变量 HOST 的值为 mynginx;
  • 第六行定义容器的启动命令,命令格式为 json 数组。这里设置了容器的启动命令为 nginx ,并且添加了 nginx 的启动参数 -g ‘daemon off;’ ,使得 nginx 以前台的方式启动。

4.3 yaml 解释

version: "3.7"
services:
 
  example:                     #服务名称
    image: tomcat:8.0-jre8     #使用的镜像
    container_name: example_container_name    #容器启动时的名字
    ports:                     #容器端口映射,前面的是宿主机端口,后面是容器内端口
      - "8080:8080"
    volumes:                   #容器中的哪个路径和宿主中的路径进行数据卷映射
      - /root/apps:/usr/local/tomcat/webapps     #手动映射
      - tomcatwebapps:/usr/local/tomcat/webapps  #自动创建数据卷映射,需要在后面声明数据卷
    networks:                  #指定容器启动使用的网桥
      - aa
    command: xxxxx             #用于覆盖容器默认启动指令
    envoriment:                #指定容器启动时的环境参数
      - xxxxx=xxxx
      
    #env_file:                 使用环境变量文件,书写格式和环境参数相同
    #  - ./xxx.env
    
    depends_on:                #设置这个服务依赖的服务名称(即启动优先级)
      #可以关联其他的服务
      - xxxxx
      
    #sysctls:                  #修改容器内部参数
    #  - xxxx=xxxx
    
volumes:
   tomcatwebapps:
   #external:       默认卷名会带上项目名(yml文件所在文件夹名),
   #  true          可以声明使用外部以存在的卷
   
networks:
   aa:                #创建逻辑同volume,将不同服务关联到一个网桥

4.4 示例

整体架构如下,Dockerfile 和 docker-compose.yml 放在同一层目录:

.
`-- freshcup
    |-- data
    |   |-- mysql
    |   |   `-- initsql
    |   |       `-- freshcup.sql
    |   `-- redis
    |       `-- conf
    |           `-- redis.conf
    |-- docker-compose.yml
    |-- Dockerfile
    `-- freshcup.jar

Dockerfile 的写法如下:

FROM openjdk:11

MAINTAINER cxy621 <cxyphp@gmail.com>

# VOLUME 指定了临时文件目录为/tmp。
# 其效果是在主机 /var/lib/docker 目录下创建了一个临时文件,并链接到容器的/tmp
VOLUME /tmp

WORKDIR /app

COPY . .

ADD target/*.jar /app/app.jar

RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
RUN echo 'Asia/Shanghai' > /etc/timezone

EXPOSE 10000

ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app/app.jar"]

docker-compose.yaml 配置如下:

version: "3.7"

services:

  freshcup:                        #自己写的 web 服务
    build:                         #根据指定的 dockerfile 构建镜像
      context:  .                  #指定 dockerfile 所在目录作为docker构建镜像的context环境
      dockerfile: Dockerfile       #指定 dockerfile 的文件,默认为 Dockerfile
    container_name: test_spring
    ports:
      - "10000:10000"
    networks:
      - freshcup
    depends_on:
      - mysql
      - redis

  mysql:
    image: mysql:latest
    container_name: test_mysql
    ports:
      - "10001:3306"
    environment:
      - MYSQL_ROOT_PASSWORD=root
      - MYSQL_DATABASE=123456
    volumes:
      - ./data/mysql/initsql:/docker-entrypoint-initdb.d    #用于初始化 mysql 数据库
      - ./data/mysql/db:/var/lib/mysql                      #mysql 数据文件挂载
      - ./data/mysql/conf:/etc/mysql                        #mysql 配置文件挂载
      - ./data/mysql/log:/var/log/mysql                     #mysql 日志文件挂载
    networks:
      - test_net

  reids:
    image: redis:latest
    container_name: test_redis
    ports:
      - "10002:6379"
    volumes:
      - ./data/redis/db:/data                                    #redis 数据文件挂载
      - ./data/redis/conf/redis.conf:/etc/redis/redis.conf       #redis 配置文件挂载
    command:
      - sh
      - -c
      - | #设置 redis 只读,并需要密码,
        redis-server /etc/redis/redis.conf                   
        redis-server --requirepass 123456                        
        redis-server --appendonly yes                            

networks:
  test_net:

在 idea 中配置好之后,执行就可以对此进行部署。

4.4.1 注意

如果 cmd 具有多个命令,不能直接通过 - 来执行,需要如下操作:

command:
    - sh
    - -c 
    - |
        cmd1
        cmd2
        cmd3
#这是串行的方式,命令

5. Docker Machine

Docker Machine 可以当作一个 Docker 的虚拟机,可以集中管理所有的 docker 主机,比如快速的给 100 台服务器安装上 docker。可以运行在本地或者云服务平台比如腾讯云。

示例图片

打开 git bash 进行操作:

# Linux 安装命令
base=https://github.com/docker/machine/releases/download/v0.16.0 &&
curl -L $base/docker-machine-$(uname -s)-$(uname -m) >/tmp/docker-machine &&
sudo mv /tmp/docker-machine /usr/local/bin/docker-machine &&
chmod +x /usr/local/bin/docker-machine

# Windows 安装命令
base=https://github.com/docker/machine/releases/download/v0.16.0 &&
mkdir -p "$HOME/bin" &&
curl -L $base/docker-machine-Windows-x86_64.exe > "$HOME/bin/docker-machine.exe" &&
chmod +x "$HOME/bin/docker-machine.exe"

5.1 启动虚拟机

既然是虚拟机,那就需要一个虚拟机管理的系统作为 machine 的驱动,大部分的网站示例都是使用 ToolBox,我们这里使用 vmwareworkstastion。

在启动虚拟机时,实际上是从一个镜像文件中创建系统需要的文件,所以我们需要一个 ISO 文件放在C:\Users\<用户名>\.docker\machine\machines\cache中,下载链接

还需要一个驱动的 exe 文件,放在C:\Program Files\Docker\Docker\resources\bin中,下载链接

# 通过这个命令,创建一个名字为 test 的虚拟机
# 也可以使用 -d 参数
docker-machine create --driver vmwareworkstation test

# 通过 ls 查看我们目前已有的虚拟机
docker-machine ls

# 进入虚拟机内部
docker-machine ssh test

# 因为 docker 服务是在外网,我们需要提前换源
sudo vi /var/lib/boot2docker/profile

# 在 provider 下面一行输入以下内容(中科大源)
# 吐槽一句,国内某文章博客的横杠竟然是中文的,导致我换源失败……
--registry-mirror https://docker.mirrors.ustc.edu.cn/

sudo /etc/init.d/docker restart

# 换源之后查看 docker 配置文件信息
docker info 

# 运行 hello-world 测试 docker 情况
docker run hello-world

这里需要注意一点,进入 docker 虚拟机修改的时候,可能会有显示错位的问题(显示的位置和实际修改的位置差两个空白符作为的距离),这一点需要注意。

test 创建成功

内置 docker 服务的虚拟机创建完成

docker machine 常用命令

5.2 坑点

如果在使用 vmwareworkstation 驱动时候,出现No default Boot2Docker ISO found locally报错。说明是没有安装 Boot2Docker 的镜像文件,详细可以参考这篇文章的回答

6. Docker Swarm

Docker Swarm 是 Docker 的集群管理工具。它将 Docker 主机池转变为单个虚拟 Docker 主机。 Docker Swarm 提供了标准的 Docker API,所有任何已经与 Docker 守护程序通信的工具都可以使用 Swarm 轻松地扩展到多个主机。

swarm 集群由管理节点(manager)和工作节点(work node)构成。

  • swarm mananger:负责整个集群的管理工作包括集群配置、服务管理等所有跟集群有关的工作;

  • work node:即图中的 available node,主要负责运行相应的服务来执行任务(task)。

原理示例图

6.1 构建主机

# 创建一个管理用的 docker 虚拟机
docker-machine create -d vmwareworkstation swarm-manager

# 使用 ssh 之后初始化 swarm 管理
# 使用的 ip 是创建之后虚拟管理系统分配的 ip
# 返回的结果是作为连接管理端和其他节点的重要命令,要保存好
docker swarm init --advertise-addr 192.168.114.134

# 创建两个 worker 作为测试,执行返回的命令,讲两个虚拟主机初始化为 manager 的节点
docker swarm join --token SWMTKN-1-4iyqlb1hkizqewx45wq8ixjqyysn39t7ouw808ybqq3gx4uo7n-93hhn1gh6fvqiojx8srkf9via 192.168.114.134:2377

返回解决

添加之后显示两个 worker 节点已经连接成功

6.2 测试服务

# 创建一个名为 helloworld 的服务
docker service create --replicas 1 --name helloworld alpine ping docker.com

# 查看特定服务的运行情况
docker service ps helloworld

# 查看服务的具体信息
docker service inspect --pretty helloworld

# 将服务扩展到两个节点
docker service scale helloworld=2

docker service rm helloworld

6.3 示例

给所有节点的服务拉取一个 redis 镜像:

docker service create --replicas 1 --name redis --update-delay 10s redis:3.0.6

# 将 redis 的版本滚动升级到最新的 latest
docker service update --image redis:latest redis

# 查看所有的节点
docker node ls

# 将 swarm-worker 激活
docker node update --availability active swarm-worker1

7. 启动错误

7.1 堆栈跟踪的末尾

仅限 Windows 平台的 Docker,原因是 wsl 2 内核出现问题,可能是在刚处于升级状态,暂时无法使用。

需要下载 NoLsp,之后执行 NoLsp.exe C:\Windows\System32\wsl.exe,success 之后就可以正常使用 wsl 2。

7.2 内存崩溃

对于 Docker 容器,不建议在第一次启动时就设置 --restart=always

对于一些资源占用过大的容器,会挤占大量服务器资源,导致 ssh 都无法正常使用。

8. Docker 底层原理

Docker 整体结构

Docker 整体架构采用 C/S(客户端 / 服务器)模式。

Docker 从诞生到现在,服务端经历了多次架构重构。起初,服务端的组件是全部集成在 docker 二进制里。但是从 1.11 版本开始, dockerd 已经成了独立的二进制,此时的容器也不是直接由 dockerd 来启动了,而是集成了 containerd、runC 等多个组件。

  • runC 是 Docker 官方按照 OCI 容器运行时标准的一个实现。通俗地讲,runC 是一个用来运行容器的轻量级工具,是真正用来运行容器的;
  • containerd 是 Docker 服务端的一个核心组件,它是从 dockerd 中剥离出来的 ,它的诞生完全遵循 OCI 标准,是容器标准化后的产物。containerd 通过 containerd-shim 启动并管理 runC,可以说 containerd 真正管理了容器的生命周期。

8.1 Docker 组件

组件根据工作职责可以分为以下三大类。

  1. Docker 相关的组件:docker、dockerd、docker-init 和 docker-proxy;
  2. containerd 相关的组件:containerd、containerd-shim 和 ctr;
  3. 容器运行时相关的组件:runc。

8.1.1 Docker 相关组件

Docker 组件

docker

docker 是 Docker 客户端的一个完整实现,它是一个二进制文件,对用户可见的操作形式为 docker 命令,通过 docker 命令可以完成所有的 Docker 客户端与服务端的通信(还可以通过 REST API、SDK 等多种形式与 Docker 服务端通信)。

Docker 客户端与服务端的交互过程是:docker 组件向服务端发送请求后,服务端根据请求执行具体的动作并将结果返回给 docker,docker 解析服务端的返回结果,并将结果通过命令行标准输出展示给用户。这样一次完整的客户端服务端请求就完成了。

dockerd

dockerd 是 Docker 服务端的后台常驻进程,用来接收客户端发送的请求,执行具体的处理任务,处理完成后将结果返回给客户端。

Docker 客户端可以通过多种方式向 dockerd 发送请求,我们常用的 Docker 客户端与 dockerd 的交互方式有三种:

  • 通过 UNIX 套接字与服务端通信:配置格式为 unix://socket_path,默认 dockerd 生成的 socket 文件路径为 /var/run/docker.sock,该文件只有 root 用户或者 docker 用户组的用户才可以访问,这就是为什么 Docker 刚安装完成后只有 root 用户才能使用 docker 命令的原因;
  • 通过 TCP 与服务端通信:配置格式为tcp://host:port,通过这种方式可以实现客户端远程连接服务端,但是在方便的同时也带有安全隐患,因此在生产环境中如果你要使用 TCP 的方式与 Docker 服务端通信,推荐使用 TLS 认证,可以通过设置 Docker 的 TLS 相关参数,来保证数据传输的安全;
  • 通过文件描述符的方式与服务端通信:配置格式为:fd:// 这种格式一般用于 systemd 管理的系统中。

Docker 客户端和服务端的通信形式必须保持一致,否则将无法通信,只有当 dockerd 监听了 UNIX 套接字客户端才可以使用 UNIX 套接字的方式与服务端通信,UNIX 套接字也是 Docker 默认的通信方式,如果你想要通过远程的方式访问 dockerd,可以在 dockerd 启动的时候添加 -H 参数指定监听的 HOST 和 PORT。

docker-init

在 Linux 系统中,1 号进程是 init 进程,是所有进程的父进程。主机上的进程出现问题时,init 进程可以帮我们回收这些问题进程。同样的,在容器内部,当我们自己的业务进程没有回收子进程的能力时,在执行 docker run 启动容器时可以添加 –init 参数,此时 Docker 会使用 docker-init 作为1号进程,帮你管理容器内子进程,例如回收僵尸进程等。

docker-proxy

docker-proxy 主要是用来做端口映射的。当我们使用 docker run 命令启动容器时,如果使用了 -p 参数,docker-proxy 组件就会把容器内相应的端口映射到主机上来,底层是依赖于 iptables 实现的。

当我们启动一个容器时需要端口映射时, Docker 为我们创建了一个 docker-proxy 进程,并且通过参数把我们的容器 IP 和端口传递给 docker-proxy 进程,然后 docker-proxy 通过 iptables 实现了 nat 转发。

8.1.2 containerd 相关的组件

containerd

containerd 组件是从 Docker 1.11 版本正式从 dockerd 中剥离出来的,它的诞生完全遵循 OCI 标准,是容器标准化后的产物。containerd 完全遵循了 OCI 标准,并且是完全社区化运营的,因此被容器界广泛采用。

containerd 不仅负责容器生命周期的管理,同时还负责一些其他的功能:

  • 镜像的管理,例如容器运行前从镜像仓库拉取镜像到本地;
  • 接收 dockerd 的请求,通过适当的参数调用 runc 启动容器;
  • 管理存储相关资源;
  • 管理网络相关资源。

containerd 包含一个后台常驻进程,默认的 socket 路径为 /run/containerd/containerd.sock,dockerd 通过 UNIX 套接字向 containerd 发送请求,containerd 接收到请求后负责执行相关的动作并把执行结果返回给 dockerd。

如果你不想使用 dockerd,也可以直接使用 containerd 来管理容器,由于 containerd 更加简单和轻量,生产环境中越来越多的人开始直接使用 containerd 来管理容器。

containerd-shim

containerd-shim 的意思是垫片,类似于拧螺丝时夹在螺丝和螺母之间的垫片。containerd-shim 的主要作用是将 containerd 和真正的容器进程解耦,使用 containerd-shim 作为容器进程的父进程,从而实现重启 containerd 不影响已经启动的容器进程。

ctr

ctr 实际上是 containerd-ctr,它是 containerd 的客户端,主要用来开发和调试,在没有 dockerd 的环境中,ctr 可以充当 docker 客户端的部分角色,直接向 containerd 守护进程发送操作容器的请求。

8.1.3 runc

runc 是一个标准的 OCI 容器运行时的实现,它是一个命令行工具,可以直接用来创建和运行容器。

config.json 文件定义了 runc 启动容器时的一些配置,如根目录的路径,文件挂载路径等配置。

8.2 Docker 网络实现

于是 Docker 从 1.7 版本开始,便把网络和存储从 Docker 中正式以插件的形式剥离开来,并且分别为其定义了标准,Docker 定义的网络模型标准称之为 CNM (Container Network Model) 。

8.2.1 CNM

CNM (Container Network Model) 是 Docker 发布的容器网络标准,意在规范和指定容器网络发展标准,CNM 抽象了容器的网络接口 ,使得只要满足 CNM 接口的网络方案都可以接入到 Docker 容器网络,更好地满足了用户网络模型多样化的需求。

CNM 只是定义了网络标准,对于底层的具体实现并不太关心,这样便解耦了容器和网络,使得容器的网络模型更加灵活。

CNM 定义的网络标准包含三个重要元素:

  • 沙箱(Sandbox):沙箱代表了一系列网络堆栈的配置,其中包含路由信息、网络接口等网络资源的管理,沙箱的实现通常是 Linux 的 Net Namespace,但也可以通过其他技术来实现,比如 FreeBSD jail 等;
  • 接入点(Endpoint):接入点将沙箱连接到网络中,代表容器的网络接口,接入点的实现通常是 Linux 的 veth 设备对;
  • 网络(Network):网络是一组可以互相通信的接入点,它将多接入点组成一个子网,并且多个接入点之间可以相互通信。

CNM 的三个要素基本抽象了所有网络模型,使得网络模型的开发更加规范。

8.2.2 Libnetwork

Libnetwork 是 Docker 启动容器时,用来为 Docker 容器提供网络接入功能的插件,它可以让 Docker 容器顺利接入网络,实现主机和容器网络的互通。

工作流程

第一步:Docker 通过调用 libnetwork.New 函数来创建 NetworkController 实例。NetworkController 是一个接口类型,提供了各种接口,代码如下:

type NetworkController interface {
   // 创建一个新的网络。 options 参数用于指定特性类型的网络选项。
   NewNetwork(networkType, name string, id string, options ...NetworkOption) (Network, error)
   // ... 此次省略部分接口
}

第二步:通过调用 NewNetwork 函数创建指定名称和类型的 Network,其中 Network 也是接口类型,代码如下:

type Network interface {
   // 为该网络创建一个具有唯一指定名称的接入点(Endpoint)
   CreateEndpoint(name string, options ...EndpointOption) (Endpoint, error)
   // 删除网络
   Delete() error
	// ... 此次省略部分接口
}

第三步:通过调用 CreateEndpoint 来创建接入点(Endpoint)。在 CreateEndpoint 函数中为容器分配了 IP 和网卡接口。其中 Endpoint 也是接口类型,代码如下:

// Endpoint 表示网络和沙箱之间的逻辑连接。
type Endpoint interface {
   // 将沙箱连接到接入点,并将为接入点分配的网络资源填充到沙箱中。
   // the network resources allocated for the endpoint.
   Join(sandbox Sandbox, options ...EndpointOption) error
   // 删除接入点
   Delete(force bool) error
   // ... 此次省略部分接口
}

第四步:调用 NewSandbox 来创建容器沙箱,主要是初始化 Namespace 相关的资源。

第五步:调用 Endpoint 的 Join 函数将沙箱和网络接入点关联起来,此时容器就加入了 Docker 网络并具备了网络访问能力。

网络模式

Libnetwork 比较典型的网络模式主要有四种,这四种网络模式基本满足了我们单机容器的所有场景。

  1. null 空网络模式:可以帮助我们构建一个没有网络接入的容器环境,以保障数据安全;
  2. bridge 桥接模式:可以打通容器与容器间网络通信的需求;
  3. host 主机网络模式:可以让容器内的进程共享主机网络,从而监听或修改主机网络;
  4. container 网络模式:可以将两个容器放在同一个网络命名空间内,让两个业务通过 localhost 即可实现访问。

Libnetwork 网络模式

null 空网络模式

使用 Docker 创建 null 空网络模式的容器时,容器拥有自己独立的 Net Namespace,但是此时的容器并没有任何网络配置。在这种模式下,Docker 除了为容器创建了 Net Namespace 外,没有创建任何网卡接口、IP 地址、路由等网络配置。

docker run --net=none -it ubuntu
# 添加 --net=none 参数启动一个空网络模式的容器
bridge 桥接模式

bridge 网络是启动容器时默认的网络模式,使用 bridge 网络可以实现容器与容器的互通,可以从一个容器直接通过容器 IP 访问到另外一个容器。同时使用 bridge 网络可以实现主机与容器的互通,我们在容器内启动的业务,可以从主机直接请求。

bridge 通过 Linux 的 veth 和 bridge 技术实现。

  • Linux veth:veth 是 Linux 中的虚拟设备接口,veth 都是成对出现的,它在容器中,通常充当一个桥梁。veth 可以用来连接虚拟网络设备;
  • Linux bridge:Linux bridge 是一个虚拟设备,是用来连接网络的设备,相当于物理网络环境中的交换机。Linux bridge 可以用来转发两个 Net Namespace 内的流量。

veth 和 bridge

Docker 启动时,libnetwork 会在主机上创建 docker0 网桥,docker0 网桥就相当于中的交换机,而 Docker 创建出的 brige 模式的容器则都会连接 docker0 上,从而实现网络互通。

host 主机网络模式

容器内的网络并不是希望永远跟主机是隔离的,有些基础业务需要创建或更新主机的网络配置,我们的程序必须以主机网络模式运行才能够修改主机网络,这时候就需要用到 Docker 的 host 主机网络模式。

使用 host 主机网络模式时:

  • libnetwork 不会为容器创建新的网络配置和 Net Namespace;
  • Docker 容器中的进程直接共享主机的网络配置,可以直接使用主机的网络信息,此时,在容器内监听的端口,也将直接占用到主机的端口;
  • 除了网络共享主机的网络外,其他的包括进程、文件系统、主机名等都是与主机隔离的。
container 网络模式

container 网络模式允许一个容器共享另一个容器的网络命名空间。当两个容器需要共享网络,但其他资源仍然需要隔离时就可以使用 container 网络模式。

8.3 Docker 持久化

镜像是由多层文件系统组成的,当我们想要启动一个容器时,Docker 会在镜像上层创建一个可读写层,容器中的文件都工作在这个读写层中,当容器删除时,与容器相关的工作文件将全部丢失。

Docker 容器的文件系统不是一个真正的文件系统,而是通过联合文件系统实现的一个伪文件系统,而 Docker 卷则是直接利用主机的某个文件或者目录,它可以绕过联合文件系统,直接挂载主机上的文件或目录到容器中。

Docker 卷的实现原理是在主机的 /var/lib/docker/volumes 目录下,根据卷的名称创建相应的目录,然后在每个卷的目录下创建 _data 目录,在容器启动时如果使用 –mount 参数,Docker 会把主机上的目录直接映射到容器的指定目录下,实现数据持久化。

参考文章

  1. 深入浅出吃透 Docker
  2. 如何通俗解释 Docker 是什么?
  3. 通俗易懂,一文带你了解什么是虚拟化技术
  4. Docker 教程 菜鸟教程
  5. Docker 技术入门与实战
  6. Docker — 从入门到实践
  7. Docker 命令自动补齐
  8. docker ps 显示指定的列
  9. Docker 部署 SpringBoot 项目
  10. 使用 Docker 部署 SpringBoot
  11. VSCode 远程连接 Docker 容器
  12. VSCode 中 Docker 插件无法连接
  13. docker-compose command 执行多条指令
  14. Docker for Windows 使用 VMware WorkStation
  15. Docker Toolbox 修改镜像源
  16. Docker Swarm 集群环境搭建及弹性服务部署
  17. docker Error An error occurred 引发异常的上一位置中堆栈跟踪的末尾

文章作者: 陈鑫扬
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 陈鑫扬 !
评论
  目录