Docker容器技术

docker

Docker 核心技术

Linux两个重要进程:

  1. /lib/systemd/systemd
    1. 负责执行内核的一部分初始化工作和系统配置,也会创建一些类似 getty 的注册进程
  2. kthreadd
    1. 负责管理和调度其他的内核进程。

Docker 的出现是因为目前的后端在开发和运维阶段确实需要一种虚拟化技术解决开发环境和生产环境环境一致的问题

Namespace

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

namespace解决了环境隔离

Linux Namespace的6大类型

No.1MNT Namespace提供磁盘挂载点和文件系统的隔离能力
No.2IPC Namespace提供进程间通信的隔离能力
No.3Net Namespace提供网络隔离能力
No.4UTS Namespace提供主机名隔离能力
No.5PID Namespace提供进程隔离能力
No.6User Namespace提供用户隔离能力
  • šclone() – 实现线程的系统调用,用来创建一个新的进程,并可以通过设计上述参数达到隔离。
  • šunshare() – 使某进程脱离某个namespace
    • 使用unshare隔离uts namespace
  • šsetns() – 把某进程加入到某个namespace

chroot、pivot_root

net namespace

Docker 默认采用 veth 的方式,将容器中的虚拟网卡同 host 上的一 个Docker 网桥 docker0 连接在一起。

PID namespace

允许在新的namespace创建一棵新的进程树,它可以有自己的PID为1的进程。在PID namespace的隔离下,子进程名字空间无法知道父进程名字空间的进程,如在Docker容器中无法看到宿主机的进程,而父进程名字空间可以看到子进程名字空间的所有进程。如图所示:

所有的 LXC 进程在 Docker 中的父进程为Docker进程,每个 LXC 进程具有不同的名字空间。同时由于允许嵌套,因此可以很方便的实现嵌套的 Docker 容器。

uts 命名空间

UTS(“UNIX Time-sharing System”) 命名空间允许每个容器拥有独立的 hostname 和 domain name, 使其在网络上可以被视作一个独立的节点而非 主机上的一个进程。

cgroups

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

http://dockone.io/article/2941

https://www.ibm.com/developerworks/cn/linux/1506_cgroup/index.html

cgroups实现了对资源的配额和度量,cpu、内存、磁盘、网络

它主要提供了如下功能:

  • Resource limitation: 限制资源使用,比如内存使用上限以及文件系统的缓存限制。
  • Prioritization: 优先级控制,比如:CPU利用和磁盘IO吞吐。
  • Accounting: 一些审计或一些统计,主要目的是为了计费。
  • Control: 挂起进程,恢复执行进程。

在实践中,系统管理员一般会利用CGroup做下面这些事(有点像为某个虚拟机分配资源似的):

  • 隔离一个进程集合(比如:nginx的所有进程),并限制他们所消费的资源,比如绑定CPU的核。
  • 为这组进程 分配其足够使用的内存
  • 为这组进程分配相应的网络带宽和磁盘存储限制
  • 限制访问某些设备(通过设置设备的白名单)

Linux把CGroup这个事实现成了一个file system

CGroup的术语
  • 任务(Tasks):就是系统的一个进程。
  • 控制组(Control Group):一组按照某种标准划分的进程,比如官方文档中的Professor和Student,或是WWW和System之类的,其表示了某进程组。Cgroups中的资源控制都是以控制组为单位实现。一个进程可以加入到某个控制组。而资源的限制是定义在这个组上,就像上面示例中我用的haoel一样。简单点说,cgroup的呈现就是一个目录带一系列的可配置文件。
  • 层级(Hierarchy):控制组可以组织成hierarchical的形式,既一颗控制组的树(目录结构)。控制组树上的子节点继承父结点的属性。简单点说,hierarchy就是在一个或多个子系统上的cgroups目录树。
  • 子系统(Subsystem):一个子系统就是一个资源控制器,比如CPU子系统就是控制CPU时间分配的一个控制器。子系统必须附加到一个层级上才能起作用,一个子系统附加到某个层级以后,这个层级上的所有控制族群都受到这个子系统的控制。Cgroup的子系统可以有很多,也在不断增加中。
