《深入剖析Kubernetes - 08 | 白话容器基础(四):重新认识Docker容器》


1、Dockerfile 制作

制作rootfs 常用的方式:Dockerfile

# 使用官方提供的 Python 开发镜像作为基础镜像
FROM python:2.7-slim

# 将工作目录切换为 /app
WORKDIR /app

# 将当前目录下的所有内容复制到 /app 下
ADD . /app

# 使用 pip 命令安装这个应用所需要的依赖
RUN pip install --trusted-host pypi.python.org -r requirements.txt

# 允许外界访问容器的 80 端口
EXPOSE 80

# 设置环境变量
ENV NAME World

# 设置容器进程为:python app.py,即:这个 Python 应用的启动命令
CMD ["python", "app.py"]


注意事项:

1、ENTRYPOINT 和 CMD 为容器启动必需参数,docker会提供一个默认的ENTRYPOINT : /bin/sh -c ,故在不指定ENTRYPOINT 时,直接指定CMD,实际上执行的命令是/bin/sh -c CMD

2、ADD 和 COPY 的区别:ADD 可以如果添加的是一个压缩包,会自动解压,COPY不会

3、每个指令都会生成对应的镜像层,所以在写RUN的时候可以通过连接符写多个命令,避免产生过多的镜像层,例如:

RUN ln -s /data/services/nginx /usr/local/nginx && \
    mkdir -p /data/weblog/nginx && \
    /etc/init.d/nginx start

创建完dockerfile 通过以下命令构建镜像(在Dockerfile所在目录下)

# docker build -t test-images .

然后通过docker push 上传到镜像仓库

# docker tag test-images test/nginx:1.14.2 
# docker push test/nginx:1.14.2


也可以通过commit的方式创建容器镜像,具体做法如下;

docker exec -it 4ddf4638572d /bin/sh
# 在容器内部新建了一个文件
root@4ddf4638572d:/app# touch test.txt
root@4ddf4638572d:/app# exit

# 将这个新建的文件提交到镜像中保存
$ docker commit 4ddf4638572d geektime/helloworld:v2


docker commit 其实就是在容器起来后,加上最上层的读写层,还有原先镜像中的只读层构成镜像。其中只读层在宿主机上是共享的,不会占用额外空间。

根据联合文件系统,在镜像rootfs上做的任何更改都会在最上层先复制一层,再此基础上进行修改,也就是所谓的写时复制(copy on write)


2、docker exec 实现原理

宿主机上可以看到容器执行的进程,通过PS 看到进程pid后(假设为25686),在/proc/25686/ns 这个目录下,可以看到全部ns对应的文件

ls -l  /proc/25686/ns
total 0
lrwxrwxrwx 1 root root 0 Aug 13 14:05 cgroup -> cgroup:[4026531835]
lrwxrwxrwx 1 root root 0 Aug 13 14:05 ipc -> ipc:[4026532278]
lrwxrwxrwx 1 root root 0 Aug 13 14:05 mnt -> mnt:[4026532276]
lrwxrwxrwx 1 root root 0 Aug 13 14:05 net -> net:[4026532281]
lrwxrwxrwx 1 root root 0 Aug 13 14:05 pid -> pid:[4026532279]
lrwxrwxrwx 1 root root 0 Aug 13 14:05 pid_for_children -> pid:[4026532279]
lrwxrwxrwx 1 root root 0 Aug 13 14:05 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0 Aug 13 14:05 uts -> uts:[4026532277]


然后通过setns() 的系统调用,即可将进程加入到对应的ns中。

setns() 需要2个参数,第一个参数是要加入的namespace文件路径,如/proc/25686/ns/net;第二个参数是要执行的程序,如/bin/bash

当docker启动时指定--net=host,则容器启动时不会为进程启动network namespace,容器跟宿主机共享一个网络栈。


3、voluem实现机制

主要解决宿主机和容器之间文件互通的问题,例如:

(1) 宿主机上如何访问到容器产生的文件

(2) 容器怎么访问到宿主机上的文件

在docker上,可以通过以下两种方式实现

$ docker run -v /test ...
$ docker run -v /home:/test ...

第一种方式相当于在宿主机本地创建一个temp目录,再挂载到容器的/test目录

第二种方式则是将宿主机上的/home目录挂载到容器的/test目录


本质上是使用了linux的bind mount机制,其主要作用是允许将一个目录或文件而不是这块设备挂载到指定目录上。其原理是一个inode替换的过程,在linux中,inode存放的文件内容的对象,而dentry则存放的是指向这个对象的指针。故这个挂载的过程,实际上就是修改指针,指向另外一个inode,执行umount 时则将指针指向回原来的inode。

image.png


注意:

1、在挂载目录上做的操作并不会影响源目录

2、对于挂载目录的修改,执行docker commit 时不会生效,只会创建对应的空目录