Docker的知识点

1、镜像操作命令

  1. 拉取镜像,使用 docker pull 命令拉取远程仓库的镜像到本地 ;

  2. 重命名镜像,使用 docker tag 命令“重命名”镜像 ;

  3. 查看镜像,使用 docker image ls 或 docker images 命令查看本地已经存在的镜像;

  4. 删除镜像,使用 docker rmi 命令删除无用镜像 ;

  5. 构建镜像,构建镜像有两种方式。第一种方式是使用 docker build 命令基于 Dockerfile 构建镜像,也是我比较推荐的镜像构建方式;第二种方式是使用 docker commit 命令基于已经运行的容器提交为镜像。

2、通过Dockerfile构建Docker的镜像

Dockerfile 常用的指令
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 命令设置工作目录。
   

Dockerfile示例:

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;"]

以下是参数解析

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

镜像的实现原理:

  • 镜像是由一系列的镜像层(layer )组成,每一层代表了镜像构建过程中的一次提交,当我们需要修改镜像内的某个文件时,只需要在当前镜像层的基础上新建一个镜像层,并且只存放修改过的文件内容。分层结构使得镜像间共享镜像层变得非常简单和方便。

3、容器详解

  • 容器是基于镜像创建的可运行实例,并且单独存在,一个镜像可以创建出多个容器。运行容器化环境时,实际上是在容器内部创建该文件系统的读写副本。 这将添加一个容器层,该层允许修改镜像的整个副本。
  1. 容器的生命周期

容器的生命周期是容器可能处于的状态,容器的生命周期分为 5 种。

  1. created:初建状态

  2. running:运行状态

    扫描二维码关注公众号,回复: 12603796 查看本文章
  3. stopped:停止状态

  4. paused: 暂停状态

  5. deleted:删除状态

各生命周期之前的转换关系如图所示2

docker容器的状态转移图

通过docker create命令生成的容器状态为初建状态,初建状态通过docker start命令可以转化为运行状态,运行状态的容器可以通过docker stop命令转化为停止状态,处于停止状态的容器可以通过docker start转化为运行状态,运行状态的容器也可以通过docker pause命令转化为暂停状态,处于暂停状态的容器可以通过docker unpause转化为运行状态 。处于初建状态、运行状态、停止状态、暂停状态的容器都可以直接删除。

4、容器的操作

(1)创建并启动容器

容器十分轻量,用户可以随时创建和删除它。我们可以使用docker create命令来创建容器。

  • 创建容器:docker create -it --name=busybox busybox
  • 启动容器:

      1、使用docker start命令基于已经创建好的容器直接启动 。

      2、使用docker run命令直接基于镜像新建一个容器并启动,相当于先执行docker create命令从镜像创建容器,然后再执行docker start命令启动容器。docker run -it --name=busybox busybox

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

  • Docker 会检查本地是否存在 busybox 镜像,如果镜像不存在则从 Docker Hub 拉取 busybox 镜像;

  • 使用 busybox 镜像创建并启动一个容器;

  • 分配文件系统,并且在镜像只读层外创建一个读写层;

  • 从 Docker IP 池中分配一个 IP 给容器;

  • 执行用户的启动命令运行镜像。

上述命令中, -t 参数的作用是分配一个伪终端,-i 参数则可以终端的 STDIN 打开,同时使用 -it 参数可以让我们进入交互模式。 在交互模式下,用户可以通过所创建的终端来输入命令

$ ps aux

PID   USER     TIME  COMMAND

    1 root      0:00 sh

    6 root      0:00 ps aux

我们可以看到容器的 1 号进程为 sh 命令,在容器内部并不能看到主机上的进程信息,因为容器内部和主机是完全隔离的。同时由于 sh 是 1 号进程,意味着如果通过 exit 退出 sh,那么容器也会退出。所以对于容器来说,杀死容器中的主进程,则容器也会被杀死。

(2)终止容器