CGroup的子系统
  • blkio — 这个子系统为块设备设定输入/输出限制,比如物理设备(磁盘,固态硬盘,USB 等等)。
  • cpu — 这个子系统使用调度程序提供对 CPU 的 cgroup 任务访问。
  • cpuacct — 这个子系统自动生成 cgroup 中任务所使用的 CPU 报告。
  • cpuset — 这个子系统为 cgroup 中的任务分配独立 CPU(在多核系统)和内存节点。
  • devices — 这个子系统可允许或者拒绝 cgroup 中的任务访问设备。
  • freezer — 这个子系统挂起或者恢复 cgroup 中的任务。
  • memory — 这个子系统设定 cgroup 中任务使用的内存限制,并自动生成内存资源使用报告。
  • net_cls — 这个子系统使用等级识别符(classid)标记网络数据包,可允许 Linux 流量控制程序(tc)识别从具体 cgroup 中生成的数据包。
  • net_prio — 这个子系统用来设计网络流量的优先级
  • hugetlb — 这个子系统主要针对于HugeTLB系统进行限制,这是一个大页文件系统。

每一个 CGroup 都是一组被相同的标准和参数限制的进程,不同的 CGroup 之间是有层级关系的,也就是说它们之间可以从父类继承一些用于限制资源使用的标准和参数。

13.png

Linux 使用文件系统来实现 CGroup,我们可以直接使用下面的命令查看当前的 CGroup 中有哪些子系统:

root@instance-64x155ng:/sys/fs/cgroup# ll
total 0
drwxr-xr-x 15 root root 380 Jul  2 18:08 ./
drwxr-xr-x 11 root root   0 Aug  2 11:32 ../
dr-xr-xr-x  5 root root   0 Aug  2 13:05 blkio/
lrwxrwxrwx  1 root root  11 Jul  2 18:08 cpu -> cpu,cpuacct/
dr-xr-xr-x  5 root root   0 Aug  2 13:05 cpu,cpuacct/
lrwxrwxrwx  1 root root  11 Jul  2 18:08 cpuacct -> cpu,cpuacct/
dr-xr-xr-x  3 root root   0 Aug  2 13:05 cpuset/
dr-xr-xr-x  5 root root   0 Aug  2 13:05 devices/
dr-xr-xr-x  3 root root   0 Aug  2 13:05 freezer/
dr-xr-xr-x  3 root root   0 Aug  2 13:05 hugetlb/
dr-xr-xr-x  5 root root   0 Aug  2 13:05 memory/
lrwxrwxrwx  1 root root  16 Jul  2 18:08 net_cls -> net_cls,net_prio/
dr-xr-xr-x  3 root root   0 Aug  2 13:05 net_cls,net_prio/
lrwxrwxrwx  1 root root  16 Jul  2 18:08 net_prio -> net_cls,net_prio/
dr-xr-xr-x  3 root root   0 Aug  2 13:05 perf_event/
dr-xr-xr-x  5 root root   0 Aug  2 13:05 pids/
dr-xr-xr-x  2 root root   0 Aug  2 13:05 rdma/
dr-xr-xr-x  6 root root   0 Aug  2 13:05 systemd/
dr-xr-xr-x  5 root root   0 Aug  2 13:05 unified/

如果我们想要创建一个新的 cgroup 只需要在想要分配或者限制资源的子系统下面创建一个新的文件夹,然后这个文件夹下就会自动出现很多的内容

