Docker深入

容器中进程启动的两种模式

docker容器内运行的进程全部都是宿主机上的独立进程;该进程是不是docker容器进程本身要依据Dockerfile的写法判定。

1
2
3
4
5
在ENTRYPOINT和CMD指令中,提供了两种不同的进程执行方式:shell和exec
1. 在shell方式中,CMD/ENTRYPOINT指令如下定义:这种方式中的1号进程是以/bin/sh -c "executable param1 param2"方式启动的。
CMD "executable param1 param2"
2. 而在exec方式中CMD/ENTRYPOINT指令如下定义:此时1号进程会以executable param1 param2方式而不是shell方式启动。
CMD ["executable","param1","param2"]
  • 创建dockerfile 启动验证
    1
    2
    3
    4
    5
    6
    7
    # shell方式
    # 制作镜像docker build -t myshellredis -f shellredis/dockerfile .
    # 运行 docker run -d --name myshellredis shellredis
    FROM ubuntu:14.04
    RUN apt-get update && apt-get install redis-server -y && rm -rf /var/lib/apt/lists/*
    EXPOSE 6379
    CMD "/usr/bin/redis-server"

mark

1
2
3
4
5
# exec方式
FROM ubuntu:14.04
RUN apt-get update && apt-get install redis-server -y && rm -rf /var/lib/apt/lists/*
EXPOSE 6379
CMD ["/usr/bin/redis-server"]

mark

  • 启动总结
1
2
3
4
由此可看出,execredis镜像中以exec方式启动容器中的redis进程,所以redis进程就是容器进程本身。
容器中启动的redis进程就是容器内1号进程。

而shellredis镜像中用shell方式启动容器中的redis进程,所以被容器启动的redis进程是容器进程的一个子进程,是独立存在的。
  • 停止容器
1
2
3
4
5
6
Docker提供了docker stop和docker kill两个命令向容器中的1号进程发送信号。
当执行docker stop时,docker会首先向容器的1号进程发送一个SIGTERM信号,用于容器内程序的退出。
如果容器在收到SIGTERM信号后没有结束进程,那么docker Daemon会在等待一段时间(默认10秒)后再向容器发送SIGKILL信号,将容器杀死并变为退出状态。
这种方式给docker应用提供了一个优雅的退出机制,允许应用在收到stop命令时清理和释放使用中的资源。

而docker kill命令可以向容器内的1号进程发送任何信号,默认是发送SIGKILL信号来强制退出应用。

mark
mark

  • 总结
1
2
3
4
5
6
7
8
原因在于,用shell脚本启动的容器,其1号进程是shell进程。shell进程中没有对SIGTERM信号的处理逻辑,所以它忽略了接收到的SIGTERM信号。
当docker等待stop命令执行10秒超时之后,Docker Daemon将发送SIGKILL信号强制杀死1号进程,并销毁它的PID命名空间,其子进程redis-server也在收到SIGKILL信号后被强制终止并退出。
如果此时应用中还有正在执行的事务或未持久化的数据,强制退出进程可能导致数据丢失或状态不一致。

所以容器的1号进程必须能够正确的处理SIGTERM信号来支持优雅退出。
如果容器中包含多个进程,则需要1号进程能够正确的传播SIGTERM信号来结束所有的子进程,之后再退出。

正确的做法就是,令每个容器中只包含一个进程,同时都采用exec模式启动进程。
容器中的”隔离”
1
2
3
4
5
每个容器内部的PID和进程体系都是相互隔离、互不影响的。

docker用户组的权限和root权限差别不大,官方安全文档中警告用户谨慎使用。

namespace 内核级别的环境隔离方法
容器的本质

简单的给容器下个定论:容器 = CGroups + Namespace + Rootfs

  • Namespace
1
2
3
4
5
6
namespace是linux提供的一种内核级别的环境隔离方法,提供了对Mount、UTS、IPC、PID、Network、User等进行隔离的机制。

Namespace的实现主要是基于以下三个系统方法:
clone(): 实现线程的系统调用,用来创建一个新的进程,并可以通过设置相关参数实现隔离。
unshare():使某个进程脱离某个Namespace
setns():把某个进程加入某个Namespace中

容器技术原理

  • chroot

    1
    2
    3
    chroot 是在Unix和Linux系统的一个操作,针对正在运作的软件行程和它的子进程,改变它外显的根目录。
    chroot虽然实现了当前进程与主机的隔离,但是网络信息、进程信息等并未隔离。
    还需要Namespace、Cgroups和联合文件系统来实现完整的容器。
  • Namespace

    1
    2
    Namespace 是 Linux 内核的一项功能,该功能对内核资源进行隔离,使得容器中的进程都可以在单独的命名空间中运行,并且只可以访问当前容器命名空间的资源。
    Namespace 可以隔离进程 ID、主机名、用户 ID、文件名、网络访问和进程间通信等相关资源。
  • Cgroups

    1
    2
    Cgroups 是一种 Linux 内核功能,可以限制和隔离进程的资源使用情况(CPU、内存、磁盘 I/O、网络等)。
    在容器的实现中,Cgroups 通常用来限制容器的 CPU 和内存等资源的使用。
  • 联合文件系统

    1
    2
    联合文件系统,又叫 UnionFS,是一种通过创建文件层进程操作的文件系统,因此,联合文件系统非常轻快。
    Docker 使用联合文件系统为容器提供构建层,使得容器可以实现写时复制以及镜像的分层构建和存储。

```

-------------本文结束感谢您的阅读-------------
原创技术分享,感谢您的支持。