容器启动后,如果我们想停止运行中的容器,可以使用docker stop命令。命令格式为 docker stop [-t|--time[=10]]。该命令首先会向运行中的容器发送 SIGTERM 信号,如果容器内 1 号进程接受并能够处理 SIGTERM,则等待 1 号进程处理完毕后退出,如果等待一段时间后,容器仍然没有退出,则会发送 SIGKILL 强制终止容器。

如果你想查看停止状态的容器信息,你可以使用 docker ps -a 命令。

处于终止状态的容器也可以通过docker start命令来重新启动。

此外,docker restart命令会将一个运行中的容器终止,并且重新启动它。

(3)进入容器

处于运行状态的容器可以通过docker attachdocker execnsenter等多种方式进入容器。

  • 使用docker attach命令进入容器

使用 docker attach ,进入我们上一步创建好的容器,如下所示。

$ docker attach busybox

/ # ps aux

PID   USER     TIME  COMMAND

    1 root      0:00 sh

    7 root      0:00 ps aux

/ #

注意:当我们同时使用docker attach命令同时在多个终端运行时,所有的终端窗口将同步显示相同内容,当某个命令行窗口的命令阻塞时,其他命令行窗口同样也无法操作。
由于docker attach命令不够灵活,因此我们一般不会使用docker attach进入容器。下面我介绍一个更加灵活的进入容器的方式docker exec

  • 使用 docker exec 命令进入容器

Docker 从 1.3 版本开始,提供了一个更加方便地进入容器的命令docker exec,我们可以通过docker exec -it CONTAINER的方式进入到一个已经运行中的容器,如下所示。

$ docker exec -it busybox sh

/ # ps aux

PID   USER     TIME  COMMAND

    1 root      0:00 sh

    7 root      0:00 sh

   12 root      0:00 ps aux

我们进入容器后,可以看到容器内有两个sh进程,这是因为以exec的方式进入容器,会单独启动一个 sh 进程,每个窗口都是独立且互不干扰的,也是使用最多的一种方式。

(4)删除容器

我们已经掌握了用 Docker 命令创建、启动和终止容器。那如何删除处于终止状态或者运行中的容器呢?删除容器命令的使用方式如下:docker rm [OPTIONS] CONTAINER [CONTAINER...]

如果要删除一个停止状态的容器,可以使用docker rm命令删除。

docker rm busybox

如果要删除正在运行中的容器,必须添加 -f (或 --force) 参数, Docker 会发送 SIGKILL 信号强制终止正在运行的容器。

docker rm -f busybox

(5)导出导入容器

  • 导出容器

我们可以使用docker export CONTAINER命令导出一个容器到文件,不管此时该容器是否处于运行中的状态。导出容器前我们先进入容器,创建一个文件,过程如下。

首先进入容器创建文件

docker exec -it busybox sh

cd /tmp && touch test

然后执行导出命令

docker export busybox > busybox.tar

执行以上命令后会在当前文件夹下生成 busybox.tar 文件,我们可以将该文件拷贝到其他机器上,通过导入命令实现容器的迁移。

  • 导入容器

通过docker export命令导出的文件,可以使用docker import命令导入,执行完docker import后会变为本地镜像,最后再使用docker run命令启动该镜像,这样我们就实现了容器的迁移。

导入容器的命令格式为 docker import [OPTIONS] file|URL [REPOSITORY[:TAG]]。接下来我们一步步将上一步导出的镜像文件导入到其他机器的 Docker 中并启动它。

首先,使用docker import命令导入上一步导出的容器

docker import busybox.tar busybox:test

此时,busybox.tar 被导入成为新的镜像,镜像名称为 busybox:test 。下面,我们使用docker run命令启动并进入容器,查看上一步创建的临时文件

docker run -it busybox:test sh

/ # ls /tmp/

test

可以看到我们之前在 /tmp 目录下创建的 test 文件也被迁移过来了。这样我们就通过docker exportdocker import命令配合实现了容器的迁移。

Dcoker如何实现资源隔离

什么是Namespace?