root@instance-64x155ng:/sys/fs/cgroup/cpu/docker# ll
total 0
drwxr-xr-x 3 root root 0 Aug  2 16:42 ./
dr-xr-xr-x 5 root root 0 Aug  2 13:05 ../
drwxr-xr-x 2 root root 0 Aug  7 19:48 6e5753f159feedcd77ebe89f189a612732dfd094e434f0286978e4eae876ba07/
-rw-r--r-- 1 root root 0 Aug  7 19:48 cgroup.clone_children
-rw-r--r-- 1 root root 0 Aug  7 19:48 cgroup.procs
-rw-r--r-- 1 root root 0 Aug  7 19:48 cpu.cfs_period_us
-rw-r--r-- 1 root root 0 Aug  7 19:48 cpu.cfs_quota_us
-rw-r--r-- 1 root root 0 Aug  7 19:48 cpu.shares
-r--r--r-- 1 root root 0 Aug  7 19:48 cpu.stat
-r--r--r-- 1 root root 0 Aug  7 19:48 cpuacct.stat
-rw-r--r-- 1 root root 0 Aug  7 19:48 cpuacct.usage
-r--r--r-- 1 root root 0 Aug  7 19:48 cpuacct.usage_all
-r--r--r-- 1 root root 0 Aug  7 19:48 cpuacct.usage_percpu
-r--r--r-- 1 root root 0 Aug  7 19:48 cpuacct.usage_percpu_sys
-r--r--r-- 1 root root 0 Aug  7 19:48 cpuacct.usage_percpu_user
-r--r--r-- 1 root root 0 Aug  7 19:48 cpuacct.usage_sys
-r--r--r-- 1 root root 0 Aug  7 19:48 cpuacct.usage_user
-rw-r--r-- 1 root root 0 Aug  7 19:48 notify_on_release
-rw-r--r-- 1 root root 0 Aug  7 19:48 tasks

6e5753f159feedc其实就是我们运行的一个 Docker 容器

每一个 CGroup 下面都有一个 tasks 文件,其中存储着属于当前控制组的所有进程的 pid,作为负责 cpu 的子系统,cpu.cfs_quota_us 文件中的内容能够对 CPU 的使用作出限制,如果当前文件的内容为 50000,那么当前控制组中的全部进程的 CPU 占用率不能超过 50%。

AUFS

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

https://blog.csdn.net/xftony/article/details/80569777

1.png

它用到了一个重要的资源管理技术,叫写时复制。写时复制(copy-on-write),也叫隐式共享,是一种对可修改资源实现高效复制的资源管理技术。对于一个重复资源,若不修改,则无需立刻创建一个新的资源,该资源可以被共享使用。当发生修改的时候,才会创建新资源。这会大大减少对于未修改资源复制的消耗。

AUFS有所有Union FS的特性,把多个目录,合并成同一个目录,并可以为每个需要合并的目录指定相应的权限,实时的添加、删除、修改已经被mount好的目录。而且,他还能在多个可写的branch/dir间进行负载均衡。

典型的启动Linux运行需要两个FS: bootfs + rootfs:

├── fruits
│   ├── apple.txt
│   └── tomato.txt
└── vege
 ├── carrot.txt
 └── tomato.txt

mkdir mnt
mount -t aufs -o dirs=./fruits:./vegetables none ./mnt
    
├── fruits
│   ├── apple.txt
│   └── tomato.txt
├── mnt
│   ├── apple.txt
│   ├── carrot.txt
│   └── tomato.txt
└── vege
 ├── carrot.txt
 └── tomato.txt

现在修改一个文件夹另一个文件夹也会生效

关于 AUFS 的几个特点:

  • 默认情况下,只有第一层是可写的,其余层是只读的。
  • 增加文件:默认情况下,新增的文件都会被放在最上面的可写层中。
  • 删除文件:因为底下各层都是只读的,当需要删除这些层中的文件时,AUFS 使用 whiteout 机制,它的实现是通过在上层的可写的目录下建立对应的whiteout隐藏文件来实现的。
  • 修改文件:AUFS 利用其 CoW (copy-on-write)特性来修改只读层中的文件。AUFS 工作在文件层面,因此,只要有对只读层中的文件做修改,不管修改数据的量的多少,在第一次修改时,文件都会被拷贝到可写层然后再被修改。
  • 节省空间:AUFS 的 CoW 特性能够允许在多个容器之间共享分层,从而减少物理空间占用。
  • 查找文件:AUFS 的查找性能在层数非常多时会出现下降,层数越多,查找性能越低,因此,在制作 Docker 镜像时要注意层数不要太多。因为AUFS会把所有的分支mount起来,所以,在查找文件上是比较慢了。因为它要遍历所有的branch,是个O(n)的算法。所以,branch越多,查找文件的性能也就越慢。
  • 性能:AUFS 的 CoW 特性在写入大型文件时第一次会出现延迟。

