docker-study-1

docker-study-1

Scroll Down

Docker:

一、关于虚拟化

    1.1 计算机领域,虚拟化技术主要分为两类,一类基于硬件虚拟化,另一类基于软件虚拟化。硬件虚拟化并不多见,大都是半虚拟化与软件结合,应用较为广泛的就是基于软件的虚拟化技术。

    基于软件的虚拟化又可以分为应用虚拟化(如Wine)和平台虚拟化(如虚拟机),容器技术属于平台虚拟化中的一种(操作系统虚拟化)

二、容器

    2.1 容器技术又称为容器虚拟化,顾名思义容器就是来存放东西的道具,在Docker刚进入国内时,还有过一段时间在讨论Container这个单词是翻译为“容器”合适,还是翻译为“集装箱”合适。就像工业运输领域的集装箱一样,软件行业的容器技术也是在尝试打造一套标准化的软件构建、分发流程、以降低运维成本、提高软件安全与运行稳定。与工业运输集装箱不同的是,它不仅仅是要打造一个运输用的“集装箱”,还要保证软件在容器内能够运行,在操作系统上打造一个“独立”的箱子,这需要解决文件系统、网络、硬件等多方面的问题。

    2.2 为什么要使用容器?

    与传统软件行业的开发与运维相比,容器虚拟化可以更高效的构建应用,也更容易管理维护。就像常见的搭建LNMP组合开发网站,按照传统做法自然是各种安装,然后配置、测试、发布一系列麻烦事,当服务器进行搬迁后,还需要在执行一次之前的部署步骤,迁移后还会出现无法预测的bug等问题。

    但是如果使用了容器技术,运维只需要一句简单的命令即可部署一整套LNMP环境,即使搬迁也只用打包传输即可,在另一台机器上也不会出现“水土不服”的问题。

    2.3 容器的前世今生 (略)

    2.3.1 容器技术的起源 chroot( 暂略 )

    2.3.2 容器的发展 ( 略 )

    2.4 容器的原理

    容器的核心技术是Cgroups(Control groups)和Linux的命名空间namespace,容器的本质是宿主机上的进程,容器技术通过namespace实现资源隔离,通过Cgroups实现资源控制,通过rootfs实现文件系统隔离,再加上容器引擎自身的特性来管理容器的生命周期。

    2.4.1 namespace( 实现资源隔离 )

namespace隔离内容
IPC信号量、消息队列和共享内存
Network网络资源
Mount文件系统挂载点
PID远程ID
UTS主机名和域名
User用户ID和组ID

        对namespace的操作主要通过clone( )、setns()、unshare()这三个系统调用来完成。

        ① clone( )用来创建一个新的进程(namespace),clone有一个flags参数,该参数以CLONE_NEW*为格式,包括CLONE_NEWNS、CLONE_NEWIPC、CLONE_NEWPID、CLONE_NEWNET、CLONE_USER、CLONE_UTS,通过传入这些参数后,由clone()创建出来的进程就被位于新的namespace之中了。

        ( 因为Mount namespace 是第一个实现的namespace,当初没有考虑到还有其他namespace的出现,因次用了CLONE_NEWNS的名字,而不是包括CLONE_NEWMNT之类的名字,其他CLONE_NEW*都可以看名字知用途。)

        ② 那么如何为已有的进程创建新的namespace呢?

        这就需要用到unshare()。使用unshare()调用的进程会被放进新的namespace里面,而sents()则是将进程放到已有的namespace中,docker exec的命令的实现原理就是setns()
事实上,开发namespace的目的就是实现轻量级虚拟化服务,在同一个namespace下的进程可以彼此响应,而对外界隔离,这样在一个namespace下,进程仿佛处于一个独立的系统环境中,达到容器的目的。

小实践:

    Linux提供的namespace操作API。

    包括有clone(),setns(),unshare()

     如何查看当前进程的namespace

# ls -l /proc/$$/ns

    ( $$代表的是当前进程,诸如4026531835这样的数字表示的就是当前进程的namespace,当两个进程指向同一串数字,表示他们处于同一个namespace下 )

     使用clone()创建新的namespace