Namespace 是 Linux 内核的一个特性,该特性可以实现在同一主机系统中,对进程 ID、主机名、用户 ID、文件名、网络和进程间通信等资源的隔离。Docker 利用 Linux 内核的 Namespace 特性,实现了每个容器的资源相互隔离,从而保证容器内部只能访问到自己 Namespace 的资源。

Namespace 名称 作用 内核版本
Mount(mnt) 隔离挂载点 2.4.19
Process ID (pid) 隔离进程 ID 2.6.24
Network (net) 隔离网络设备,端口号等 2.6.29
Interprocess Communication (ipc) 隔离 System V IPC 和 POSIX message queues 2.6.19
UTS Namespace(uts) 隔离主机名和域名 2.6.19
User Namespace (user) 隔离用户和用户组 3.8
Control group (cgroup) Namespace 隔离 Cgroups 根目录 4.6
Time Namespace 隔离系统时间 5.6

虽然 Linux 内核提供了8种 Namespace,但是最新版本的 Docker 只使用了其中的前6 种,分别为Mount Namespace、PID Namespace、Net Namespace、IPC Namespace、UTS Namespace、User Namespace。

各种 Namespace 的作用?

1、Mount Namespace

它可以用来隔离不同的进程或进程组看到的挂载点。通俗地说,就是可以实现在不同的进程中看到不同的挂载目录。使用 Mount Namespace 可以实现容器内只能看到自己的挂载信息,在容器内的挂载操作不会影响主机的挂载目录。

# 创建一个 bash 进程并且新建一个 Mount Namespace
sudo unshare --mount --fork /bin/bash

# 已经在主机上创建了一个新的 Mount Namespace,并且当前命令行窗口加入了新创建的 Mount Namespace
# 在/tmp 目录下创建一个目录
mkdir /tmp/tmpfs

# 创建好目录后使用 mount 命令挂载一个 tmpfs 类型的目录
mount -t tmpfs -o size=20m tmpfs /tmp/tmpfs

# 这时候使用以下命令发现已经挂载了/tmp/tempfs目录
df -h

# 这时候重新一个连接窗口通过df -h 命令查看没有挂载该目录
# 通过mount namespace就实现了挂载点隔离

通过以上结果我们可以得出结论,使用 unshare 命令可以新建 Mount Namespace,并且在新建的 Mount Namespace 内 mount 是和外部完全隔离的。

2、PID Namespace

PID Namespace 的作用是用来隔离进程。在不同的 PID Namespace 中,进程可以拥有相同的 PID 号,利用 PID Namespace 可以实现每个容器的主进程为 1 号进程,而容器内的进程在主机上却拥有不同的PID。

# 使用以下命令创建一个 bash 进程,并且新建一个 PID Namespace
[root@mymaster01 ~]# unshare --pid --fork --mount-proc /bin/bash

# 主机上创建了一个新的 PID Namespace,并且当前命令行窗口加入了新创建的 PID Namespace
[root@mymaster01 ~]# ps aux
USER        PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root          1  0.6  0.1 115572  2136 pts/0    S    22:25   0:00 /bin/bash
root         12  0.0  0.0 155328  1856 pts/0    R+   22:26   0:00 ps aux

# 然后新开一个窗口再执行ps aux会发现看不到bash 进程存在

3、UTS Namespace

UTS Namespace 主要是用来隔离主机名的,它允许每个 UTS Namespace 拥有一个独立的主机名。例如我们的主机名称为 docker,使用 UTS Namespace 可以实现在容器内的主机名称为 lagoudocker 或者其他任意自定义主机名。

[root@mymaster01 ~]# unshare --uts --fork /bin/bash
[root@mymaster01 ~]# hostname -b lagoudocker
[root@mymaster01 ~]# hostname
lagoudocker

# 新开的另一个窗口
[root@mymaster01 ~]# hostname
mymaster01

4、IPC Namespace

IPC Namespace 主要是用来隔离进程间通信的。例如 PID Namespace 和 IPC Namespace 一起使用可以实现同一 IPC Namespace 内的进程彼此可以通信,不同 IPC Namespace 的进程却不能通信。

