容器探路-Docker镜像和runtime
一、什么是容器容器:是一种轻量级、可移植、自包含的软件打包技术,使应用程序可以在几乎任何地方以相同的方式运行。开发人员在自己笔记本上创建并测试好的容器,无需任何修改就能够在生产系统的虚拟机、物理服务器或公有云主机上运行。组成:1、应用程序本身,2、依赖:比如应用程序需要的库或其他软件二、为什么使用容器容器和虚机都是为应用提供封装和隔离,但是容器使软件具备了超强的可移植能力。虚机:每...
一、什么是容器
容器:是一种轻量级、可移植、自包含的软件打包技术,使应用程序可以在几乎任何地方以相同的方式运行。开发人员在自己笔记本上创建并测试好的容器,无需任何修改就能够在生产系统的虚拟机、物理服务器或公有云主机上运行。
组成:1、应用程序本身,2、依赖:比如应用程序需要的库或其他软件
二、为什么使用容器
容器和虚机都是为应用提供封装和隔离,但是容器使软件具备了超强的可移植能力。虚机:每一个虚机都需要安装一个操作系统,每个操作系统都有kernel,每一个kernel都依赖硬件,而docker仅依赖于库(目前kernel几乎都是X86指令架构),所以虚机内核和host内核是一样的(uname -r)
三、怎么学习和使用docker
1、容器核心架构-组件:Docker 采用的是 Client/Server 架构
a、Docker 客户端 - Client
b、Docker 服务器 - Docker daemon
c、Docker 镜像 - Image
d、Registry
e、Docker 容器 - Container
2、镜像
一个image由manifest、image index (可选)、filesystem layers和configuration四部分组成。
先来看看构成image的四部分的关系图:
Filesystem Layer包含了文件系统的信息,即该image包含了哪些文件/目录,以及它们的属性和数据。
image config就是一个json文件,这个json文件包含了对这个image的描述,包括CPU的架构、OS、config即运行容器的时候的默认参数、rootfs:指定了image所包含的filesystem layers,type的值必须是layers,diff_ids包含了layer的列表
manifest也是一个json文件,这个文件包含了对前面filesystem layers和image config的描述,包括镜像的层级即依赖的镜像和本次镜像-----都有可能多个层级
image index也是个json文件,是为了能够让镜像支持多个多个平台和多个tag
-----------docker pull的时候会去先下载image的manifests,根据manifests文件中config的sha256码,得到image config文件,遍历manifests里面的所有layer,根据其sha256码在本地找,如果找到对应的layer,则跳过,否则从服务器取相应layer的压缩包,然后拼出完整的image
3、上面是对镜像结构的描述,那怎创建镜像呢
A、通过docker commit创建镜像:
命令:docker commit {container_id} {image_name}
首先,创建基础镜像的容器,然后安装自己的库或者执行自己的命令(本次镜像层),最后通过命令创建镜像
B、通过Dockerfile构建文件创建镜像:
命令:docker build -t {image_name} .
通过 Dockerfile 构建镜像的过程:
1)、从 base 镜像运行一个容器。
2)、执行一条指令,对容器做修改。
3)、执行类似 docker commit 的操作,生成一个新的镜像层。
4)、Docker 再基于刚刚提交的镜像运行一个新容器。
5)、重复 2-4 步,直到 Dockerfile 中的所有指令执行完毕。
docker commit与Dockerfile 对比:docker commit无法通过docker history查看镜像是怎么来的,只能看到镜像大小增加了,等到镜像使用有问题后,无法追溯问题在哪?
4、Dockerfile文件:
Dockerfile是由一系列命令和参数构成的脚本,这些命令应用于基础镜像并最终创建一个新的镜像。
1)FROM(指定基础image)
构建指令,必须指定且需要在Dockerfile其他指令的前面。后续的指令都依赖于该指令指定的image。FROM指令指定的基础image可以是官方远程仓库中的,也可以位于本地仓库。
2)MAINTAINER(用来指定镜像创建者信息)
构建指令,用于将image的制作者相关的信息写入到image中。当我们对该image执行docker inspect命令时,输出中有相应的字段记录该信息。
3)COPY
将文件从 build context 复制到镜像。
COPY 支持两种形式:
1、COPY src dest
2、COPY ["src", "dest"]
4)ADD
与 COPY 类似,从 build context 复制文件到镜像。不同的是,如果 src 是归档文件(tar, zip, tgz, xz 等),文件会被自动解压到 dest。镜像。
5)ENV
设置环境变量,环境变量可被后面的指令使用。
6)EXPOSE
设置指令,该指令会将容器中的端口映射成宿主机器中的某个端口。当你需要访问容器的时候,可不是用容器的IP地址而是使用宿主机器的IP地址和映射后的端口
EXPOSE、-expose、-p对比
EXPOSE(dockerfile)和参数(-expose):暴露端口,不依赖宿主机------目前发现只是写了下配置文件,并没什么用,应该只是告诉镜像使用者,应该映射哪个端口
-p:发布端口,docker run -p ip::host_port:docker_port 其中,ip和host_port都可以省略,Docker会帮助选择一个宿主机端口
可以通过docker inspect docker_name查询,字段:"ExposedPorts",只使用EXPOSE(dockerfile)和参数(-expose),无法通过宿主机地址+端口进行访问的
7)RUN
在容器中运行指定的命令。
8)CMD
容器启动时运行指定的命令。
Dockerfile 中可以有多个 CMD 指令,但只有最后一个生效。CMD 可以被 docker run 之后的参数替换。
9)ENTRYPOINT
设置容器启动时运行的命令。
Dockerfile 中可以有多个 ENTRYPOINT 指令,但只有最后一个生效。CMD 或 docker run 之后的参数会被当做参数传递给 ENTRYPOINT。
run、cmd与ENTRYPOINT的区别:
1、RUN 执行命令并创建新的镜像层,RUN 经常用于安装软件包。
2、CMD 设置容器启动后默认执行的命令及其参数,但 CMD 能够被 docker run 后面跟的命令行参数替换。
3、ENTRYPOINT 配置容器启动时运行的命令。
5、上面讲述了镜像的创建,接下来看下容器的创建:
容器创建需要:1、内核 2、runtime 3、管理工具
runtime 是容器真正运行的地方。runtime 需要跟操作系统 kernel 紧密协作,为容器提供运行环境,lxc、runc 和 rkt 是目前主流的三种容器 runtime。MANO使用的是runc 通过docker info可以查看。
runc 的管理工具是 docker engine。docker engine 包含后台 deamon 和 cli 两个部分。我们通常提到Docker,一般就是指的 docker engine。
创建容器命令:
docker run -it --name {container_name} {image_name}
注意:正常启动容器时,容器会正常退出,是因为缺少死循环,因此需要在命令最后加上</bin/bash -c "while true;do sleep 1; done">,或者在run.sh(正常启动容器都不会把所有命令写在Dockerfile文件里,会通过ENTRYPOINT命令执行run.sh的)中
容器启动后,需要通过一些命令查看容器图:
6、docker内存限额:
docker run -it -m 200M --memory-swap=300M progrium/stress --vm 1 --vm-bytes 280M
1、-m 或 --memory:设置内存的使用限额,例如 100M, 2G。
2、--memory-swap:设置 内存+swap 的使用限额。
3、默认情况下,上面两组参数为 -1,即对容器内存和 swap 的使用没有限制
4、--vm 1:启动 1 个内存工作线程
5、--vm-bytes 280M:每个线程分配 280M 内存。
progrium/stress为镜像名称,如果--memory-swap不写,默认是memory的两倍
7、CPU限制:
Docker 可以通过 -c 或 --cpu-shares 设置容器使用 CPU 的权重。如果不指定,默认值为 1024。
与内存限额不同,通过 -c 设置的 cpu share 并不是 CPU 资源的绝对数量,而是一个相对的权重值。某个容器最终能分配到的 CPU 资源取决于它的 cpu share 占所有容器 cpu share 总和的比例。
8、block IO 权重:
Block IO 是另一种可以限制容器使用的资源。Block IO 指的是磁盘的读写,docker 可通过设置权重、限制 bps 和 iops 的方式控制容器读写磁盘的带宽
默认情况下,所有容器能平等地读写磁盘,可以通过设置 --blkio-weight 参数来改变容器 block IO 的优先级。
--blkio-weight 与 --cpu-shares 类似,设置的是相对权重值,默认为 500。在下面的例子中,container_A 读写磁盘的带宽是 container_B 的两倍。
docker run -it --name container_A --blkio-weight 600 ubuntu
docker run -it --device-write-bps /dev/mapper/vg_data-lv_home:200MB docker1
限制 bps 和 iops
bps 是 byte per second,每秒读写的数据量。
iops 是 io per second,每秒 IO 的次数。
可通过以下参数控制容器的 bps 和 iops:
--device-read-bps,限制读某个设备的 bps。
--device-write-bps,限制写某个设备的 bps。
--device-read-iops,限制读某个设备的 iops。
--device-write-iops,限制写某个设备的 iops。
6、7和8总结:
cgroup 和 namespace 是最重要的两种技术。cgroup 实现资源限额, namespace 实现资源隔离。
cgroup 全称 Control Group。Linux 操作系统通过 cgroup 可以设置进程使用 CPU、内存 和 IO 资源的限额。相信你已经猜到了:前面我们看到的--cpu-shares、-m、--device-write-bps 实际上就是在配置 cgroup。
对应的资源配置在/sys/fs/cgroup/{res_name}/docker/{dockerid}下
9、docker网络:
网络应该是docker中比较重要和关键的资源,接下来分享些docker原生的网络、如何自定义网络和容器网络通信(包括容器之间和容器与外界)
1)、容器提供的原生的网络:
Docker 安装时会自动在 host 上创建三个网络:bridge、host和none。
可以通过docker network ls 查看容器目前提供的网络
A、none
none 网络就是什么都没有的网络。挂在这个网络下的容器除了 lo,没有其他任何网卡。容器创建时,可以通过 --network=none 指定使用 none 网络。
docker run -it --network=none hdi
B、host 网络
连接到 host 网络的容器共享 Docker host 的网络栈,容器的网络配置与 host 完全一样。可以通过 --network=host 指定使用 host 网络。
直接使用 Docker host 的网络最大的好处就是性能,如果容器对网络传输效率有较高要求,则可以选择 host 网络。当然不便之处就是牺牲一些灵活性,比如要考虑端口冲突问题,Docker host 上已经使用的端口就不能再用了。
docker run -it --network=host hdi
C、bridge
Docker 安装时会创建一个 命名为 docker0 的 linux bridge。如果不指定--network,创建的容器默认都会挂到 docker0 上。
可以通过brctl show显示出来
一个新的网络接口 vethd7b76f5 被挂到了 docker0 上,vethd7b76f5就是新创建容器的虚拟网卡。
可以进入容器看下网络配置:命令:ip a
容器有一个网卡 eth0@if52。大家可能会问了,为什么不是vethd7b76f5 呢?
实际上 eth0@if52 和 vethd7b76f5 是一对 veth pair。veth pair 是一种成对出现的特殊网络设备,可以把它们想象成由一根虚拟网线连接起来的一对网卡,网卡的一头(eth0@if52)在容器中,另一头(vethd7b76f5)挂在网桥 docker0 上,其效果就是将eth0@if52 也挂在了 docker0 上。
由命名可见该虚拟网卡序号为51关联52,而52网卡在哪呢?其实就是vethd7b76f5,可以在host下看到:命令:ip a
而网卡序号正式挂在bridge0上的vethd7b76f5。
我们还看到 eth0@if52 已经配置了 IP 172.17.0.6,为什么是这个网段呢?让我们通过 docker network inspect bridge 看一下 bridge 网络的配置信息:
原来 bridge 网络配置的 subnet 就是 172.17.0.0/16,并且网关是 172.17.0.1。这个网关在哪儿呢?大概你已经猜出来了,就是 docker0。
D、自定义网络:
除了 none, host, bridge 这三个自动创建的网络,用户也可以根据业务需要创建 user-defined 网络
Docker 提供三种 user-defined 网络驱动:bridge, overlay 和 macvlan。overlay 和 macvlan 用于创建跨主机的网络.
docker network create --driver bridge my_net
上述命令可以类似于前面docker0(默认bridge)的网络,可以通过subnet和gateway参数为网络提供子网和网关
docker network create --driver bridge --subnet 172.19.0.0/16 --gateway 172.19.0.1 my_net2
可以通过docker network inspect my_net2查看具体信息:
可以为容器指定静态的网络:只有使用 --subnet 创建的网络才能指定静态 IP。
docker run -it --network=my_net2 --ip 172.19.0.16 image_name
docker network connect my_net container_id
创建两个自定义网络my_net与my_net2,启动两个容器,Docker1的网卡eth0与eth1分别挂在在docker0和my_net上,Docker2的网卡eth0与eth1分别挂在在my_net和my_net2上,很明显docker1能够与docker2通讯,因为两个容器在同一个网络上
不同网络下的docker不能够直接通讯,ping不通
iptables-save命令可以看到如下:
-A DOCKER-ISOLATION -i br-5d863e9f78b6 -o docker0 -j DROP
-A DOCKER-ISOLATION -i docker0 -o br-5d863e9f78b6 -j DROP
原因:iptables DROP 掉了网桥 docker0 与 br-5d863e9f78b6 之间双向的流量
可以将上述容器httpd添加my_net2网络:docker network connect my_net2 docker_id
可以通过删除路由规则让两个容器之间可以通讯
2)网络通信的三种方式: IP,Docker DNS Server 或 joined
A、IP:
两个容器要能通信,必须要有属于同一个网络的网卡。
B、Docker DNS Server
使用 docker DNS 有个限制:只能在 user-defined 网络中使用。也就是说,默认的 bridge 网络是无法使用 DNS 的
C、joined
joined 容器非常特别,它可以使两个或多个容器共享一个网络栈,共享网卡和配置信息,joined 容器之间可以通过 127.0.0.1 直接通信
3)网络交互
A、容访问外部网络:
如果host是能够访问外部网络的,那么容器默认就是能够访问外网的。
可以在容器内PING外网:
可以查看docker0网卡查看数据是怎么转发的:
是通过docker上eth0-172.17.0.3发往10.43.35.96的,那么问题来了,内部的网络172.17.0.3怎么能够和10.43.35.96通讯的?
其实,当数据到达docker0时,docker0会将数据转交给NAT,将地址转换成eth0的地址
Iptables -t nat -S
可以显示NAT表:-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
其含义是:来自 172.17.0.0/16 网段的包,目标地址是外网(! -o docker0),就把它交给 MASQUERADE 处理。而 MASQUERADE 的处理方式是将包的源地址替换成 host 的地址发送出去,即做了一次网络地址转换(NAT)
B、外网访问容器:
外部网络如何访问到容器?
其实,是通过端口映射
启动容器是通过 -p 参数设置host与docker的映射关系:docker run --name hdd -p 22324:2324 image_name
这样host的 22324端口就和容器hdd的2324端口有了映射关系,只要是发往22324的端口的数据,就会转发给hdd
每一个映射的端口,host 都会启动一个 docker-proxy 进程来处理访问容器的流量
针对网络与外部的交互,整理了下面这幅图:
10、存储:
Docker 为容器提供了两种存放数据的资源:storage driver 和 data volume
1)由 storage driver 管理的镜像层和容器层 (无状态-容器删除,数据丢失)
storage driver:容器的copy-on-write就是storage driver实现的,删除容器时,销毁
Docker 支持多种 storage driver,有 AUFS、Device Mapper、Btrfs、OverlayFS、VFS 和 ZFS,优先使用 Linux 发行版默认的 storage driver
可以通过 docker info查看
Storage Driver: overlay2
Docker Root Dir: /home/docker
2)Data Volume (有状态-容器删除数据保存)
data volume:保存永久性数据,包括bind mount 和 docker managed volume
A、bind mount
bind mount是将 host 上已存在的目录或文件 mount 到容器
docker run -d --name hdd -v /home/zp:/home/ngomm hdi -----目录
docker run -d --name hdd -v /home/zp/hello_world_uwsgi/Dockerfile:/home/test1/df hdi ------文件
B、docker managed volume
与 bind mount 在使用上的最大区别是不需要指定 mount 源,指明 mount point 就行了,可以通过docker inspect hdd 查看mount源地址
docker run -d --name hdd -v /home/test1/ngomm hdi
容器共享数据:
1.bind mount几个容器共享同一个目录
2.volume container 先创建一个容器:docker create --name vcdata -v /home/dir1:/home/dir1 -v /home/dir2 hdi,再创建其他容器使用该vc:docker run -itd --name hdd --volumes-from vcdata hdi
还可以将volume写到镜像dockerfile中
可以通过docker inspect vcdata 查看:
很明显指定host目录的volume使用指定目录,没有分配的会默认创建一个/home/docker/volumes/82442f84beb2824b908413912dd8266bd52c43ba615e245b07c655f4c2ea7e06/_data
相关命令:
1、docker images {image-id}
查询镜像信息
2、docker inspect {image-id}
查看镜像详情信息
3、docker history {image-id}
查看镜像的构建过程
更多推荐
所有评论(0)