DOCKER概览

网友投稿 662 2022-05-29

docker服务端是个daemon进程,管理Docker容器并与客户端交互。服务端监听/var/run/docker.sock这个套接字,提供命令行交互和RESTful API两种方式。

Docker的daemon进程需要用root运行,docker用户组有root用户相同的权限,所以是种安全隐患。另外,默认通信方式是非认证的模式,所以也是不安全的,但是可以设置TLS认证。

镜像的概念不是Docker发明的,甚至可以参照理解windows系统的ghost盗版方式。

Docker镜像是基于Union文件系统的一种分层结构,可以由一步一步的构建出来。可以参照理解纯净版的ghost windows(基础镜像),和安装了office等软件的ghost windows(FROM基础镜像)

镜像也可以理解为容器的“源代码”,后面会稍微展开一些。

上面说了镜像是容器的源代码,那么Registry就可以理解为代码库。

Registry官方提供一个公共库,是收费的。企业也可以搭建自己的私有Registry。

容器就涉及Docker大名鼎鼎的集装箱概念了,容器就是集装箱,镜像是货物。容器不关心具体镜像是什么,容器会按照相同的方式去管理镜像,如创建、启动、关闭、重启、销毁。

容器可以包含一个或多个进程,但是当前的主流做法(微服务)是单容器单进程。

Docker并不是凭空冒出来的,依赖于原来OS的一些功能。但是Docker也希望反向作用于容器标准化,于是有了libcontainer作为容器管理的抽象,后续好真正实现跨平台。

Docker底层依赖linux内核的namespace、cgroups、Union File System(可选:Device Manager、AUFS、vfs、btrfs),主要用于资源的隔离和控制,如文件系统、进程、网络等。所以Docker需要较新的内核版本,推荐是3.8以上。