# 使用 unshare 命令来创建一个 IPC Namespace
[root@mymaster01 ~]# unshare --ipc --fork /bin/bash
# 用来查看系统间通信队列列表
[root@lagoudocker ~]# ipcs -q

------ Message Queues --------
key        msqid      owner      perms      used-bytes   messages    

# 用来创建系统间通信队列。
[root@lagoudocker ~]# ipcmk -Q
Message queue id: 0
[root@lagoudocker ~]# ipcs -q

------ Message Queues --------
key        msqid      owner      perms      used-bytes   messages    
0x01105f14 0          root       644        0            0           

# 另一个新开的窗口
[root@mymaster01 ~]# ipcs -q

------ Message Queues --------
key        msqid      owner      perms      used-bytes   messages    

5、User Namespace

User Namespace 主要是用来隔离用户和用户组的。一个比较典型的应用场景就是在主机上以非 root 用户运行的进程可以在一个单独的 User Namespace 中映射成 root 用户。使用 User Namespace 可以实现进程在容器内拥有 root 权限,而在主机上却只是普通用户。

# CentOS7 默认允许创建的 User Namespace 为 0
[root@mymaster01 ~]# echo 65535 > /proc/sys/user/max_user_namespaces
# 这里切换到eason用户
[root@mymaster01 ~]# su eason
# User Namesapce 的创建是可以不使用 root 权限的,以普通用户的身份创建一个 User Namespace
[eason@mymaster01 root]$ unshare --user -r /bin/bash
# 下面看到目前在当前的namespace中已经是root用户了
[root@mymaster01 root]# id
uid=0(root) gid=0(root) groups=0(root) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
# 这里执行只有root用户才能执行的reboot,但是发现失败了。
# 说明:在隔离的 User Namespace 中,并不能获取到主机的 root 权限,也就是说 User Namespace 实现了用户和用户组的隔离。
[root@mymaster01 root]# reboot
Failed to open /dev/initctl: Permission denied
Failed to talk to init daemon.

6、Net Namespace

Net Namespace 是用来隔离网络设备、IP 地址和端口等信息的。Net Namespace 可以让每个进程拥有自己独立的 IP 地址,端口和网卡信息。

# 这里先查看下当前的网络配置
[root@mymaster01 root]# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 00:0c:29:03:fb:b2 brd ff:ff:ff:ff:ff:ff
    inet 192.168.52.128/24 brd 192.168.52.255 scope global noprefixroute ens33
       valid_lft forever preferred_lft forever
    inet6 fe80::20c:29ff:fe03:fbb2/64 scope link 
       valid_lft forever preferred_lft forever
3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default 
    link/ether 02:42:08:33:f2:10 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
       valid_lft forever preferred_lft forever
# 这里创建一个新的Net Namespace
[root@mymaster01 root]# unshare --net --fork /bin/bash
# 然后再查看当前的网络配置,发现和之前的不一样
[root@mymaster01 root]# ip a
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00

cgroups 功能及核心概念

cgroups(全称:control groups)是 Linux 内核的一个功能,

cgroups 主要提供了如下功能。

  • 资源限制: 限制资源的使用量,例如我们可以通过限制某个业务的内存上限,从而保护主机其他业务的安全运行。

  • 优先级控制:不同的组可以有不同的资源( CPU 、磁盘 IO 等)使用优先级。

  • 审计:计算控制组的资源使用情况。

  • 控制:控制进程的挂起或恢复。

了解了 cgroups 可以为我们提供什么功能,下面我来看下 cgroups 是如何实现这些功能的。

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

  • 子系统(subsystem):是一个内核的组件,一个子系统代表一类资源调度控制器。例如内存子系统可以限制内存的使用量,CPU 子系统可以限制 CPU 的使用时间。

  • 控制组(cgroup):表示一组进程和一组带有参数的子系统的关联关系。例如,一个进程使用了 CPU 子系统来限制 CPU 的使用时间,则这个进程和 CPU 子系统的关联关系称为控制组。

  • 层级树(hierarchy):是由一系列的控制组按照树状结构排列组成的。这种排列方式可以使得控制组拥有父子关系,子控制组默认拥有父控制组的属性,也就是子控制组会继承于父控制组。比如,系统中定义了一个控制组 c1,限制了 CPU 可以使用 1 核,然后另外一个控制组 c2 想实现既限制 CPU 使用 1 核,同时限制内存使用 2G,那么 c2 就可以直接继承 c1,无须重复定义 CPU 限制。

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