AUFS下文件读操作
1、文件存在于container-layer:直接从container-layer进行读取;
2、文件不存在于container-layer:自container-layer下一层开始,逐层向下搜索,找到该文件,并从找到文件的那一层layer中读取文件;
3、当文件同时存在于container-layer和image-layer,读取container-layer中的文件。
简而言之,从container-layer开始,逐层向下搜索,找到该文件即读取,停止搜索。

AUFS下修改文件或目录
写操作
1、对container-layer中已存在的文件进行写操作:直接在该文件中进行操作(新文件直接在container-layer创建&修改);
2、对image-layers中已存在的文件进行写操作:将该文件完整复制到container-layer,然后在container-layer对这份copy进行写操作;
删除
1、删除container-layer中的文件/目录:直接删除;
2、删除image-layers中的文件:在container-layer中创建whiteoutfile,image-layer中的文件不会被删除,但是会因为whiteout而变得对container而言不可见;
3、删除image-layers中的目录:在container-layer中创建opaquefile,作用同whiteout;
重命名
1、container-layer文件/目录重命名:直接操作;
2、image-layer文件重命名:

3、image-layer目录重命名:在docker的AUFS中没有支持,会触发EXDEV。

Dockerfile中其他命令生成的layer为Read-only的,CMD或ENTRYPOINT生成的layer是R/W的

镜像(Image)就是一堆只读层(read-only layer)的统一视角

在 Docker 的术语里,一个只读层被称为镜像,一个镜像是永久不会变的。

由于 Docker 使用一个统一文件系统,Docker 进程认为整个文件系统是以读写方式挂载的。 但是所有的变更都发生顶层的可写层,而下层的原始的只读镜像文件并未变化。由于镜像不 可写,所以镜像是无状态的。

通常 Union FS 有两个用途, 一方面可以实现不借助 LVM、RAID 将多个 disk 挂到同一个目录下,另一个更常用的就是将一个只读的分支和一个可写的分支联合在一起,Live CD 正是基于此方法可以允许在镜像不变的基础上允许用户在其上进行一些写操作。 Docker 在 AUFS 上构建的容器也是利用了类似的原理。

Overlay

https://www.jianshu.com/p/959e8e3da4b2

overlayfs在linux主机上只有两层,一个目录在下层,用来保存镜像(docker),另外一个目录在上层,用来存储容器信息。在overlayfs中,底层的目录叫做lowerdir,顶层的目录称之为upperdir,对外提供统一的文件系统为merged,提供单个统一的视角。
当需要修改一个文件时,使用CoW将文件从只读的Lower复制到可写的Upper进行修改,结果也保存在Upper层。在Docker中,底下的只读层就是image,可写层就是Container。

可以看到镜像层和容器层可以保存相同的文件,容器层的文件会覆盖镜像层的文件
  • 在overlayfs中每个镜像层都会在/var/lib/docker/overlay有对应的目录,使用硬链接与底层数据进行关联。

OverlayFS支持页缓存共享,多个容器访问同一个文件能共享一个页缓存,以此提高内存使用

root@xftony:/var/lib/docker/overlay2# tree -L 2
.
├── 82f20fe6e3d9c9a33560b18bd0dcab53afeadca03bb7daea174e736d3ac4ee2d
│   ├── diff
│   ├── link
│   ├── lower
│   └── work
...
├── dbafc7976ac255df27aea16935b901745c1cf66487f142ec01b047998f139122
│   ├── diff
│   ├── link
│   ├── lower
│   └── work
└── l
    ├── CMR5LVSFJRXC7QA2ZF5MWLQB5Z -> ../82f20fe6e3d9c9a33560b18bd0dcab53afeadca03bb7daea174e736d3ac4ee2d/diff
    ...
    └── XR6454BGFPITLDXL4D7MPQEUHZ -> ../bce6bd75c4c2900eb51d3b103073a158ea17be3708461714f19eddb9e2bc9713/diff