int clone(int (*fn)(void *), void *child_stack, int flags, void *arg);

    fn:传入子进程运行的程序主函数

    child_stack:传入子进程使用的栈空间

    flags:使用哪些标志位,与namespace相关的标志位主要包括CLONE_NEWNS、CLONE_NEWIPC、CLONE_NEWPID、CLONE_NEWNET、CLONE_USER、CLONE_UTS。

    arg:传入的用户参数

    clone()的功能是flags控制决定的,如果调用clone()时设置了CLONE_NEW标志,一个与之对应的新的命名空间将会被创建,新的进程属于该命名空间,可以使用多个CLONE_NEW标志的组合

     使用setns()关联一个已经存在的namespace

    当一个namespace没有进程时还保持其打开,这么做是为了后续添加进程到该namespace,而添加这个功能就是使用setns()系统调用来完成,这使得调用的进程能够和namespace关联,docker exec就需要用到这个方法:

int  setns(int fd,int nstype);

        fd:表示要加入的namespace的文件描述符。它是一个指向/proc/[pid]/ns目录的文件描述符,可以通过直接打开该目录下的链接得到。

        nstype:让调用者可以检查fd指向的namespace类型是否符合实际的要求。参数为0表示不检查。

     使用unshare()在已有进程上进行namespace隔离

    2.4.2 Cgroups

    Cgroups是Linux内核提供的一种可以限制、记录、隔离进程组(process groups)所使用的物理资源(如 CPU,内存,I/O等)的机制。

    cgroups(Control Groups) 是 linux 内核提供的一种机制,这种机制可以根据需求把一系列系统任务及其子任务整合(或分隔)到按资源划分等级的不同组内,从而为系统资源管理提供一个统一的框架。简单说,cgroups 可以限制、记录任务组所使用的物理资源。本质上来说,cgroups 是内核附加在程序上的一系列钩子(hook),通过程序运行时对资源的调度触发相应的钩子以达到资源追踪和限制的目的。

    cgroups 的主要作用:

    实现 cgroups 的主要目的是为不同用户层面的资源管理提供一个统一化的接口。从单个任务的资源控制到操作系统层面的虚拟化,cgroups 提供了四大功能:

    资源限制:cgroups 可以对任务使用的资源总额进行限制。
比如设定任务运行时使用的内存上限,一旦超出就发 OOM。

    优先级分配:通过分配的 CPU 时间片数量和磁盘 IO 带宽,实际上就等同于控制了任务运行的优先级。

    资源统计:cgoups 可以统计系统的资源使用量,比如 CPU 使用时长、内存用量等。这个功能非常适合当前云端产品按使用量计费的方式。

    任务控制:cgroups 可以对任务执行挂起、恢复等操作。

    2.4.3 容器的创建

    (1) 系统调用clone()创建新进程,拥有自己的namespace

        该进程拥有自己的pid、mount、user、net、ipc和uts namespace

# pid = clone(fun,stack,flags,clone_arg)

    (2) 将pid写入Cgroup子系统这样就受到Cgroups子系统控制

# echo$pid > /sys/fs/cgroup/cpuset/tasks
# echo$pid > /sys/fs/cgroup/bikio/tasks
# echo$pid > /sys/fs/cgroup/memory/tasks
# echo$pid > /sys/fs/cgroup/devices/tasks
# echo$pid > /sys/fs/cgroup/feezer/tasks

    (3)通过pivot_root系统调用,使进程进入一个新的rootfs,之后通过exec()系统调用,在新的namespace、Cgroups、rootfs中执行/bin/bash

	pivot_root(“path_of_rootfs/”,path)
	exec(“/bin/bash”);
}

    通过上面的操作,成功的在一个容器中运行了/bin/bash

    2.5 容器云

    无论是个人还是企业,在使用上都有各种各样的需求,例如主机连接容器,各种类型的负载均衡,持续构建、集成和交付,以及大规模容器管理等。

    容器难以满足所有的要求,于是慢慢慢慢出现了容器云,完整来说,容器云是以容器为资源分割和调度的基本单位,通过容器封装软件运行环境,为用户提供一个集构建、发布和运行于一体的分布式应用平台,它与Iaas、Paas等不同,容器云可以共享与隔离资源、编排与部署容器。在这一点上容器云与Iaas相似,但是容器云也可以渗透到应用支撑与运行时环境,在这点上与Paas类似。

    当然容器云并不是特指以docker为基础的容器技术,使用其他容器技术(CoreOS的Rocket项目)实现容器云也是可以的。