它可以实现限制进程或者进程组的资源(如 CPU、内存、磁盘 IO 等)。

cgroups子系统

查看已经挂载的cgroup子系统

[root@mymaster01 ~]# mount -t cgroup
cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,xattr,release_agent=/usr/lib/systemd/systemd-cgroups-agent,name=systemd)
cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,cpuacct,cpu)
cgroup on /sys/fs/cgroup/net_cls,net_prio type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,net_prio,net_cls)
cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,blkio)
cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,freezer)
cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,cpuset)
cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,memory)
cgroup on /sys/fs/cgroup/hugetlb type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,hugetlb)
cgroup on /sys/fs/cgroup/perf_event type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,perf_event)
cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,devices)
cgroup on /sys/fs/cgroup/pids type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,pids)

cgroup的cpu隔离性

# cgroups的创建很简单,只需要在相应的子系统下创建目录即可。
[root@mymaster01 ~]# mkdir /sys/fs/cgroup/cpu/mydocker

# 查看新建目录下发生了什么
[root@mymaster01 ~]# ls -l /sys/fs/cgroup/cpu/mydocker/
total 0
-rw-r--r--. 1 root root 0 Oct 22 22:59 cgroup.clone_children
--w--w--w-. 1 root root 0 Oct 22 22:59 cgroup.event_control
-rw-r--r--. 1 root root 0 Oct 22 22:59 cgroup.procs
-r--r--r--. 1 root root 0 Oct 22 22:59 cpuacct.stat
-rw-r--r--. 1 root root 0 Oct 22 22:59 cpuacct.usage
-r--r--r--. 1 root root 0 Oct 22 22:59 cpuacct.usage_percpu
-rw-r--r--. 1 root root 0 Oct 22 22:59 cpu.cfs_period_us
# cpu.cfs_quota_us 文件代表在某一个阶段限制的 CPU 时间总量,单位为微秒,我们想限制某个进程最多使用 1 核 CPU,就在这个文件里写入 100000(100000 代表限制 1 个核) ,
# tasks 文件中写入进程的 ID 即可(如果要限制多个进程 ID,在 tasks 文件中用换行符分隔即可)。
-rw-r--r--. 1 root root 0 Oct 22 22:59 cpu.cfs_quota_us
-rw-r--r--. 1 root root 0 Oct 22 22:59 cpu.rt_period_us
-rw-r--r--. 1 root root 0 Oct 22 22:59 cpu.rt_runtime_us
-rw-r--r--. 1 root root 0 Oct 22 22:59 cpu.shares
-r--r--r--. 1 root root 0 Oct 22 22:59 cpu.stat
-rw-r--r--. 1 root root 0 Oct 22 22:59 notify_on_release
-rw-r--r--. 1 root root 0 Oct 22 22:59 tasks

# 将当前的shell进程加入到cgroup中
[root@mymaster01 ~]# cd /sys/fs/cgroup/cpu/mydocker
[root@mymaster01 mydocker]# echo $$ > tasks
[root@mymaster01 mydocker]# cat tasks 
1742
2728

# 这里执行一个死循环进行测试
[root@mymaster01 mydocker]# while true;do echo;done;

# 新开一个窗口监视shell进程的cpu使用率
[root@mymaster01 ~]# top -p 1742
top - 23:05:19 up 25 min,  2 users,  load average: 1.16, 0.31, 0.14
Tasks:   1 total,   1 running,   0 sleeping,   0 stopped,   0 zombie
%Cpu(s): 39.6 us, 10.5 sy,  0.0 ni, 49.4 id,  0.0 wa,  0.0 hi,  0.5 si,  0.0 st
KiB Mem :  1865308 total,  1270844 free,   186528 used,   407936 buff/cache
KiB Swap:        0 total,        0 free,        0 used.  1468024 avail Mem 

   PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND                                                                                
  1742 root      20   0  115572   2208   1688 R  98.3  0.1   0:36.20 bash