l:目录下存储了多个软链接,使用短名指向其他各层;
其他一级目录:例如8fa01a5986e8a08c5等目录,为lowerdir,是一层层的镜像;
diff:包含该层镜像的具体内容,即upperdir和lowerdir,此处都为lowerdir;
link:记录该目录对应的短名;
lower:记录该目录的所有lowerdir,每一级间使用: 分隔;
work:该目录是OverlayFS功能需要的,会被如copy_up之类的操作使用;

XXX-init:这是顶层的lowerdir的父目录,因此也是只读的,它的目的是为了初始化container配置信息,譬如hostname等信息,XXX对应的是upperdir的父目录名;
XXX:这是upperdir的父目录,可读写,container的写操作都会发生在该层;
merged:该目录就是container的mount point,这就是暴露的lowerdir和upperdir的统一视图。任何对容器的改变也影响这个目录。

一个运行态容器(running container)被定义为一个可读写的统一文件系统加上隔离的进程空间和包含其中的进程

容器 = cgroup + namespace + rootfs + 容器引擎(用户态的工具)

容器的创建原理

pid = clone(func, stack, CLONE_NEWPID | CLONE_NEWNS | CLONE_NEWUSER | CLONE_NEWNET| CLONE_NEWIPC | CLONE_NEWUTS| ... , clone_arg);

通过clone系统调用,并传入各个Namespace对应的clone flag,创建了一个新的子进程,该进程拥有自己的Namespace。根据上面的代码可知,该进程拥有自己的pid、mount、user、net、ipc、uts namespace。

Image Layer Definition

6.png

8.png

docker create 命令为指定的镜像(image)添加了一个可读写层,构成了一个新的容器。注意,这个容器并没有运行。

Docker start命令为容器文件系统创建了一个进程隔离空间。注意,每一个容器只能够有一个进程隔离空间。

docker run命令类似于git pull命令。git pull命令就是git fetch 和 git merge两个命令的组合,同样的,docker run就是docker create和docker start两个命令的组合。

docker stop命令会向运行中的容器发送一个SIGTERM的信号,然后停止所有的进程。

docker rm命令会移除构成容器的可读写层。注意,这个命令只能对非运行态容器执行。

docker rmi 命令会移除构成镜像的一个只读层。你只能够使用docker rmi来移除最顶层(top level layer)(也可以说是镜像),你也可以使用-f参数来强制删除中间的只读层。

docker build命令非常有趣,它会反复的执行多个命令。我们从上图可以看到,build命令根据Dockerfile文件中的FROM指令获取到镜像,然后重复地1)run(create和start)、2)修改、3)commit。在循环中的每一步都会生成一个新的层,因此许多新的层会被创建。

docker 镜像是⼀一个只读的 docker 容器器模板

docker 镜像的⽂文件内容以及⼀一些运⾏行行 docker 容器器的配置⽂文件组成 了了 docker 容器器的静态⽂文件系统运⾏行行环境:rootfs

rootfs
rootfs 是 docker 容器器在启动时内部进程可⻅见的⽂文件系统,即 docker 容器器的根⽬目录。rootfs 通常包含 ⼀一个操作系统运⾏行行所需的⽂文件系统,例例如可能包含典型的类 Unix 操作系统中的⽬目录系统, 如/dev、/proc、/bin、/etc、/lib、/usr、/tmp 及运⾏行行 docker 容器器所需的配置⽂文件、⼯工具等。 在传统 的 Linux 操作系统内核启动时,⾸首先挂载⼀一个只读的 rootfs,当系统检测其完整性之后,再将其切换为 读写模式。⽽而在 docker 架构中,当 docker daemon 为 docker 容器器挂载 rootfs 时,沿⽤用了了 Linux 内 核启动时的做法,即将 rootfs 设为只读模式。在挂载完毕之后,利利⽤用联合挂载(union mount)技术在已 有的只读 rootfs 上再挂载⼀一个读写层。这样,可读写的层处于 docker 容器器⽂文件系统的最顶层,其下可 能联合挂载了了多个只读的层,只有在 docker 容器器运⾏行行过程中⽂文件系统发⽣生变化时,才会把变化的⽂文件 内容写到可读写层,并隐藏只读层中的旧版本⽂文件。