实践部分

一、启用 ELRepo 仓库

     ELRepo 仓库是基于社区的用于企业级 Linux 仓库,提供对 RedHat Enterprise (RHEL) 和 其他基于 RHEL的 Linux 发行版(CentOS、Scientific、Fedora 等)的支持。

    ELRepo 聚焦于和硬件相关的软件包,包括文件系统驱动、显卡驱动、网络驱动、声卡驱动和摄像头驱动等。

    1.1启用 ELRepo 仓库:

# rpm --import https://www.elrepo.org/RPM-GPG-KEY-elrepo.org 
# rpm -Uvh http://www.elrepo.org/elrepo-release-7.0-2.el7.elrepo.noarch.rpm
# rm -f /var/run/yum.pid

二、安装最新内核:

# yum --enablerepo=elrepo-kernel install  kernel-ml-devel kernel-ml -y
# uname -r

三、设置 grub2

    内核安装好后,需要设置为默认启动选项并重启后才会生效

    3.1查看系统上的所有可以内核:
    # awk -F' '$1=="menuentry " {print i++ " : " $2}' /etc/grub2.cfg

    3.2设置 grub2

    可以通过 grub2-set-default 0 命令或编辑 /etc/default/grub 文件来设置。

# grub2-set-default 0
# reboot

    3.3(也可以选择编辑 /etc/default/grub 文件)

    设置 GRUB_DEFAULT=0,表示使用上一步的 awk 命令显示的编号为 0 的内核作为默认内核:

# vi /etc/default/grub

GRUB_TIMEOUT=5
GRUB_DISTRIBUTOR="$(sed 's, release .*$,,g' /etc/system-release)"
GRUB_DEFAULT=0
GRUB_DISABLE_SUBMENU=true
GRUB_TERMINAL_OUTPUT="console"
GRUB_CMDLINE_LINUX="crashkernel=auto console=ttyS0 console=tty0 panic=5"
GRUB_DISABLE_RECOVERY="true"
GRUB_TERMINAL="serial console"
GRUB_TERMINAL_OUTPUT="serial console"
GRUB_SERIAL_COMMAND="serial --speed=9600 --unit=0 --word=8 --parity=no --stop=1"

    下一步,通过 gurb2-mkconfig 命令创建 grub2 的配置文件,然后重启:

# grub2-mkconfig -o /boot/grub2/grub.cfg
# reboot

    3.4 安装必要依赖

# yum install -y yum-utils device-mapper-persistent-data lvm2

    3.5 添加软件源信息

# yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo

    3.6 更新 yum 缓存

# yum makecache fast

    3.7 安装 Docker

# yum install docker-ce docker-ce-cli containerd.io

四、docker基本操作

    可以理解为镜像是 Docker 生命周期中的构件或打包阶段,而容器则是启动或执行阶段

      ①  centos8 之后docker就被替换为podman了,在 centos7 及之前版本中可直接使用 yum install docker 安装 docker

      ②  创建一个交互式 docker 容器

#  docker  run  -i  -t  centos  /bin/bash

    ($docker  create ……    仅创建不运行,可以在容器工作流中对其进行细粒度的控制)

    第一次 run 会从 docker 官方拉取 centos 镜像,普通情况下可能会显示 Client.Timeout exceeded while awaiting headers

    该提示即连接不上 docker 官网,解决办法就是使用国内容器镜像加速,解决办法:https://www.ishells.cn/archives/docker-run

    -i  标志保证容器中 STDIN 是开启的,持久的标准输入是交互式 shell 的半边天

    -t 标志则是另外的半边天,它告诉 Docker 为要创建的容器分配一个伪 tty 终端,这样,新创建的容器才能保证一个交互式 shell,这两个参数是创建一个交互式 shell 最基本的参数

