0
点赞
收藏
分享

微信扫一扫

k8s之docker容器


k8s之docker容器

容器是什么?

容器,就是一个被隔离的进程。

为什么要隔离?

  1. 将应用程序与外界系统隔离,保证容器外系统安全
  2. 资源隔离,只能使用指定配额

和虚拟机的区别是什么?

虚拟机:虚拟的是硬件,需要在上面安装操作系统才能运行应用程序。

容器:共享下层的硬件和操作系统。

下图是官方的图

k8s之docker容器_容器

其实上图关于容器的部分并不准确,APP也就是容器并不是运行在Docker上的,Docker只是在帮助用户创建进程时添加了各种Namespace参数,容器是特殊的进程,还是运行在操作系统上的。


实现方式

优势

劣势

虚拟机

虚拟化硬件

隔离程度非常高

资源消耗大,启动慢

容器

直接利用下层的硬件和操作系统

资源利用率高,运行速度快

隔离程度低, 安全性低

  1. 虚拟机是硬件级别的隔离,而容器化是进程间的隔离。
  2. 虚拟化需要模拟硬件占用部分内存,并且对宿主机操作的系统调用需要经过虚拟化软件的拦截与转换,造成资源的开销。而容器就是一个普通的进程,基本无额外的计算资源的开销。
  3. 在Linux内核中有部分的资源和对象无法namespace化,如时间。
  4. 因为容器是共享宿主机内核,所以对外暴露的供给面非常的大。

什么是容器化应用?

镜像,就是将容器的初始化环境固化下来,将运行进程所需要的文件系统、依赖库、环境变量、启动参数等打包整合到一起,保存成一个静态的文件。

容器化环境可以通过镜像快速重建容器,应用程序看到的就是一致的运行环境。

容器化应用,也就是应用程序不直接与操作系统去打交道,而是将应用程序打包成镜像,再交给容器环境去运行

镜像与容器的关系还可以用"序列化"和"反序列化"来理解,镜像就是序列化到磁盘的数据,而容器是反序列化后内存中的对象。

k8s之docker容器_kubernetes_02

k8s之docker容器_docker_03

常用镜像操作

命令

作用

docker pull

从远端仓库拉取镜像

docker images

列出当前本地已有镜像

docker rmi

删除不再使用的镜像

常用容器操作

命令

作用

例子

docker run

使用镜像启动容器

docker ps

列出正在运行的容器

docker exec

在容器内执行另一个程序

docker stop

停止容器

docker start

将停止的容器再次启动

docker rm

删除容器

docker export

将容器内的文件系统导出

docker export -o rootfs.tar 容器ID

容器被停止后,docker ps命令就看不到该容器了,需要使用docker ps -a来查看所有容器,包括已经停止的容器。

可能会导致非常多已经停止的容器占用系统资源,所以建议docker run时添加--rm参数,在容器运行完毕时自动清除

docker exec是如何进入到容器中的?

该命令会创建一个新的进程加入到容器的namepsace中。

/proc/{进程ID}/ns/下的虚拟文件会链接到真实的Namespace文件上。通过查看exec创建的进程ns文件可以看出和容器的Namespace文件一致

[root@k8s-master proc]# ll /proc/288948/ns/pid
lrwxrwxrwx 1 root root 0 Jul  8 11:27 /proc/288948/ns/pid -> 'pid:[4026532247]'

[root@k8s-master proc]# ll /proc/289220/ns/pid
lrwxrwxrwx 1 root root 0 Jul  8 11:27 /proc/289220/ns/pid -> 'pid:[4026532247]'

docker run和docker exec的区别是什么?

run是将镜像运行成容器并执行命令,该命令为1号进程。

exec是在容器中执行一个命令,该命令是另一个进程,加入到了容器的namespace中。

容器镜像

镜像内部机制

容器镜像内部是由许多的镜像层(Layer)组成的,每层都是只读不可修改的一组文件,相同的层可以在镜像中共享,然后多个层像搭积木叠加起来,使用**联合文件系统(UnionFS)**将它们合并起来,最终形成容器看到的文件系统。

镜像中的层级是只读层,而容器所在的层级是可读写层。

k8s之docker容器_k8s_04

镜像的分层信息可以通过命令docker inspect 镜像名称获取,其中RootFs是对应的信息

>>> docker inspect b3log/siyuan

.....
"RootFS": {
            "Type": "layers",
            "Layers": [
                "sha256:24302eb7d9085da80f016e7e4ae55417e412fb7e0a8021e95e3b60c67cde557d",
                "sha256:e7356c89d8c31fc628769b331f73d6e036e1d5900d2d2a3990c89ef91bce707a",
                "sha256:90358380b9ea63cfb8832ae627455faf85596e822ff8abe9e1d7c8bbd93804ad",
                "sha256:c6d8ffacc07d179562cd70114402e549d9fce92b12a019d3f4003eb94944d089"
            ]
        }