Docker采用写时复制(https://en.wikipedia.org/wiki/Copy-on-write)技术,通过分层的文件系统保证高效率和很少占用空间。

Docker可以日志化容器中的标准输入输出,STDOUT/STDERR/STDIN,也支持伪tty终端,提供一个交互shell,可以用来定位和调试。

Docker底层依赖的设计思路可以参考公司内李国柱的博客:

http://3ms.huawei.com/hi/blog/11100_1899017.html?h=h

细节可参照陈皓的系列博客:

http://coolshell.cn/articles/17010.html

http://coolshell.cn/articles/17029.html

http://coolshell.cn/articles/17049.html

http://coolshell.cn/articles/17061.html

http://coolshell.cn/articles/17200.html

Docker命令大概分四类,参考:

http://www.infoq.com/cn/articles/docker-command-line-quest

其中大部分和LINUX命令相同的名字的命令,基本也是相似的语义。

info命令用于查看docker基本信息,会返回所有容器的镜像、执行和存储驱动、基本配置。容器保存在/var/lib/docker/containers 。

inspect命令可以查看容器的最详细信息。

images命令查看所有的镜像。镜像保存在宿主机的/var/lib/docker目录。

run命令是运行容器,-i参数保证STDIN开启,-t命令启动伪tty,两个参数可以保证启动容器后的交互。run指定的镜像,会先找本地,找不到会找仓库,找到一次就缓存到本地,整个过程类似maven的管理。

--name会给容器指定一个名字,这个很有用,会被很多地方引用,所以必须是唯一的,如果有重名,必须先rm掉旧的容器。-d表示daemon,创建一个长期运行的容器。-- rm表示一次性运行的容器,运行完容器的进程会自动删除容器。-h会指定容器的主机名,不指定的话默认是容器ID。--volumes-from指定从某个容器挂载所有的卷。

attach可以重新附着一个run的容器会话。

logs命令查看容器日志,-f参数使用和linux的tail –f类似。-t为日志加时间戳。

DOCKER概览

build构建镜像、history可以查看这个构建的过程。

exec命令在容器内启动新的进程,-d可以设置为daemon进程。

Docker镜像就是文件系统一层一层叠加出来的。

最底层是一个引导文件系统bootfs,类似linux的引导文件系统。docker用户不会和引导文件系统有交互,因为在容器启动后,就会被移到内存中,引导文件系统会被unmount,以便留出更多内存供initrd(boot loader initialized RAM disk)磁盘镜像使用。

引导文件系统上层是rootfs,rootfs是基于内存的文件系统,所有操作都在内存中完成;也没有实际的存储设备,所以不需要设备驱动程序的参与。基于以上原因,linux在启动阶段使用rootfs文件系统,当磁盘驱动程序和磁盘文件系统成功加载后,linux系统会将系统根目录从rootfs切换到磁盘文件系统。

linux引导过程中,root文件系统先按只读方式加载,引导完成,包括校验完成,会被切换为读写模式。但是Docker中,root文件系统永远都是只读模式,Docker用union mount技术又会在root文件系统之上加载其他的文件系统,这些也是只读的。

union mount会一次同时加载多个文件系统,但是从外看,只有一个文件系统。union mount把各层文件叠加到一起,这个看起来的最终的文件系统会包含所有底层的文件和目录。这个外观型的文件系统,就是镜像。

所以,容易理解,镜像也是可以一层一层叠加的,下层的镜像就是上层的parent image,最底层的镜像是base image。

当容器启动时,Docker在镜像的最顶层加载一个读写文件系统,在Docker中运行的程序就是在这个读写层执行的。刚启动时,这个读写层是空的,发生变化时,变化的部分会体现在这一层,这就是所谓的写时复制。

实际上,容器就是这个读写层+下面的镜像层+配置。

镜像的版本管理貌似是参考了git的思路。

Docker Registry管理镜像,包含了镜像、层、和一些关于镜像的元数据。

Docker默认用的是官方的Registry,也就是Docker Hub,这个部分也是开源的,所以企业也可以搭建自己的私有Registry。Registry也可以运行在Docker中~

Docker Hub中有两种类型的仓库,user repository和top-level repository。用户仓库的镜像是Docker用户创建。顶级仓库是Docker内部人员管理,一般是由Docker合作厂家提供,如操作系统发行商。用户基于顶层的这些基础镜像来构建自己的镜像。

用户仓库的命名由用户名和仓库名两部分组成,如user/repo,顶层仓库只包含仓库名。

同一仓库中的镜像,可以通过加tag区分。相同镜像ID可以有多个TAG。

docker run命令从镜像启动一个容器时,如果镜像不在本地,docker默认会从Docker Hub下载镜像,如果没有显示指定tag,会默认下载latest标签的镜像。也可以通过docker pull命令,预先加载到本地。

docker search命令,查找所有Docker Hub上公共的可用镜像。docker push命令将镜像推送到docker hub。

Docker Hub还有个比较有意思的功能是“Trusted Build”,将Docker Hub关联GitHub上的Dockerfile文件,每次提交代码,都会触发一次构建,并创建新镜像。

https://docs.docker.com/engine/reference/builder/

这里边讲的“构建”,并不是说从零构建,这会复杂的多。一般说的构建镜像就是基于基础镜像去构建新镜像。从零构建可以参考.

https://docs.docker.com/engine/userguide/eng-image/baseimages/

构建镜像有两种方式:

1. 单步命令:docker commit

2. 批量方式:Dockerfile文件 + docker build命令

可以类比于java中的单个编译和ant脚本批量编译。commit命令一般不推荐使用,因为Dockerfile功能更强,也更灵活。

上面说Registry类似git仓库,其实commit命令其实类似git提交代码。下载镜像、基于它构建、构建镜像的过程,其实可以类比于下载代码,基于代码修改,提交代码的过程。

commit命令需要指定docker容器的ID(通过docker ps –l –q命令看刚创建的容器ID),一个目标仓库和镜像名。commit命令提交的只是差异部分,并不是全量提交,这也是分层的好处。

Dockerfile实际上是定义了一种DSL,提供了一种批量构建镜像的方式。参考

Dockerfile所在的目录就是构建环境,被成文build context。Docker会在构建镜像时将这个目录和目录中的文件和子目录上传到Docker守护进程。Docker守护进程可以直接访问构建所需的任何代码、文件或其他的数据。

Dockerfile由一系列的指令和参数组成,每条指令,如FROM,都是大写字母。指令后面要加一个参数。Dockerfile中的指令从上到下按顺序执行。

每条指令都会创建一个新的镜像,并自动提交。相当于基于父镜像,每执行一条指令,就docker commit一次,然后新构建的镜像作为下一条指令的父镜像。直到所有指令执行完。

官方给了几个dockerfile的实例,举一个如下:

EXPOSE指定对外暴漏的接口,在实际宿主机上,还要做端口映射。方法是使用docker run命令的-p参数,可以随机分配,或指定端口映射,设置可以指定某个特定的宿主机的映射方式。也可以使用-P参数,直接暴露所有EXPOSE指令指定的端口。

RUN是最基本的运行指令,会在当前镜像中运行指定的指令。每条RUN都会创建一个新的镜像层,如果成功,就会提交镜像,然后继续执行下一条。

FROM会引入基础镜像,所有命令基于此开始构建。

# 开头是注释。

docker build命令,会执行dockerfile,并返回最后一个成功的镜像。

build命令推荐指定镜像名和标签,没指定tag会默认打上latest标签。也可以打多个标签:

$ docker build -t shykes/myapp:1.0.2 -t shykes/myapp:latest .

build命令执行过程中,如上面所说,每一步都是独立的构建,每一步都会有一个镜像ID:

所以,在执行某一个指令失败时,会返回最后一个成功的镜像。可以在这个镜像的基础上,手工执行出错的指令,进行调试。而之前构建出来的镜像也会被作为缓存,再次构建不是从头开始,而是从失败的位置开始构建。如果需要关闭缓存,在build命令后加—no-cache参数。

构建的过程,也可以通过docker history命令回溯。

CMD命令指定容器启动时要运行的命令;对应的,上面说的RUN是镜像构建时要运行的命令。参数格式和RUN命令相同,都支持数组形式参数:

CMD ["executable","param1","param2"] (exec form, this is the preferred form)

CMD ["param1","param2"] (as default parameters to ENTRYPOINT)

CMD command param1 param2 (shell form)

CMD在一个dockerfile中只有一条,即使配置了多条,也只有最后一条生效。

要注意的是,启动命令可以被docker run命令指定的启动命令覆盖。

ENTRYPOINT和CMD非常类似,区别在于不能被覆盖。docker run命令指定的任何参数都会被当作参数再次船体给ENTRYPOINT指令中指定的命令。

ENV命令,顾名思义,设置构建镜像的环境变量。从构建的镜像启动容器,环境变量就会生效,且持久生效。对应的docker run命令-e指定的环境变量,只会在运行时有效。

容器启动的工作目录。CMD和ENTRYPOINT都会从这个目录下执行。可以设置多次,运行不同指令,如:

WORKDIR /a RUN xxx WORKDIR b CMD xxx run命令的-w可以覆盖工作目录

指定镜像以什么用户运行。run命令的-u选项可以覆盖

向容器添加卷。卷是docker设计的一种共享方式,可以绕过unionfs,在不同容器间持久化共享数据。可以用来存放调试代码、日志管理、数据共享等用途。

卷有几个特性:

1.卷在容器间共享和重用

2.对卷的修改是即时生效的

3.卷的修改不会对更新镜像产生影响

4.直到所有引用的容器都被删除,卷才会被删除(要注意删除容器,卷被误删)

5.卷这个参数不是必须的,容器可以选择不共享

可以通过数组参数,指定多个卷。

提交或创建镜像时,镜像中不包含卷。

ADD把构建目录下的文件和目录复制到镜像中。文件源支持正常路径和URL模式。目的路径如果不存在,会创建全路径。

ADD有个奇葩的隐藏功能,会根据地址参数末尾的字符来判断是文件还是目录,比如以/结尾,就认为是目录。

另一个奇葩的隐藏功能,如果将gzip等标准压缩格式作为源,ADD会自动解压。

COPY和ADD基本一样,只是不会自动解压。个人认为docker这种ADD/COPY,CMD/ENTRYPOINT的设计非常奇葩。这个命令创建的文件和目录的UID和GID都会设为0。拷贝的内容包含文件系统的元数据。

这是个钩子,当镜像作为父镜像构建时,钩子会被调用。

ONBUILD后面可以加任何构建指令,插入的指令可以认为是紧跟在FROM之后执行的。

可以结合这个命令和构建缓存的功能,制作构建模板。

docker支持两种网络驱动,bridge 和overlay ,默认是网桥模式。

如果不特意指定,守护进程管理所有容器的网络连接,都是通过docker0这个虚拟网桥:

ubuntu@ip-172-31-36-118:~$ ifconfig

docker0 Link encap:Ethernet HWaddr 02:42:47:bc:3a:eb

inet addr:172.17.0.1 Bcast:0.0.0.0 Mask:255.255.0.0

inet6 addr: fe80::42:47ff:febc:3aeb/64 Scope:Link

UP BROADCAST RUNNING MULTICAST MTU:9001 Metric:1

RX packets:17 errors:0 dropped:0 overruns:0 frame:0

TX packets:8 errors:0 dropped:0 overruns:0 carrier:0

collisions:0 txqueuelen:0

RX bytes:1100 (1.1 KB) TX bytes:648 (648.0 B)

docker0接口有符合RFC1918的私有IP地址,范围是172.16~172.30,上例中的网关地址是172.17.0.1,也是所有docker容器的网关地址。

docker创建一个容器就会创建一组互联的网络接口,一端是容器的eth0,一段是docker0,这样Docker相当于管理了一个虚拟子网。

默认容器和宿主机网络是不通的,除非指定打开端口。

docker容器重启,就会改变IP地址。这对需要互联的容器很不方便,所以docker提供了link功能。link可以把一个或多个docker容器连接起来,互相通信。

https://docs.docker.com/engine/userguide/networking/work-with-networks/#linking-containers-in-user-defined-networks

link需要引用容器的名字,所以上文也强调过名字的重要性。

docker run –link选项,可以创建两个容器间的父子连接,有两个参数,一个是要连接的容器名,另一个参数是连接后容器的别名。别名可以让使用者不关注底层容器的名字。

因为是父子连接,所以不是双向的,但是可以运行两个命令来达到双向的目的:

$ docker run --net=isolated_nw -itd --name=container4 --link container5:c5 busybox

01b5df970834b77a9eadbaff39051f237957bd35c4c56f11193e0594cfd5117c

$ docker run --net=isolated_nw -itd --name=container5 --link container4:c4 busybox

72eccf2208336f31e9e33ba327734125af00d1e1d2657878e2ee8154fbb23c7a

$ docker attach container4

/ # ping -w 4 c5

PING c5 (172.25.0.5): 56 data bytes

64 bytes from 172.25.0.5: seq=0 ttl=64 time=0.070 ms

64 bytes from 172.25.0.5: seq=1 ttl=64 time=0.080 ms

64 bytes from 172.25.0.5: seq=2 ttl=64 time=0.080 ms

64 bytes from 172.25.0.5: seq=3 ttl=64 time=0.097 ms

--- c5 ping statistics ---

4 packets transmitted, 4 packets received, 0% packet loss

round-trip min/avg/max = 0.070/0.081/0.097 ms

/ # ping -w 4 container5

PING container5 (172.25.0.5): 56 data bytes

64 bytes from 172.25.0.5: seq=0 ttl=64 time=0.070 ms

64 bytes from 172.25.0.5: seq=1 ttl=64 time=0.080 ms

64 bytes from 172.25.0.5: seq=2 ttl=64 time=0.080 ms

64 bytes from 172.25.0.5: seq=3 ttl=64 time=0.097 ms

--- container5 ping statistics ---

4 packets transmitted, 4 packets received, 0% packet loss

round-trip min/avg/max = 0.070/0.081/0.097 ms

$ docker attach container5

/ # ping -w 4 c4

PING c4 (172.25.0.4): 56 data bytes

64 bytes from 172.25.0.4: seq=0 ttl=64 time=0.065 ms

64 bytes from 172.25.0.4: seq=1 ttl=64 time=0.070 ms

64 bytes from 172.25.0.4: seq=2 ttl=64 time=0.067 ms

64 bytes from 172.25.0.4: seq=3 ttl=64 time=0.082 ms

--- c4 ping statistics ---

4 packets transmitted, 4 packets received, 0% packet loss

round-trip min/avg/max = 0.065/0.070/0.082 ms

/ # ping -w 4 container4

PING container4 (172.25.0.4): 56 data bytes

64 bytes from 172.25.0.4: seq=0 ttl=64 time=0.065 ms

64 bytes from 172.25.0.4: seq=1 ttl=64 time=0.070 ms

64 bytes from 172.25.0.4: seq=2 ttl=64 time=0.067 ms

64 bytes from 172.25.0.4: seq=3 ttl=64 time=0.082 ms

--- container4 ping statistics ---

4 packets transmitted, 4 packets received, 0% packet loss

round-trip min/avg/max = 0.065/0.070/0.082 ms

父容器可以直接访问自容器的任意公开端口,而且只有配置--link连接的容器才能访问。容器端口甚至不需要对宿主机开放。

连接只能运行在同个宿主机中,不能运行在不同宿主机。

配置link,会在父容器的dns配置文件,如/etc/hosts里写入IP,重启后,这个IP也会随着刷新。不过docker技术变化很快,这种底层的技术也可能变化很快。

Docker生态有三种API:Registry API、Docker Hub API、Docker Remote API,前两个是与仓库交互,最后一个是与docker daemon进程交互。

REST API支持证书认证。

基本接口和docker命令都能一一对应,如/images/json返回结果和docker images命令非常类似。还可以控制容器,如/containers/start

容器编排有两大类,一种是简单的把容器连起来,如Fig;一种是更强大的容器编排,包括服务发现,动态伸缩,调度等,如Consul(服务发现),kubernetes、mesos(集群调度和管理),swarm。

每个话题都很大,这里不展开。

描述问题一般有个约定,要给出背景信息:

docker info和docker version的输出

uname –a命令的输出

要描述你想要什么,希望它如何工作,尝试了什么。

提问前可以先搜索,参考提问的艺术。

转载请注明出处:华为云博客 https://portal.hwclouds.com/blogs

Docker

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:kafka系列之原理简介
下一篇:HERO联盟知识学堂第一期——数据库工程师进阶之路——化繁为简,从“新”开始——LV1(一)未完
相关文章