这条命令的执行流程为:

    首先 Docker 会检查本地是否存在 centos 镜像,如果本地还没有该镜像的话,那么 Docker 会连接官方维护的 Docker Hub Registry(未修改镜像来源),查看 Docker Hub 中是否有该镜像。Docker 一旦找到该镜像,就会下载该镜像并将其保存到本地宿主机中 

    随后,Docker 在文件系统内部用这个镜像创建了一个新容器。该容器拥有自己的网络、IP 地址,以及一个用来和宿主机进行通信的桥接网络接口,最后我们告诉 Docker 在新容器重要运行什么命令,在本例中我们在容器中运行、bin/bash 命令启动了一个 bash shell.

    当容器创建完毕之后,Docker 就会执行容器中的 / bin/bash 命令,这时就可以看到容器内的 shell 了。

    这里如果你仅仅使用了-d选项选择后台启动容器,没有指定任意一个前台进程的话,容器创建之后就会自动关闭

    你必须指定一个前台进程(哪怕是ping等),否则容器创建之后就会自动关闭。例如使用:

# docker run -d centos ping www.baidu.com

      ③ 当你输入 exit,就可以返回到宿主机的命令提示符了

      ④ 输入 exit 之后,交互式容器已经停止运行了,只有指定的 / bin/bash 命令处于运行状态的时候,交互式容器才会处于相应的运行状态。

      但容器仍然是存在的,可以用 docker  ps  -a  查看当前系统中容器的列表。

      当执行 docker ps 时,只会列出正在运行的容器。

      docker   ps  -l    带上 - l 标志,列出最后一个运行的容器,无论其是正在运行还是已经停止

      也可以通过 --format 标志,进一步控制显示哪些信息

      ⑤  创建容器时可以加上 --name  指定容器名字,之后便可以通过名字对容器进行操作,如果不指定默认随机产生

     

      ⑥  重新启动已经停止的容器

       $docker start/restart  byname    重新启动容器,但是交互式容器并不会使用命令提示符

     

      docker 容器重新启动的时候,会沿用 docker run 命令时制定的参数来运行,因此我们这里重新启动后会运行一个交互式会话 shell

      ⑦  使用 name 或 ID 附着到正在运行的容器上

$docker attach  byname 

      ​ 

      ⑧  创建守护式容器

      另一种选择是创建守护式容器,及长期运行的容器,其没有交互式会话,非常适合运行应用程序和服务。

$ docker run --name daemon_dave -d centos /bin/bash -c  "while true; do echo hello world; sleep 1; done"

      -d 参数会将容器放到后台运行。后边的 while 循环是此容器要执行的任务。

      通过组合使用上面这些参数,你会发现 docker  run 并没有像交互式容器一样将主机的控制台附着到新的 shell 会话上,而是仅仅返回了一个容器 ID 而已。

      查看正在运行的 daemon_dave 容器

      ⑨ 为了探究容器内部在干什么,可以用 docker logs 命令来获取容器的日志。

   

      看到 while 循环正在向日志中打印 hello world。Docker 会输出最后几条日志项并返回,我们还可以在命令后使用 - f 参数来监控 Docker 的日志,这与 tail -f 命令相似。

      这个命令一直在输出日志,通过 ctrl + c 退出日志跟踪

      为了让调试更简单,还可以加上 - t 为每条日志项加上时间戳

      ⑩  Docker 日志驱动

      自 Docker 1.6 开始,也可以控制 Docker 守护进程和容器所用的日志驱动,这可以通过 --log-driver 选项来实现。可以在启动 Docker 守护进程或者执行 docker run 命令时使用这个选项

      有好几个选项,包括默认的 json-file,json-file 也为我们前面看到的 docker logs 命令提供了基础

      其他可用的选项还包括 syslog,该选项将禁用 docker logs 命令,并且将所有容器的日志输出都重定向到 Syslog。可以在启动 Docker 守护进程时指定该选项,将所有容器的日志都输出到 Syslog,或者通过 docker run 对个别的容器进行日志重定向输出