rootfs只是一个操作系统所包含的文件、配置和目录,并不包括操作系统的内核(bootfs包含了BootLoader和Kernel)

Docker容器的启动过程

 docker run -i -t ubuntu /bin/bash
  1. 检查本地是否存在指定的镜像,不存在则从公有仓库下载。即docker pull
  2. 使用镜像创建并启动容器。即docker container create
    1. namespace, cgroups, aufs, linux bridge
  3. 分配一个文件系统,并在只读的镜像层外面挂载一层可读可写层
  4. 从宿主主机配置的网桥接口中桥接一个虚拟接口道容器中去。从地址池分配一个ip地址给容器
  5. start container. 执行用户指定的应用程序

Docker 使用存储驱动来管理镜像每层内容及可读写的容器层,存储驱动有 DeviceMapper、AUFS、Overlay、Overlay2、Btrfs、ZFS 等,不同的存储驱动实现方式有差异,镜像组织形式可能也稍有不同,但都采用栈式存储,并采用 Copy-on-Write(CoW) 策略。且存储驱动采用热插拔架构,可动态调整。

  1. docker create
    1. 拉取镜像
    2. 创建容器对象
      1. 初始化一个容器的结构体newContainer,包含基本的信息,比如容器名称、配置文件结构体、对应的镜像ID等
    3. 设置读写层
    4. 配置网络
  2. docker start
    1. 创建容器实例
    2. 启动容器
      1. 挂载读写层
      2. 初始化网络

Docker的4种网络模式

  • host模式,使用–net=host指定
  • container模式,使用–net=container:NAME_or_ID指定,与容器共享
  • none模式,使用–net=none指定
  • bridge模式,使用–net=bridge指定,默认设置

bridge模式

默认的网络设置,此模式会为每一个容器分配Network Namespace、设置IP等,并将一个主机上的Docker容器连接到一个虚拟网桥上。

bridge模式的拓扑

当Docker server启动时,会在主机上创建一个名为docker0的虚拟网桥,此主机上启动的Docker容器会连接到这个虚拟网桥上。虚拟网桥的工作方式和物理交换机类似,这样主机上的所有容器就通过交换机连在了一个二层网络中。接下来就要为容器分配IP了,Docker会从RFC1918所定义的私有IP网段中,选择一个和宿主机不同的IP地址和子网分配给docker0,连接到docker0的容器就从这个子网中选择一个未占用的IP使用。如一般Docker会使用172.17.0.0/16这个网段,并将172.17.42.1/16分配给docker0网桥。单机环境下的网络拓扑如下,主机地址为10.10.101.105/24。

7.png
Docker完成以上网络配置的过程大致是这样的:

  1. 在主机上创建一对虚拟网卡veth pair设备。veth设备总是成对出现的,它们组成了一个数据的通道,数据从一个设备进入,就会从另一个设备出来。因此,veth设备常用来连接两个网络设备。
  2. Docker将veth pair设备的一端放在新创建的容器中,并命名为eth0。另一端放在主机中,以veth65f9这样类似的名字命名,并将这个网络设备加入到docker0网桥中,可以通过brctl show命令查看。

4.2 bridge模式下容器的通信

在bridge模式下,连在同一网桥上的容器可以相互通信(若出于安全考虑,也可以禁止它们之间通信,方法是在DOCKER_OPTS变量中设置–icc=false,这样只有使用–link才能使两个容器通信)。

容器也可以与外部通信,我们看一下主机上的Iptable规则,可以看到这么一条

-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE

