- 进程
- 隔离与限制
- namespaces修改进程视图,其实就是创建新进程clone()的一个可选参数
- 创建容器进程时,指定了这个进程所需要启用的一组 namespace 参数,
- 容器只是一种特殊的进程而已,但由于本质上还是宿主机上的一个进程,所以隔离的不彻底
- 1. 多个容器之间还是共享的同一个宿主机的操作系统内核,这一点不如虚拟机可以虚拟出任何OS
- 2. linux内核中很多资源和对象不能被namespace化,最典型的就是:时间
- cgroups给用户暴露的接口是文件系统上的目录和文件,/sys/fs/cgroup下
- 可以通过 mount -t cgroup 查看
- 目录下包含可以被cgroup限制的资源种类,目录下的文件内容就可以对其进行限制
- 容器启动时就是把docker run的参数写入到对应的cgroup下面的文件内,并把PID写入对应tasks文件
- cgroup限制的也不够彻底,主要的是容器内top命令查看的是宿主的情况,即/proc文件系统不了解/sys/fs/cgroup
- ls /sys/fs/cgroup #查看可以被cgroup限制的资源子系统
- ls /sys/fs/cgroup/cpu
- mkdir test && cd test && ls #这个test目录即为控制组
- while : ; do : ; done & #执行一个死循环,把cpu吃到100%
- top #查看 36214号PID
- cat cpu.cfs_quota_us #quota没有受限
- cat cpu.cfs_period_us #period默认100ms(100000us)
- echo 20000 > cpu.cfs_quota_us #设置quota为20ms,即每一个period内只有20ms可使用
- echo 36214 > tasks # 把对应的PID写入tasks文件,使资源限制生效
- top #再次查看资源使用情况
- Docker容器
- 使用官方提供的Python开发镜像作为基础镜像
- 将工作目录切换为/app
- 将当前目录下的所有内容复制到/app下
- 使用pip命令安装这个应用所需要的依赖
- 允许外界访问容器的80端口
- 设置环境变量
- 设置容器进程为:python app.py,即:这个Python应用的启动命令
- 容器实际运行的完整进程是 “ENTRYPOINT CMD”
- 默认情况下 Docker 提供一个隐含的ENTRYPOINT即/bin/sh -c
- CMD的内容就是ENTRYPOINT的参数
- 所以上面的容器实际运行的完整进程是 /bin/sh -c “python app.py”
- docker run -d -v /test helloworld
- docker volume ls
- ls /var/lib/docker/volumes/cb1c2f7221fa/_data/
- docker exec -it cf53b766fa6f /bin/sh
- ls /var/lib/docker/volumes/cb1c2f7221fa/_data/
- ls /var/lib/docker/aufs/mnt/6780d0778b8a/test #宿主机上查看容器读写层的目录,发现是空的
- 所以这个文件不会被commit进镜像,但会在镜像中创建一个空的test目录
- K8S本质
进程
- 程序运行起来后计算机执行环境的总和就是进程
- 进程
- 静态表现:程序代码保存在磁盘中
- 动态表现:运行起来后数据和状态的总和
- 容器技术的核心功能就是通过修改进程视图和设置资源限制,从而为其创建一个边界
- namespace技术用来修改进程视图,容器只是一种特殊的进程
- PID Namespace
- Mount Namespace
- UTS Namespace
- IPC Namespace
- Network Namespace
- User Namespace
- cgroups,LinuxControlGroup,限制一个进程组能够使用的资源上限、优先级设置、审计、挂起和恢复
- CPU
- 内存
- 磁盘
- 网络
- namespace技术用来修改进程视图,容器只是一种特殊的进程
- 容器是一个单进程模型
- 容器的本质就是一个进程
- 用户的应用进程实际上就是容器里PID=1的进程
- 容器本身的设计希望容器和应用同生命周期,对后期编排非常重要,防止出现容器还在,应用已挂的情况
隔离与限制
```shellnamespaces修改进程视图,其实就是创建新进程clone()的一个可选参数
int pid = clone(main_function, stack_size, CLONE_NEWPID |SIGCHLD, NULL);创建容器进程时,指定了这个进程所需要启用的一组 namespace 参数,
容器只是一种特殊的进程而已,但由于本质上还是宿主机上的一个进程,所以隔离的不彻底
1. 多个容器之间还是共享的同一个宿主机的操作系统内核,这一点不如虚拟机可以虚拟出任何OS
2. linux内核中很多资源和对象不能被namespace化,最典型的就是:时间
cgroups给用户暴露的接口是文件系统上的目录和文件,/sys/fs/cgroup下
可以通过 mount -t cgroup 查看
目录下包含可以被cgroup限制的资源种类,目录下的文件内容就可以对其进行限制
容器启动时就是把docker run的参数写入到对应的cgroup下面的文件内,并把PID写入对应tasks文件
cgroup限制的也不够彻底,主要的是容器内top命令查看的是宿主的情况,即/proc文件系统不了解/sys/fs/cgroup
ls /sys/fs/cgroup #查看可以被cgroup限制的资源子系统
blkio cpu,cpuacct freezer net_cls perf_event cpu cpuset hugetlb net_cls,net_prio pids cpuacct devices memory net_prio systemd
ls /sys/fs/cgroup/cpu
cgroup.clone_children cpuacct.usage cpu.rt_runtime_us system.slice cgroup.event_control cpuacct.usage_percpu cpu.shares tasks cgroup.procs cpu.cfs_period_us cpu.stat user.slice cgroup.sane_behavior cpu.cfs_quota_us notify_on_release cpuacct.stat cpu.rt_period_us release_agent
mkdir test && cd test && ls #这个test目录即为控制组
cgroup.clone_children cpuacct.usage_percpu cpu.shares cgroup.event_control cpu.cfs_period_us cpu.stat cgroup.procs cpu.cfs_quota_us notify_on_release cpuacct.stat cpu.rt_period_us tasks cpuacct.usage cpu.rt_runtime_us
while : ; do : ; done & #执行一个死循环,把cpu吃到100%
[1] 36214
top #查看 36214号PID
top - 20:05:43 up 156 days, 4:45, 2 users, load average: 0.72, 0.31, 0.19 Tasks: 308 total, 4 running, 304 sleeping, 0 stopped, 0 zombie %Cpu(s): 13.3 us, 0.5 sy, 0.0 ni, 86.2 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st KiB Mem : 16249876 total, 3736296 free, 4290776 used, 8222804 buff/cache KiB Swap: 0 total, 0 free, 0 used. 10864892 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 36214 root 20 0 116496 1472 136 R 100.0 0.0 1:19.73 bash
cat cpu.cfs_quota_us #quota没有受限
-1
cat cpu.cfs_period_us #period默认100ms(100000us)
100000
echo 20000 > cpu.cfs_quota_us #设置quota为20ms,即每一个period内只有20ms可使用
echo 36214 > tasks # 把对应的PID写入tasks文件,使资源限制生效
top #再次查看资源使用情况
top - 20:15:04 up 156 days, 4:54, 2 users, load average: 0.37, 0.72, 0.50 Tasks: 305 total, 2 running, 303 sleeping, 0 stopped, 0 zombie %Cpu(s): 2.9 us, 0.2 sy, 0.0 ni, 96.9 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st KiB Mem : 16249876 total, 3742296 free, 4284572 used, 8223008 buff/cache KiB Swap: 0 total, 0 free, 0 used. 10871096 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 36214 root 20 0 116496 1472 136 R 19.9 0.0 9:53.80 bash
<a name="hM4Cq"></a>
# 容器镜像
- 对于Docker项目来说,最核心的原理实际上就是为待创建的用户进程
- 启用Linux Namespace配置
- 设置指定的 Cgroups参数
- 切换进程的根目录chroot(ChangeRoot)生成rootfs,必须伴随着mount才能改变MountNamespace视图
- 容器内的rootfs不包含操作系统内核,容器共享宿主机内核,有需要配置内核参数的会影响到全局
- 但也是因为这样保证了开发和线上环境的高度一致性
- Docker镜像制作
- 没有沿用之前的rootfs的标准流程,而是做了一个小小的创新
- 镜像的设计中,引入了层的概念,用户制作镜像的每一步操作都会生成一个层,也就是一个增量的rootfs
- 引入了联合文件系统UnionFS(UnionFileSystem),把多个不同位置的目录联合挂载到同一目录下
- docker image inspect ImageID # 查看层
```shell
# UnionFS
# tree
.
├── A
│ ├── a
│ └── x
└── B
├── b
└── x
# mkdir C
# mount -t aufs -o dirs=./A:./B none ./C
# aufs AdvanceUnionFS,是对UnionFS的重写和改进
# tree ./C
./C
├── a
├── b
└── x
# 容器真正UnionFS挂载的目录
# ls /var/lib/docker/aufs/mnt/6e3be5d2ecccae7cc0fcfa2a2f5c89dc21ee30e166be823ceaeba15dce645b3e
bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
# 容器镜像的层
# docker image inspect ubuntu:latest
...
"RootFS": {
"Type": "layers",
"Layers": [
"sha256:f49017d4d5ce9c0f544c...",
"sha256:8f2b771487e9d6354080...",
"sha256:ccd4d61916aaa2159429...",
"sha256:c01d74f99de40e097c73...",
"sha256:268a067217b5fe78e000..."
]
}
# cat /proc/mounts| grep aufs # 通过aufs挂载信息找到 si
none /var/lib/docker/aufs/mnt/6e3be5d2ecccae7cc0fc... aufs rw,relatime,si=972c6d361e6b32ba,dio,dirperm1 0 0
# cat /sys/fs/aufs/si_972c6d361e6b32ba/br[0-9]* # 查看各个层的信息
/var/lib/docker/aufs/diff/6e3be5d2ecccae7cc...=rw
/var/lib/docker/aufs/diff/6e3be5d2ecccae7cc...-init=ro+wh
/var/lib/docker/aufs/diff/32e8e20064858c0f2...=ro+wh
/var/lib/docker/aufs/diff/2b8858809bce62e62...=ro+wh
/var/lib/docker/aufs/diff/20707dce8efc0d267...=ro+wh
/var/lib/docker/aufs/diff/72b0744e06247c7d0...=ro+wh
/var/lib/docker/aufs/diff/a524a729adadedb90...=ro+wh
# /var/lib/docker/aufs/diff/下的所有目录被联合挂载到/var/lib/docker/aufs/mnt/6e3be5d2eccca*
只读层
- ro+wh:readonly + whiteout
以增量的方式分别包含操作系统的一部分
# ls /var/lib/docker/aufs/diff/72b0744e06247c7d0... etc sbin usr var # ls /var/lib/docker/aufs/diff/32e8e20064858c0f2... run # ls /var/lib/docker/aufs/diff/a524a729adadedb900... bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
读写层
rw:read write
- 在没有写入文件之前,这个目录是空的
- 一旦在容器中做了写操作,修改的内容就以增量的方式出现在这个层
- 为了实现删除只读层的文件的效果,会在可读写层创建一个whiteout文件,遮挡只读层的文件实现删除的效果
- 如果要删除只读层的foo文件,需要在可读写层创建一个.wh.foo的文件,UnionFS就会让其消失
所以可读写层专门用来保存修改rootfs后的增量,后期可以用docker commit 和push保存这个可读写层
init层
是一个以-init 结尾的层,夹在只读层和读写层中间
- 是docker项目单独生成的一个内部层
- /etc/hosts
- /etc/hostname
- /etc/resolv.conf
- 这些文件原本属于只读层的文件,但用户往往需要自定义,所以需要在读写层修改,但这些修改只对当前容器有效,不希望被commit,所以单独抽离了一层,不被commit提交,又可以自定义。
Docker容器
全景图
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”]
容器实际运行的完整进程是 “ENTRYPOINT CMD”
默认情况下 Docker 提供一个隐含的ENTRYPOINT即/bin/sh -c
CMD的内容就是ENTRYPOINT的参数
所以上面的容器实际运行的完整进程是 /bin/sh -c “python app.py”
- 使用标准的原语(大写的关键词)描述要构建的镜像,每条原语都会生成一个对应的镜像层
- 按顺序执行
<a name="0C9pA"></a>
## 命令
- docker build -t TAG .
- docker image ls
- docker run -p 38080:8080 [--net host|container:CID] helloworld [CMD]
- docker run -v [host_path:]container_path helloworld
<a name="N9g30"></a>
## COW
- Copy-On-Write
- 容器里对镜像rootfs所做的任何修改都会被操作系统先复制到读写层,然后再修改
- init层不会被commit
<a name="YujQS"></a>
## Volume
- 允许将宿主机上的目录和文件挂载到容器内部进行读取和修改
- 默认宿主机上的目录在/var/lib/docker/volumes/[VOLUME_ID]/_data
- 在rootfs准备好后,执行chroot之前,把volume指定的宿主机目录挂载到容器内的目录对应在宿主机上的目录(/var/lib/docker/aufs/mnt/[可读写层ID]/容器内目录)即可
- Linux的BindMount绑定挂载的主要作用就是允许将一个目录或文件而不是整个设备挂载到一个指定的目录上
- 在挂载点上的任何操作都是在被挂载的目录或文件上,原挂载点的内容被隐藏起来不受影响
- 绑定挂载实际是一个inode的替换过程,inode存放文件内容的对象,dentry是目录项即访问这个inode的指针
- docker commit时 -v挂载的目录也不会被提交,因为在宿主机上操作 commit,宿主机不知道ns的隔离
![bindMount.png](https://cdn.nlark.com/yuque/0/2020/png/1491874/1601304063938-f164edc3-94d4-4660-aa80-8dae5937760e.png#align=left&display=inline&height=342&margin=%5Bobject%20Object%5D&name=bindMount.png&originHeight=342&originWidth=1497&size=20425&status=done&style=none&width=1497)
docker run -d -v /test helloworld
cf53b766fa6f
docker volume ls
DRIVER VOLUME NAME local cb1c2f7221fa9b0971cc35f68aa1034824755ac44a034c0c0a1dd318838d3a6d
ls /var/lib/docker/volumes/cb1c2f7221fa/_data/
docker exec -it cf53b766fa6f /bin/sh
cd test/ touch text.txt
ls /var/lib/docker/volumes/cb1c2f7221fa/_data/
text.txt