# docker run --log-driver="syslog" --name daemon_dwayne -d centos /bin/bash -c "while true; do echo hello world; sleep 1; done"

      上面的命令会将 daemon_dwayne 容器的日志都输出到 Syslog,导致 docker logs 命令不输出任何东西

      最后,还有一个可用的选项是 none,这个选项将会禁用所有容器中的日志,导致 docker logs 命令也被禁用

      ⑪  查看容器内进程

      查看容器内进程要使用 docker top 命令
image.png

      该命令执行后,可以看到容器内的所有进程、运行进程的用户及进程 ID

      ⑫   Dcoker 统计信息

      使用docker stats命令,它用来显示一个或多个容器的统计信息

$ docker stats daemon_dave

      能看到一个守护式容器的列表,以及它们的 CPU、内存、网络 I/O 及存储 I/O 的性能和指标。这对快速监控一台主机上的一组容器非常有用

      ⑭ 在容器内部运行进程

      可以通过 docker exec 命令在容器内部额外启动新进程。可以在容器内运行的进程有两种类型:后台任务和交互式任务。后台任务在容器内运行且没有交互需求,而交互式任务则保持在前台运行。对于需要在容器内部打开 shell 的任务,交互式任务是很实用的

      也可以在 daemon_dave 容器中启动一个诸如打开 shell 的交互式任务

      和运行交互容器时一样,这里的 - t 和 - i 标志为我们执行的进程创建了 TTY 并捕捉 STDIN。接着我们指定了要在内部执行这个命令的容器的名字以及要执行的命令。在上面的例子中,这条命令会在 daemon_dave 容器内创建一个新的 bash 会话,有了这个会话,我们就可以在该容器中运行其他命令了

      ⑮   停止守护式容器

      要停止守护式容器,只需要执行 docker stop 命令,后跟 ID 或 Name

      还有一个很实用的命令 docker ps -n x,该命令会显示最后 x 个容器,不论这些容器正在运行还是已经停止

      ⑯ 自动重启容器

      如果由于某种错误而导致容器停止运行,还可以通过 --restart 标志,让 Docker 自动重新启动该容器。--restart 标志会检查容器的退出代码,并据此来决定是否要重启容器。默认的行为是 Docker 不会重启容器

      例子:

      在本例中,--restart 标志被设置为 always。无论容器的退出代码是什么,Docker 都会自动重启该容器。除了 always,还可以将这个标志设为 on-failure,这样,只有当容器的退出代码为非 0 值的时候,才会自动重启。另外,on-failure 还接受一个可选的重启次数参数

--restart=on-failure:5

      这样,当容器退出代码为非 0 时,Docker 会尝试自动重启该容器,最多重启 5 次

      ⑰   深入容器

      除了通过 docker ps 命令获取容器的信息,还可以使用 docker inspect 来获得更多的容器信息

      docker inspect 命令会对容器进行详细的检查,然后返回其配置信息,包括名称、命令、网络配置以及很多有用的数据,也可以用 - f 或者 --format 标志来选定查看结果

      上面这条命令会返回容器的运行状态,示例中该状态为 false

      也可以同时指定多个容器,并显示每个容器的输出结果

      可以为该参数指定要查询和返回的查看散列(inspect hash)中的任意部分

      ⑱   删除容器

      如果容器已经不再使用,可以使用 docker rm 命令来删除它们。目前,还没有办法一次删除所有容器,不过可以通过下图所示的小技巧来删除全部容器

      上面的 docker ps 命令会列出现有的全部容器,-a 标志代表列出所有容器,而 - q 标志则表示只需要返回容器的 ID 而不会返回容器的其他信息。这样我们就得到了容器 ID 的列表,并传给了 docker rm 命令,从而达到删除所有容器的目的