这条规则会将源地址为172.17.0.0/16的包(也就是从Docker容器产生的包),并且不是从docker0网卡发出的,进行源地址转换,转换成主机网卡的地址。

那么,外面的机器是如何访问Docker容器的服务呢?我们首先用下面命令创建一个含有web应用的容器,将容器的80端口映射到主机的80端口。

docker run -d --name web -p 80:80 fmzhen/simpleweb

然后查看Iptable规则的变化,发现多了这样一条规则:

-A DOCKER ! -i docker0 -p tcp -m tcp --dport 80 -j DNAT --to-destination 172.17.0.5:80

此条规则就是对主机eth0收到的目的端口为80的tcp流量进行DNAT转换,将流量发往172.17.0.5:80,也就是我们上面创建的Docker容器。所以,外界只需访问10.10.101.105:80就可以访问到容器中得服务。

libnetwork

整个网络部分的功能都是通过 Docker 拆分出来的 libnetwork 实现的,它提供了一个连接不同容器的实现,同时也能够为应用给出一个能够提供一致的编程接口和网络层抽象的容器网络模型。

The goal of libnetwork is to deliver a robust Container Network Model that provides a consistent programming interface and the required network abstractions for applications.

libnetwork 中最重要的概念,容器网络模型由以下的几个主要组件组成,分别是 Sandbox、Endpoint 和 Network:

9.png

在容器网络模型中,每一个容器内部都包含一个 Sandbox,其中存储着当前容器的网络栈配置,包括容器的接口、路由表和 DNS 设置,Linux 使用网络命名空间实现这个 Sandbox,每一个 Sandbox 中都可能会有一个或多个 Endpoint,在 Linux 上就是一个虚拟的网卡 veth,Sandbox 通过 Endpoint 加入到对应的网络中,这里的网络可能就是我们在上面提到的 Linux 网桥或者 VLAN。

容器互联

新建网络

$ docker network create -d bridge my-net

-d 参数指定 Docker 网络类型,有 bridge overlay。其中 overlay 网络类型用于 Swarm mode

连接容器

docker run -it --rm --name busybox1 --network my-net busybox shd
docker run -it --rm --name busybox2 --network my-net busybox sh
可以ping通

常用命令

docker top

docker history <image_id>

docker cp

用于容器与主机之间的数据拷贝。

docker cp [OPTIONS] CONTAINER:SRC_PATH DEST_PATH|-
docker cp [OPTIONS] SRC_PATH|- CONTAINER:DEST_PATH

docker cp  96f7f14e99ab:/www /tmp/
docker cp /www/runoob 96f7f14e99ab:/www/

数据管理

数据卷

数据卷容器

命名的容器挂载数据卷,其他容器通过挂载这个(父容器)实现数据共享,挂载数据卷的容器,称之为数据卷容器

容器间传递共享(–volumes - from)

docker run -it --name 子容器别名 --volumes-from 父容器别名 需运行的容器id/容器名称

dockerfile

FROM centos
VOLUME ["/containerv1" ,"/containerv2"]
CMD echo "finished,==================success1"
CMD /bin/bash

创建父容器

[root@localhost ~]# docker run -it --name dc1 centos-volume
[root@ac38d9a91b90 /]# ls
anaconda-post.log  bin  containerv1  containerv2  dev  etc  home  lib  lib64  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var
[root@ac38d9a91b90 /]# cd containerv2
[root@ac38d9a91b90 containerv2]# ll
total 0
[root@ac38d9a91b90 containerv2]# touch dc01_add.txt
## ctrl+P ctrl+Q 退出容器

创建子容器1,并继承父容器,并不需要同源镜像

$ docker run -it --name dc02 --volumes-from dc01 openjdk:8
$ ls
anaconda-post.log  bin  containerv1  containerv2 
[root@33d5b88b7a7a /]# cd containerv2
[root@33d5b88b7a7a containerv2]# ls
dc01_add.txt
[root@33d5b88b7a7a containerv2]# touch dc02_add.txt
## ctrl+P ctrl+Q 退出容器