# 为了进一步证实 cgroup 限制 cpu 的准确性,我们修改 cpu 限制时间为 0.5 核
[root@mymaster01 mydocker]# echo 50000 > cpu.cfs_quota_us
# 这里还是死循环测试
[root@mymaster01 mydocker]# while true;do echo;done;

# 这里还是监视进程的cpu使用率,可以发现使用是50%左右
[root@mymaster01 ~]# top -p 1742
top - 23:09:49 up 29 min,  2 users,  load average: 0.04, 0.14, 0.12
Tasks:   1 total,   1 running,   0 sleeping,   0 stopped,   0 zombie
%Cpu(s): 20.5 us,  5.2 sy,  0.0 ni, 73.9 id,  0.0 wa,  0.0 hi,  0.5 si,  0.0 st
KiB Mem :  1865308 total,  1270884 free,   186520 used,   407904 buff/cache
KiB Swap:        0 total,        0 free,        0 used.  1468112 avail Mem 

   PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND                                                                                
  1742 root      20   0  115572   2208   1688 R  50.3  0.1   0:47.12 bash

cgroup的memory隔离性

# 在 memory 子系统下创建 cgroup
[root@mymaster01 mydocker]# mkdir /sys/fs/cgroup/memory/mydocker
[root@mymaster01 mydocker]# ls -l /sys/fs/cgroup/memory/mydocker
total 0
-rw-r--r--. 1 root root 0 Oct 22 23:12 cgroup.clone_children
--w--w--w-. 1 root root 0 Oct 22 23:12 cgroup.event_control
-rw-r--r--. 1 root root 0 Oct 22 23:12 cgroup.procs
-rw-r--r--. 1 root root 0 Oct 22 23:12 memory.failcnt
--w-------. 1 root root 0 Oct 22 23:12 memory.force_empty
-rw-r--r--. 1 root root 0 Oct 22 23:12 memory.kmem.failcnt
-rw-r--r--. 1 root root 0 Oct 22 23:12 memory.kmem.limit_in_bytes
-rw-r--r--. 1 root root 0 Oct 22 23:12 memory.kmem.max_usage_in_bytes
-r--r--r--. 1 root root 0 Oct 22 23:12 memory.kmem.slabinfo
-rw-r--r--. 1 root root 0 Oct 22 23:12 memory.kmem.tcp.failcnt
-rw-r--r--. 1 root root 0 Oct 22 23:12 memory.kmem.tcp.limit_in_bytes
-rw-r--r--. 1 root root 0 Oct 22 23:12 memory.kmem.tcp.max_usage_in_bytes
-r--r--r--. 1 root root 0 Oct 22 23:12 memory.kmem.tcp.usage_in_bytes
-r--r--r--. 1 root root 0 Oct 22 23:12 memory.kmem.usage_in_bytes
# memory.limit_in_bytes 文件代表内存使用总量,单位为 byte。
-rw-r--r--. 1 root root 0 Oct 22 23:12 memory.limit_in_bytes
-rw-r--r--. 1 root root 0 Oct 22 23:12 memory.max_usage_in_bytes
-rw-r--r--. 1 root root 0 Oct 22 23:12 memory.memsw.failcnt
-rw-r--r--. 1 root root 0 Oct 22 23:12 memory.memsw.limit_in_bytes
-rw-r--r--. 1 root root 0 Oct 22 23:12 memory.memsw.max_usage_in_bytes
-r--r--r--. 1 root root 0 Oct 22 23:12 memory.memsw.usage_in_bytes
-rw-r--r--. 1 root root 0 Oct 22 23:12 memory.move_charge_at_immigrate
-r--r--r--. 1 root root 0 Oct 22 23:12 memory.numa_stat
-rw-r--r--. 1 root root 0 Oct 22 23:12 memory.oom_control
----------. 1 root root 0 Oct 22 23:12 memory.pressure_level
-rw-r--r--. 1 root root 0 Oct 22 23:12 memory.soft_limit_in_bytes
-r--r--r--. 1 root root 0 Oct 22 23:12 memory.stat
-rw-r--r--. 1 root root 0 Oct 22 23:12 memory.swappiness
-r--r--r--. 1 root root 0 Oct 22 23:12 memory.usage_in_bytes
-rw-r--r--. 1 root root 0 Oct 22 23:12 memory.use_hierarchy
-rw-r--r--. 1 root root 0 Oct 22 23:12 notify_on_release
-rw-r--r--. 1 root root 0 Oct 22 23:12 tasks