....

好处是,如果多个镜像使用了相同的层,可以直接共享,减少磁盘空间的占用。比如nginx镜像和Tomcat镜像都是用了基础镜像centos,那么该基础镜像可以共享。

k8s之docker容器_容器_05

OverlayFS

镜像层和容器是如何合并的呢?

k8s之docker容器_k8s_06

lowerdir是镜像层,upperdir是容器层,如果双方有相同文件则展示容器层的文件。

在容器写文件时,会先从镜像层拷贝一份文件到容器层,然后再写入,使用的是**写时复制(copy on write)**策略

例子

overlay2
├── lowerdirA
│   ├── a     内容:AA
│   └── b     内容:AA
├── lowerdirB
│   └── a     内容: BB
├── merge
├── upper
└── work

执行以下命令使用overlay进行合并层

mount -t overlay overlay -o lowerdir=lowerdirA:lowerdirB,upperdir=upper,workdir=work merge

lowerdir为镜像层,upperdir为容器层,merge目录为最终展示层。

可以看到merge目录中的a文件内容lowerdirA镜像层的内容

[root@iZwz93q4afq8ck02cesqh4Z k8s_learn]# cat merge/a 
AA

当我们修改megre目录中的a文件时,可以看到upperdir目录的会生成a文件并且内容修改后的内容

[root@iZwz93q4afq8ck02cesqh4Z k8s_learn]# ls upper/
[root@iZwz93q4afq8ck02cesqh4Z k8s_learn]# echo upper > merge/a
[root@iZwz93q4afq8ck02cesqh4Z k8s_learn]# ls upper/
a
[root@iZwz93q4afq8ck02cesqh4Z k8s_learn]# cat upper/a
upper
[root@iZwz93q4afq8ck02cesqh4Z k8s_learn]# cat merge/a
upper

当删除文件merge/a时,会出现什么情况呢?

[root@iZwz93q4afq8ck02cesqh4Z k8s_learn]# rm merge/a
rm: remove regular file ‘merge/a’? y
[root@iZwz93q4afq8ck02cesqh4Z k8s_learn]# ll lowerdirA/
total 8
-rw-r--r-- 1 root root 3 Jul 12 19:11 a
-rw-r--r-- 1 root root 3 Jul 12 19:10 b
[root@iZwz93q4afq8ck02cesqh4Z k8s_learn]# ll upper/
total 0
c--------- 1 root root 0, 0 Jul 12 19:31 a
[root@iZwz93q4afq8ck02cesqh4Z k8s_learn]#

可以看出镜像层lowerdirA的文件a是不变的,而在容器层upper中的a文件类型变成了c,该文件类型,最终在展示层看不到该文件了。

可以使用命令docker inspect来查看layer的路径

>>> docker inspect xxx

....
"GraphDriver": {
            "Data": {
                "LowerDir": "/var/lib/docker/overlay2/641e486c54d15d2a8d807fd8964f4a4b8687cbcf95c176cd9a46553b1e80341d/diff:/var/lib/docker/overlay2/ed9ad4fb9d0f9bf3aea553c634e54fef89448cf43c5b662468d79f01cf41d0c3/diff:/var/lib/docker/overlay2/9db169e1ad2165f688e652ef06dfe9a3e465c31299f3c357a37a6919747efbc8/diff",
                "MergedDir": "/var/lib/docker/overlay2/fa3166e545a2d1811dbeecb6f1fdda96b9f97b3cd629f32a8ea378aa79b1c780/merged",
                "UpperDir": "/var/lib/docker/overlay2/fa3166e545a2d1811dbeecb6f1fdda96b9f97b3cd629f32a8ea378aa79b1c780/diff",
                "WorkDir": "/var/lib/docker/overlay2/fa3166e545a2d1811dbeecb6f1fdda96b9f97b3cd629f32a8ea378aa79b1c780/work"
            },
            "Name": "overlay2"
        },
....

Dockerfile

Dockerfile是一个用来创建镜像的文本文件,该文件中的每一条命令都会成生成一个layer。

例子:

最简单的Dockerfile的例子

FROM busybox                  # 选择基础镜像
CMD echo "hello world"        # 启动容器时默认运行的命令

FROM指令是构建使用的基础镜像

CMD指令是用于启动容器时默认运行的命令

使用docker build 即可执行创建镜像

docker build -f Dockerfile .

Sending build context to Docker daemon   7.68kB
Step 1/2 : FROM busybox
 ---> d38589532d97
Step 2/2 : CMD echo "hello world"
 ---> Running in c5a762edd1c8
Removing intermediate container c5a762edd1c8
 ---> b61882f42db7
Successfully built b61882f42db7

容器与外部的交互

如何拷贝宿主机的文件到容器内