查看父容器是否共享数据

[root@ac38d9a91b90 containerv2]# ls
dc01_add.txt  dc02_add.txt
## ctrl+P ctrl+Q 退出容器

创建子容器2,并继承父容器

dc03继承dc01, dc02继承dc01, 三个容器的数据卷里面的数据是一样的。父子之间可进行共享数据。

DockerFile

Dockerfile涉及的内容包括执行代码或者文件、环境、变量、依赖包、运行时环境、动态链接库、操作系统发行版、服务进行、内核进程

保留字指令

  • EXPOSE:当前容器对外暴露的端口号
  • WORKDIR:指定在创建容器后,终端默认登录的进来的目录,一个落脚点
  • ARG:构建参数 ARG 所设置的构建环境的环境变量,在将来容器运行时是不会存在这些环境变量的。但是不要因此就使用 ARG 保存密码之类的信息,因为 docker history 还是可以看到所有值的。
  • ENV:用来构建镜像过程中设置环境变量

ENV MY_PATH /user/local

这个环境变量可以在后续的任何RUN指令中使用,如同在命令前指定了环境变量前缀一样;

也可以在其他指令中直接使用这些环境变量,比如,WORKDIR $MY_PATH

  • VOLUME:容器数据卷,用于数据保存和持久化工作

  • CMD:指定一个容器启动时要执行的命令,DockerFile中可以有多个CMD指令,但只有最后一个生效,CMD会被docker run 之后的参数替换 (/bin/bash)

  • ENTRYPOINT:指定一个容器启动时要执行的命令

  • ONBUILD:类似触发器,当构建一个被继承的DockerFile时运行命令,父镜像在被子镜像继承后父镜像的ONBUILD被触发

  • CMD命令设置容器启动后默认执行的命令及其参数

    • 一般用作默认命令或参数
  • ENTRYPOINT配置容器启动时的执行命令(不会被忽略,一定会被执行)

    • 一般用作强制命令,搭配CMD做默认参数
      • 如果docker run跟着参数,那么CMD参数将被替换
    • 可以在docker run 后添加entrypoint的参数

tomcat dockerfile

apache-tomcat-9.0.8.tar.gz
jdk-8u171-linux-x64.tar.gz
FROM centos
MAINTAINER johnson
ENV MAINPATH /usr/
#切换镜像目录,进入/usr目录
WORKDIR /usr
#在/usr/下创建tomcat目录,用来存放tomcat
# RUN mkdir tomcat

#将宿主机的jdk目录下的文件拷至镜像的/usr/jdk目录下
ADD jdk-8u201-linux-x64.tar.gz /usr/
#将宿主机的tomcat目录下的文件拷至镜像的/usr/tomcat目录下
ADD apache-tomcat-9.0.20.tar.gz /usr/

RUN mv jdk1.8.0_201 jdk
RUN mv apache-tomcat-9.0.20 tomcat

#设置环境变量
ENV JAVA_HOME=/usr/jdk
ENV JRE_HOME=$JAVA_HOME/jre
ENV CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar:$JRE_HOME/lib:$CLASSPATH
ENV CATALINA_HOME /usr/local/apache-tomcat-9.0.20
ENV CATALINA_BASE /usr/local/apache-tomcat-9.0.20
ENV PATH $PATH:$JAVA_HOME/bin:$CATALINA_HOME/lib:$CATALINA_HOME/bin

#公开端口
EXPOSE 8080
#设置启动命令
ENTRYPOINT ["/usr/tomcat/bin/catalina.sh","run"]
# ENTRYPOINT ["/usr/local/apache-tomcat-9.0.8/bin/startup.sh" ]
# CMD ["/usr/local/apache-tomcat-9.0.8/bin/catalina.sh","run"]
# CMD /usr/local/apache-tomcat-9.0.8/bin/startup.sh && tail -F /usr/local/apache-tomcat-9.0.8/bin/logs/catalina.out
docker run --name  seen-tomcat -p 8888:8080 -d seen-tomcat
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值