# 内存使用限制为 1G,则向 memory.limit_in_bytes 文件写入 1073741824
[root@mymaster01 mydocker]# cd /sys/fs/cgroup/memory/mydocker
[root@mymaster01 mydocker]# echo 1073741824 > memory.limit_in_bytes
# 这里将当前的shell加入到cgroup中
[root@mymaster01 mydocker]# echo $$ > tasks
# 这里使用memtester去测试内存,由于限制了1G,而去申请1.5G所以会失败
[root@mymaster01 mydocker]# memtester 1500M 1
memtester version 4.2.1 (64-bit)
Copyright (C) 2010 Charles Cazabon.
Licensed under the GNU General Public License version 2 (only).

pagesize is 4096
pagesizemask is 0xfffffffffffff000
want 1500MB (1572864000 bytes)
got  1497MB (1570492416 bytes), trying mlock ...failed for unknown reason.
Killed

# 这次改成500M,以下的测试就都能通过了
[root@mymaster01 mydocker]# memtester 500M 1


删除 cgroups

# 创建的cgroups如果不想使用了,直接删除创建的文件夹即可。
[root@mymaster01 ~]# rmdir /sys/fs/cgroup/memory/mydocker/

Docker 是如何使用cgroups的?

# 这里创建一个容器并限定内存为100m
[root@mymaster01 myBusybox]# docker run -it -m=100m busybox
/ # ls /
bin   dev   etc   home  proc  root  sys   tmp   usr   var

# 这个我们查看cgroup 的memory下发现该文件中限制了内存大小为100m
[root@mymaster01 b94152dc9b49b5128562a8db8a0d94e22097aff623f0dc779f4bdee5cf43b862]# pwd
/sys/fs/cgroup/memory/docker/b94152dc9b49b5128562a8db8a0d94e22097aff623f0dc779f4bdee5cf43b862
[root@mymaster01 b94152dc9b49b5128562a8db8a0d94e22097aff623f0dc779f4bdee5cf43b862]# cat memory.limit_in_bytes
104857600
[root@mymaster01 b94152dc9b49b5128562a8db8a0d94e22097aff623f0dc779f4bdee5cf43b862]# 

事实上,Docker 创建容器时,Docker 会根据启动容器的参数,在对应的 cgroups 子系统下创建以容器 ID 为名称的目录, 然后根据容器启动时设置的资源限制参数, 修改对应的 cgroups 子系统资源限制文件, 从而达到资源限制的效果。

Docker 组件作用

1、Docker的组件结构

2、Docker各个组件的作用

Docker的网络钟类:Libnetwork 常见的四种网络模式

1、null 空网络模式

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

使用方式为:

docker run --net=none -it busybox

2、bridge 桥接模式

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

  • Linux veth

veth 是 Linux 中的虚拟设备接口,veth 都是成对出现的,它在容器中,通常充当一个桥梁。veth 可以用来连接虚拟网络设备,例如 veth 可以用来连通两个 Net Namespace,从而使得两个 Net Namespace 之间可以互相访问。

  • Linux bridge

Linux bridge 是一个虚拟设备,是用来连接网络的设备,相当于物理网络环境中的交换机。Linux bridge 可以用来转发两个 Net Namespace 内的流量。