可以使用docker cp命令将宿主机的文件拷贝到容器中。

docker cp a.txt 062:/tmp

其中的062为容器ID,如果想将容器中的文件拷贝到宿主机中,反过来即可。

docker cp 062:/tmp/a.txt /tmp

注意,这里的拷贝是临时的,拷贝进容器中的文件只存在于容器中,不存在与镜像中,如果想要将文件拷贝到镜像中,在写Dockerfile时使用copy命令拷贝即可。

宿主机与容器共享文件夹

在使用镜像运行容器时,使用参数-v可以将宿主机中的文件夹映射到容器中,双方修改该文件夹中的内容,都可以及时看到。

docker run -d --rm -v /tmp:/tmp redis

如何实现网络互通?

docker提供三种网络模式:

  • null,无网络
  • host,直接使用宿主机网络,在创建容器时,使用–net=host参数。

其实就是创建新的namespace,而是直接加入到宿主机的namesapce

docker run -d --rm --net=host nginx:alpine

  • bridge,桥接模式,由软件虚拟网卡与网桥,容器和宿主机都接入该网桥,即可正常发送数据包。可以使用参数--net=bridge创建容器,但这个是默认参数。

docker run -d --rm nginx:alpine

网络模式

优点

缺点

host

因为是直接使用宿主机的网络,效率更高

运行太多的容器,会导致端口发生冲突

bridge

因为有了网桥可以设置更多的策略,比如流量控制等

需要软件模拟虚拟网卡与网桥,效率更低

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0qGP1ISa-1660568353845)(https://gcore.jsdelivr.net/gh/tenqaz/BLOG-CDN@main/20220712213036.png)]

关于k8s与docker的关系

在2014年的时候,Docker如日中天,那么k8s自然选择基于docker上运行。

在2016年k8s加入了CNCF,一个开源的云原生计算基金会。

并且引入了一个接口标准:CRI,Container Runtime Interface。也就是规定kubelet该如何调用Container Runtime去管理容器和镜像,但这是一套全新的接口,和之前的Docker完全不兼容。目的很明显,不想绑定Docker,可以随时将Docker踢掉。

因为docker已经非常成熟,各大厂商不可能将Docker全部替换。所以k8s在kubelet和Docker中间加一个"适配器",把Docker的接口转换成符合CRI标准的接口。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ccaTpESe-1660568353858)(https://gcore.jsdelivr.net/gh/tenqaz/BLOG-CDN@main/20220717211119.png)]

什么是containerd?

不过 Docker 也没有“坐以待毙”,而是采取了“断臂求生”的策略,推动自身的重构,把原本单体架构的 Docker Engine 拆分成了多个模块,其中的 Docker daemon 部分就捐献给了 CNCF,形成了 containerd。

containerd 作为 CNCF 的托管项目,自然是要符合 CRI 标准的。但 Docker 出于自己诸多原因的考虑,它只是在 Docker Engine 里调用了 containerd,外部的接口仍然保持不变,也就是说还不与 CRI 兼容。

由于 Docker 的“固执己见”,这时 Kubernetes 里就出现了两种调用链:第一种是用 CRI 接口调用 dockershim,然后 dockershim 调用 Docker,Docker 再走 containerd 去操作容器。第二种是用 CRI 接口直接调用 containerd 去操作容器。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5o4JjlVu-1660568353859)(https://gcore.jsdelivr.net/gh/tenqaz/BLOG-CDN@main/20220717212252.png)]

显而易见,使用第二种省去了dockershim和Docker Engine两个环节,损耗更少,性能也提升了。

正式"弃用Docker"

在2020年K8s弃用Docker支持,但该弃用支持弃用了"dockershim"的这个组件,也就是把dockershim移出kubelete,只是绕过Docker,直接调用了Docker内部的containerd而已。

并且对docker也无影响,因为docker内部也是使用开源的containerd。

k8s之docker容器_docker_07

唯一影响的是,k8s是直接操作containerd操作容器,那么它和docker是独立的工作环境,彼此都不能访问对方的容器和镜像,也就是docker ps看不到k8s运行的容器。改用crictl命令。

Docker 重构自身,分离出 containerd,这是否算是一种“自掘坟墓”的行为呢?如果没有 containerd,那现在的情形会是怎么样的呢?

Docker 是一个完整的软件产品线,不止是 containerd,它还包括了镜像构建、分发、测试等许多服务,甚至在 Docker Desktop 里还内置了 Kubernetes。

docker分离containerd是一个很聪明的举动!与其将来被人分离或者抛弃不用,不如我主动革新,把Kubernates绑在我的战车上,这样cri的第一选择仍然是docker的自己人。
一时的退让是为了更好的将来。

欢迎关注,互相学习,共同进步~

我的个人博客

我的微信公众号:编程黑洞


举报

相关推荐

0 条评论