Docker
核心
容器、runtime
Java程序好比容器,jvm相当于runtime,容器在runtime中运行
docker默认runtime:runc,管理工具:docker engine包含deamon和cli两部分。通常提到的docker一般只docker engine
容器定义工具
- docker image:模版,runtime依据image创建容器
- dockerfile:包含若干命令的文本文件,通过这些命令创建docker image
Registry
- 统一存放image。
- docker Hub为docker为公众提供的Registry。
平台
编排引擎
- 基于容器的应用一般为微服务架构。不同的服务运行在各自的容器中,通过API对外提供服务。为了保证高可用,每个组件都可能运行多个相同的容器。这些容器组成集群,集群中容器会根据业务被动态创建、迁移、销毁。
- 为了处理这种动态可伸缩,引入容器编排引擎。
- 编排(orchestration)包括:容器管理、调度、集群定义和服务发现等。
- Kubernetes是Google领导开发的开源容器编排引擎。
支持技术
- 容器网络
- 服务发现
- 监控
- 数据管理
- 日志管理
- 安全性
What
容器是一种轻量级、可移植、自包含的软件打包技术,使应用程序可以在任何地方以相同方式运行。
容器由两部分组成:
- 应用程序本身
- 依赖:应用程序需要的库,与操作系统的其他进程隔离。
容器和虚拟机最大区别:
- 所有容器共享一个OS,虚拟机需要单独的OS
- 容器启动不需要启动整个系统,体积小、开销小,虚拟机需要。
Why
容器使软件具备可移植能力。
Docker将集装箱思想运用到软件打包上,为代码提供一个基于容器的标准化运输系统。
集装箱和容器的单词都是:Container。
容器是国内约定俗称的叫法,外国人思维可能Container只用到了集装箱的思想。
HOW
架构
核心组件
- Docker客户端:Client
- Docker服务器:Docker daemon
- Docker镜像:Image
- Registry
- Docker容器:Container
Docker采用Client/Server架构。客户端向服务器发送请求,服务器负责构建、运行和分发容器。C/S可以在同一台机器,C也可以通过stocket或Rest Api远程通信。
客户端
命令行界面,通过指令与服务器通信。
服务端
Docker daemon是服务器组件,以后台服务的方式运行。默认只响应本地客户端的请求,可配置远程客户端请求。
Docker镜像
可看成只读模版,通过它来创建Docker容器。
例:某个image可能包含Ubuntu操作系统、一个Apache HTTP Server以及用户开发的web应用。
多种生成方式:
- 从无到有创建
- 下载别人创建好的现成image
- 在现有image上创建新的image
Docker容器
是Docker image运行的实例。用户可以通过CLI(Docker)或者API启动、停止、移动或删除容器。
可以这么理解:image是软件生命周期的构建和打包阶段,而容器则是启动和运行阶段。
Registry
存放image的仓库,分为公有和私有。
docker pull:可以从Registry下载image
docker run:先下载image(如果本地没有),然后再启动容器。
docker images:查看已有的image
docker ps:显示运行的容器
镜像内部结构
base镜像
- 不依赖其他镜像,从scratch构建
- 其他镜像可以以之为基础扩展
- 通常为各种Linux发行的Docker镜像,比如Ubuntu、Debian、CentOS等。
- CentOS镜像大小大约为200M
Linux操作系统由内核空间和用户空间组成。
内核空间是kernel,Linux刚启动时会加载bootfs文件系统,之后bootfs系统会被卸载掉。
用户空间的文件系统是rootfs,包含/dev、/proc、/bin等目录。
对于base镜像来说,底层直接用host的kernel,自己只需要提供rootfs就行。
对于精简OS,rootfs可以很小:基本命令、工具和程序库。平时安装的CentOS除了rootfs还有很多软件、服务、图形桌面等。
base镜像只是在用户空间与发行版一致,kernel版本与发行版是不同的。
镜像分层结构
Docker支持通过扩展现有镜像,创建新的镜像。
优势:共享资源
比如:有多个镜像都从相同的base镜像构建,那么服务器只需要在磁盘上保存一份base镜像;同时内存中也只需加载一份base镜像,就可以为所有容器服务。
当容器启动时,一个新的可写层被加载到镜像的顶部。
**这一层通常被称作容器层,容器层下的都叫镜像层。**所有对容器的改动,都只会发生在容器层中,只有容器层是可写的,下面的镜像层都是只读的。
镜像层数量可能会很多,所有镜像层联合在一起组成统一的文件系统。如果不同层中有同一路径的文件,比如/a,上层的/a会覆盖下层的/a,用户只能访问到上层的/a。
只有当需要修改时才复制一份数据,这种特性被称作Copy-on-Write。容器层保存的是镜像变化的部分,不会对镜像本身进行任何修改。
构建镜像
两种方式:
- docker commit
- Dockerfile
docker commit三个步骤:
运行容器
docker run -it ubuntu(-it参数的作用是以交互模式进入容器,并打开终端)
修改容器
apt-get install -y xxx
保存为新镜像
docker commit
Dockerfile
是一个文本文件,底层也是docker commit一层一层构建镜像的。
FROM ubuntu
RUN apt-get update && apt-get install -y vim
过程分为两步
构建镜像成为容器:docker build -t hahaha .
执行docker build命令, -t 对新镜像命名(hahaha),命令末尾的.指明build context为当前目录。Docker默认会从这里查找DockerFIle文件,也可通过-f参数指定Dockerfile的位置。
root@:~# pwd
/root
root@:~# docker build -t hahaha .
- 运行容器:docker run hahaha
build context
首先Docker将build context中的所有文件发送给Docker daemon。build context为镜像构建提供所需要的文件或目录。 Dockerfile中的ADD、COPY等命令可以将build context中的文件添加到镜像。此例中,build context为当前目录 /root,该目录下的所有文件和子目录都会被发送给Docker daemon。 所以,使用build context就得小心了,不要将多余文件放到build context,特别不要把 /、/usr作为build context,否则构建过程会相当缓慢甚至失败
步骤:
- 执行FROM ,将ubuntu作为base镜像
- 执行RUN安装vim
- 构建成功
镜像分层结构
新的镜像是通过base镜像的顶部添加一个新的镜像层得到的。
docker history会显示镜像的构建历史,也就是Dokcerfile的执行过程。
此时多了97.07MB的一个镜像层。
镜像层缓存特性
FROM ubuntu
RUN apt-get update && apt-get install -y vim
#复制一个文件
COPY testfile/
如果增加新的镜像层之前,运行过相同的RUN指令,就直接使用缓存中的镜像层35ca89798937
如果不希望使用缓存在命令中加入--no--cache参数。
缓存失效
Dockerfile中的每个指令都会创建一个镜像层,上层依赖下层,如果下层发生变化,那么上层的缓存就会失效。
FROM ubuntu
#复制一个文件
COPY testfile/
RUN apt-get update && apt-get install -y vim
虽然逻辑上这种改动没有影响,但由于分层结构特性,Docker必须重建受影响的镜像层。
除了构建时候可以使用缓存,pull镜像的时候也会使用。
Dockerfile构建镜像过程
- 从base镜像运行一个容器
- 执行一条指令,对容器做修改
- 执行类型docker commit操作,生成一个新的镜像层
- Docker再基于刚刚提交的镜像运行一个新容器
- 重复2-4步,直到Dockerfile中所有的指令执行完毕。
如果某个指令执行失败,也能够得到前一个指令生成的镜像,有助于调试。
Dockerfile常用命令
FROM:指定base镜像
COPY:将文件从builde context复制到镜像,支持两种形式:COPY src dest与COPY["src", "dest"]。 src为文件目录
ADD:与COPY类似,从build context复制文件到镜像。不同的是,如果src是归档文件(tar、zip、tgz、xz等),文件会被自动解压到dest。
ENV:设置环境变量,环境变量可以被后面的指令使用
RUN:在容器中运行指定的命令。
CMD:容器启动时运行指定的命令,Dockerfile可以有多个CMD命令,但只有最后一个生效。可以被docker run之后的参数替换,目的是不用更改Dockerfile。
dockerfileFROM ubuntu:20.04 # 设置工作目录 WORKDIR /app # 复制应用程序文件到容器 COPY . /app # 定义默认的CMD命令 CMD ["python", "app.py"]
docker build -t myapp .
docker run myapp,会直接运行python的app.py文件
ENTRYPOINT:同样为容器启动时命令。不可以被docker run之后的参数替换,CMD用于补充该命令
dockerfileFROM ubuntu:20.04 ENTRYPOINT ["echo", "Hello"] CMD ["World!"]
将打印出 "Hello World!"。
WORKDIR:为后面的指令RUN、ADD等指令设置当前工作目录
容器
运行容器
- docker run 启动容器,docker run后面可以跟CMD命令, --name显式的为容器命名
- docker ps -a显示所有状态容器,退出状态为Exited
- docker run -d 以后台方式启动容器,完成后返回容器唯一长ID。
- docker ps看到的短ID是长ID的前12位
- docker exec进入容器,docker exec -it以交互模式,打开一个新的bash终端,exit退出容器。
- stop/start/restart容器
- docker rm删除容器
资源限制
一个docker host上会运行若干容器,每个容器都需要CPU、内存和IO资源。
容器可使用的内存:
物理内存(RAM):在计算机运行时为操作系统和各种程序提供临时储存。
swap(交换空间)它用于在物理内存(RAM)被完全使用时存放不再使用或者暂时不使用的数据。当系统运行内存紧张时,会利用交换空间存放部分数据,以便为其他程序提供更多可用内存。
设置内存
#最多使用200m内存和100m的交换空间,默认都为-1,指没有限制
docker run -m 200m --memory-swap=300M ubuntu
如果在启动容器时只指定 -m而不指定 --memory-swap,那么 --memory-swap默认为 -m的两倍
CPU
默认情况下,容器使用主机CPU是不受控制的。如果不进行限制,出现异常使用CPU的现象,可能会把主机的CPU资源耗尽。
#最多可以使用主机上的两个cpu的负载,并不是指定两个固定的cpu,极限时,是多个cpu综合起来是两个cpu的负载
docker run -it --rm --cpus=2
#指定固定的cpu的负载,只在这个cpu上使用
docker run --it --rm --cpuset-cpus="1" u-stress:latest /bin/bash
#u-stress:latest:这是你要运行的Docker容器的名称。u-stress可能是用户名,latest是镜像的版本。
#/bin/bash:这是在容器启动后要执行的命令。在这种情况下,它启动一个bash shell,使你可以在容器内交互式地运行命令。