bridge 就像一台交换机,而 veth 就像一根网线,通过交换机和网线可以把两个不同 Net Namespace 的容器连通,使得它们可以互相通信。bridge 桥接模式是 Docker 的默认网络模式,当我们创建容器时不指定任何网络模式,Docker 启动容器默认的网络模式为 bridge。

3、host 主机网络模式

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

使用 host 主机网络模式时:

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

使用方式为:

docker run -it --net=host busybox

4、container 网络模式

container 网络模式允许一个容器共享另一个容器的网络命名空间。当两个容器需要共享网络,但其他资源仍然需要隔离时就可以使用 container 网络模式,例如我们开发了一个 http 服务,但又想使用 nginx 的一些特性,让 nginx 代理外部的请求然后转发给自己的业务,这时我们使用 container 网络模式将自己开发的服务和 nginx 服务部署到同一个网络命名空间中。

使用方式为:

docker run -d --name=busybox1 busybox sleep 3600

docker run -it --net=container:busybox1 --name=busybox2 busybox sh

通过这种方式这两个容器的网络配置都是一样的,且第二个容器使用第一个容器的网络命名空间。

Docker 卷与持久化数据存储

Docker 提供了卷(Volume)的功能,使用docker volume命令可以实现对卷的创建、查看和删除等操作。下面我们来详细了解一下这些命令。

Docker 卷的操作

1、创建数据卷

# 默认情况下 ,Docker 创建的数据卷为 local 模式,仅能提供本主机的容器访问
docker volume create myvolume

# 还可以在 Docker 启动时使用 -v 的方式指定容器内需要被持久化的路径,Docker 会自动为我们创建卷,并且绑定到容器中
docker run -d --name=nginx-volume -v /usr/share/nginx/html nginx

2、查看数据卷

# 查看已经创建的数据卷
docker volume ls

# 查看某个数据卷的详细信息, 可以看到卷的创建日期、命令、挂载路径信息
docker volume inspect myvolume

3、使用数据卷

# 使用docker volume创建的卷在容器启动时,添加 --mount 参数指定卷的名称即可使用。这里的source指的是我们的创建的卷,而target是容器内的文件系统的目录
docker run -d --name=nginx --mount source=myvolume,target=/usr/share/nginx/html nginx

 4、删除数据卷

# 删除数据卷
docker volume rm myvolume

# 注意:
# 正在被使用中的数据卷无法删除,如果你想要删除正在使用中的数据卷,需要先删除所有关联的容器。

5、容器与容器之间数据共享

有时候,两个容器之间会有共享数据的需求,很典型的一个场景就是容器内产生的日志需要一个专门的日志采集程序去采集日志内容,例如我需要使用 Filebeat (一种日志采集工具)采集 nginx 容器内的日志,我就需要使用卷来共享一个日志目录,从而使得 Filebeat 和 nginx 容器都可以访问到这个目录,这时就需要用到容器之间共享数据卷的方式。

# 先创建一个卷
docker volume create log-vol

# 然后挂载到一个运行的容器中
docker run --mount source=log-vol,target=/tmp/log --name=log-producer -it busybox

# 然后再启动一个程序,也挂载到该卷上,这样就实现了容器间的数据共享,volumes-from参数后面跟已经启动的容器名称
docker run -it --name consumer --volumes-from log-producer  busybox

 6、主机与容器之间数据共享

Docker 卷的目录默认在 /var/lib/docker 下,当我们想把主机的其他目录映射到容器内时,就需要用到主机与容器之间数据共享的方式了,例如我想把 MySQL 容器中的 /var/lib/mysql 目录映射到主机的 /var/lib/mysql 目录中,我们就可以使用主机与容器之间数据共享的方式来实现。要实现主机与容器之间数据共享,只需要我们在启动容器的时候添加-v参数即可, 使用格式为:-v HOST_PATH:CONTIANAER_PATH

# 挂载主机的 /data 目录到容器中的 /usr/local/data 中
docker run -v /data:/usr/local/data -it busybox

Docker 卷的实现原理

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

猜你喜欢

转载自blog.csdn.net/qq_32323239/article/details/109190016