容器runtime
:runtime 是容器真正运行的地方。runtime 需要跟操作系统 kernel 紧密协作,为容器提供运行环境。
可以类比java,Java 程序就好比是容器,JVM 则好比是 runtime。JVM 为 Java 程序提供运行环境。同样的道理,容器只有在 runtime 中才能运行。
lxc、runc 和 rkt 是目前主流的三种容器 runtime。
容器管理工具
:容器管理工具对内与 runtime 交互,对外为用户提供 interface,比如 CLI。
runc 的管理工具是 docker engine。docker engine 包含后台 deamon 和 cli 两个部分。我们通常提到 Docker,一般就是指的 docker engine。
容器定义工具
:容器定义工具允许用户定义容器的内容和属性,这样容器就能够被保存,共享和重建。
docker image 是 docker 容器的模板,runtime 依据 docker image 创建容器。
dockerfile 是包含若干命令的文本文件,可以通过这些命令创建出 docker image。
容器编排引擎
:所谓编排(orchestration),通常包括容器管理、调度、集群定义和服务发现等。通过容器编排引擎,容器被有机的组合成微服务应用,实现业务需求。
容器支持技术
:被用于支持基于容器的基础设施。
容器网络
服务发现
动态变化是微服务应用的一大特点。当负载增加时,集群会自动创建新的容器;负载减小,多余的容器会被销毁。容器也会根据 host 的资源使用情况在不同 host 中迁移,容器的 IP 和端口也会随之发生变化。在这种动态的环境下,必须要有一种机制让 client 能够知道如何访问容器提供的服务。这就是服务发现技术要完成的工作。
服务发现会保存容器集群中所有微服务最新的信息,比如 IP 和端口,并对外提供 API,提供服务查询功能。
什么是容器?
容器是一种轻量级、可移植、自包含的软件打包技术,使应用程序可以在几乎任何地方以相同的方式运行。
容器由两部分组成:
- 应用程序本身
- 依赖:比如应用程序需要的库或其他软件
容器在Host操作系统的用户空间中运行,与操作系统的其他进程隔离。这一点显著区别于虚拟机。
Docker架构
:
Docker 采用的是 Client/Server 架构。客户端向服务器发送请求,服务器负责构建、运行和分发容器。客户端和服务器可以运行在同一个 Host 上,客户端也可以通过 socket 或 REST API 与远程的服务器通信。
Docker客户端:最常用的 Docker 客户端是 docker 命令。通过 docker 我们可以方便地在 Host 上构建和运行容器。
Docker服务器:Docker daemon 是服务器组件,运行在 Docker host 上,负责创建、运行、监控容器,构建、存储镜像。默认配置下,Docker daemon 只能响应来自本地 Host 的客户端请求。如果要允许远程客户端请求,需要在配置文件中打开 TCP 监听。
Docker镜像:可将 Docker 镜像看着只读模板,通过它可以创建 Docker 容器。我们可以将镜像的内容和创建步骤描述在一个文本文件中,这个文件被称作 Dockerfile,通过执行 docker build <docker-file>
命令可以构建出 Docker 镜像。
Docker容器:Docker 容器就是 Docker 镜像的运行实例。用户可以通过 CLI(docker)或是 API 启动、停止、移动或删除容器。可以这么认为,对于应用软件,镜像是软件生命周期的构建和打包阶段,而容器则是启动和运行阶段。
base镜像
:们希望镜像能提供一个基本的操作系统环境,用户可以根据需要安装和配置软件。这样的镜像我们称作 base 镜像:
- 不依赖其他镜像从scratch构建
- 其他镜像可以作为基础进行扩展
所以能称作 base 镜像的通常都是各种 Linux 发行版的 Docker 镜像比如 Ubuntu, Debian, CentOS 等。
Linux 操作系统由内核空间和用户空间组成。
Linux 刚启动时会加载 bootfs 文件系统,之后 bootfs 会被卸载掉。用户空间的文件系统是 rootfs包含我们熟悉的 /dev, /proc, /bin 等目录。
对于 base 镜像来说底层直接用 Host 的 kernel自己只需要提供 rootfs 就行了。
base 镜像提供的是最小安装的 Linux 发行版。
不同 Linux 发行版的区别主要就是 rootfs。
比如 Ubuntu 14.04 使用 upstart 管理服务 , apt 管理软件包;而 CentOS 7 使用 systemd 和 yum。
所有容器都共用 host 的 kernel在容器中没办法对 kernel 升级。
新镜像是从 base 镜像一层一层叠加生成的。每安装一个软件,就在现有镜像的基础上增加一层。
这种分层结构的最大好处就是:共享资源
容器 Copy-on-Write 特性:只有当需要修改时才复制一份数据,这种特性被称作 Copy-on-Write。
当容器启动时,一个新的可写层被加载到镜像的顶部。这一层通常被称作“容器层”,“容器层”之下的都叫“镜像层”。
所有对容器的改动 – 无论添加、删除、还是修改文件都只会发生在容器层中。
只有容器层是可写的,容器层下面的所有镜像层都是只读的。
镜像层数量可能会很多,所有镜像层会联合在一起组成一个统一的文件系统。如果不同层中有一个相同路径的文件,比如 /a,上层的 /a 会覆盖下层的 /a,也就是说用户只能访问到上层中的文件 /a。在容器层中,用户看到的是一个叠加之后的文件系统。
- 添加文件:在容器中创建文件时,新文件被添加到容器层中。
- 读取文件:在容器中读取某个文件时,Docker 会从上往下依次在各镜像层中查找此文件。一旦找到,立即将其复制到容器层,然后打开并读入内存。
- 修改文件:在容器中修改已存在的文件时,Docker 会从上往下依次在各镜像层中查找此文件。一旦找到,立即将其复制到容器层,然后修改之。
- 删除文件:在容器中删除文件时,Docker 也是从上往下依次在镜像层中查找此文件。找到后,会在容器层中记录下此删除操作。
可见,容器层保存的是镜像变化的部分,不会对镜像本身进行任何修改。
容器层记录对镜像的修改,所有镜像层都是只读的,不会被容器修改,所以镜像可以被多个容器共享。
使用docker commits
命令来创建新镜像(通过进入老镜像进行修改来构建新镜像) 不推荐
即便是用 Dockerfile(推荐方法)构建镜像,底层也是 docker commit 一层一层构建新镜像的。
docker build
命令可以通过-f
参数指定Dockerfile的位置。
镜像的构建过程:首先 Docker 将 build context 中的所有文件发送给 Docker daemon。build context 为镜像构建提供所需要的文件或目录。Dockerfile 中的 ADD
、COPY
等命令可以将 build context 中的文件添加到镜像。
所以,使用 build context 就得小心了,不要将多余文件放到 build context,特别不要把 /
、/usr
作为 build context,否则构建过程会相当缓慢甚至失败。
镜像的缓存:Docker 会缓存已有镜像的镜像层,构建新镜像时,如果某镜像层已经存在,就直接使用,无需重新创建。
但是注意:Dockerfile中每一个指令都会创建一个镜像层,上层是依赖于下层的。无论什么时候,只要某一层发生变化,其上面所有层的缓存都会失效。也就是说,如果我们改变Dockerfile 指令的执行顺序,或者修改或添加指令,都会使缓存失效。
调试Dockerfile:通过运行最新的镜像定位指令失败的原因。
镜像tag的社区方案
假设我们现在发布了一个镜像 myimage,版本为 v1.9.1。那么我们可以给镜像打上四个 tag:1.9.1、1.9、1 和 latest。
这种 tag 方案使镜像的版本很直观,用户在选择非常灵活:
- myimage:1 始终指向 1 这个分支中最新的镜像。
- myimage:1.9 始终指向 1.9.x 中最新的镜像。
- myimage:latest 始终指向所有版本中最新的镜像。
- 如果想使用特定版本,可以选择 myimage:1.9.1、myimage:1.9.2 或 myimage:2.0.0。
容器在 docker host 中实际上是一个进程,docker stop
命令本质上是向该进程发送一个 SIGTERM 信号。如果想快速停止容器,可使用 docker kill
命令,其作用是向容器进程发送 SIGKILL 信号。
--restart=always
意味着无论容器因何种原因退出(包括正常退出),就立即重启。该参数的形式还可以是 --restart=on-failure:3
,意思是如果启动进程退出代码非0,则重启容器,最多重启3次。
有时我们只是希望暂时让容器暂停工作一段时间,比如要对容器的文件系统打个快照,或者 dcoker host 需要使用 CPU,这时可以执行 docker pause
。处于暂停状态的容器不会占用 CPU 资源,直到通过 docker unpause
恢复运行。
使用 docker 一段时间后,host 上可能会有大量已经退出了的容器。这些容器依然会占用 host 的文件系统资源,如果确认不会再重启此类容器,可以通过 docker rm
删除。
如果希望批量删除所有已经退出的容器,可以执行如下命令:
docker rm -v $(docker ps -aq -f status=exited)1
顺便说一句:docker rm
是删除容器,而 docker rmi
是删除镜像。
与操作系统类似,容器可使用的内存包括两部分:物理内存和swap。通过-m
或 --memory
:设置内存的使用限额;--memory-swap
:设置 内存+swap 的使用限额。
通过 cpu share 可以设置容器使用 CPU 的优先级。
底层实现技术
cgroup 和 namespace 是最重要的两种技术。cgroup 实现资源限额, namespace 实现资源隔离。
cgroup 全称 Control Group。Linux 操作系统通过 cgroup 可以设置进程使用 CPU、内存 和 IO 资源的限额。
namespace 管理着 host 中全局唯一的资源,并可以让每个容器都觉得只有自己在使用它。换句话说,namespace 实现了容器间资源的隔离。
Linux 使用了六种 namespace,分别对应六种资源:Mount、UTS、IPC、PID、Network 和 User。
Mount namespace
让容器看上去拥有整个文件系统。UTS namespace
让容器有自己的 hostname。 默认情况下,容器的 hostname 是它的短ID,可以通过-h
或--hostname
参数设置。IPC namespace
让容器拥有自己的共享内存和信号量(semaphore)来实现进程间通信,而不会与 host 和其他容器的 IPC 混在一起。- 容器中进程的 PID 不同于 host 中对应进程的 PID,容器中 PID=1 的进程当然也不是 host 的 init 进程。也就是说:容器拥有自己独立的一套 PID,这就是
PID namespace
提供的功能。 Network namespace
让容器拥有自己独立的网卡、IP、路由等资源。User namespace
让容器能够管理自己的用户,host 不能看到容器中创建的用户。
Dockers提供几种原生网络,Docker安装时会自动在host上创建三个网络,分别为none、host、bridge。默认为bridge网络。
除了 none, host, bridge 这三个自动创建的网络,用户也可以根据业务需要创建 user-defined 网络。Docker 提供三种 user-defined 网络驱动:bridge, overlay 和 macvlan。
容器之间可通过 IP,Docker DNS Server 或 joined 容器三种方式通信。
容器默认就能访问外部世界,这里指的是容器网络外的网络环境。
实现的原理是做了一次NAT,如下图:
- busybox 发送 ping 包:172.17.0.2 > www.bing.com。
- docker0 收到包,发现是发送到外网的,交给 NAT 处理。
- NAT 将源地址换成 enp0s3 的 IP:10.0.2.15 > www.bing.com。
- ping 包从 enp0s3 发送出去,到达 www.bing.com。
而外部网络访问容器则是通过:端口映射。
每一个映射的端口,host 都会启动一个 docker-proxy 进程来处理访问容器的流量:
以 0.0.0.0:32773->80/tcp 为例分析整个过程:
- docker-proxy 监听 host 的 32773 端口。
- 当 curl 访问 10.0.2.15:32773 时,docker-proxy 转发给容器 172.17.0.2:80。
- httpd 容器响应请求并返回结果。
Docker 为容器提供了两种存放数据的资源:
- 由 storage driver 管理的镜像层和容器层。
- Data Volume。
storage driver
分层结构使镜像和容器的创建、共享以及分发变得非常高效,而这些都要归功于 Docker storage driver。正是 storage driver 实现了多层数据的堆叠并为用户提供一个单一的合并之后的统一视图。
Docker 支持多种 storage driver,有 AUFS、Device Mapper、Btrfs、OverlayFS、VFS 和 ZFS。
优先使用 Linux 发行版默认的 storage driver。
对于某些容器,直接将数据放在由 storage driver 维护的层中是很好的选择,比如那些无状态的应用。无状态意味着容器没有需要持久化的数据,随时可以从镜像直接创建。
但对于另一类应用这种方式就不合适了,它们有持久化数据的需求,容器启动时需要加载已有的数据,容器销毁时希望保留产生的新数据,也就是说,这类容器是有状态的。这就要用到 Docker 的另一种存储机制:Data Volume。
Data Volume 本质上是 Docker Host 文件系统中的目录或文件,能够直接被 mount 到容器的文件系统中。Data Volume 有以下特点:
- Data Volume 是目录或文件,而非没有格式化的磁盘(块设备)。
- 容器可以读写 volume 中的数据。
- volume 数据可以被永久的保存,即使使用它的容器已经销毁。
docker 提供了两种类型的 volume:bind mount 和 docker managed volume。
两者的不同点:
共享数据我们可以使用volume container或data-packed volume container。
- docker 原生的 overlay 和 macvlan。
- 第三方方案:常用的包括 flannel、weave 和 calico。
libnetwork 是 docker 容器网络库,最核心的内容是其定义的 Container Network Model (CNM),这个模型对容器网络进行了抽象,由以下三类组件组成:
- Sandbox 是容器的网络栈,包含容器的 interface、路由表和 DNS 设置。 Linux Network Namespace 是 Sandbox 的标准实现。Sandbox 可以包含来自不同 Network 的 Endpoint。
- Endpoint 的作用是将 Sandbox 接入 Network。Endpoint 的典型实现是 veth pair,后面我们会举例。一个 Endpoint 只能属于一个网络,也只能属于一个 Sandbox。
- Network 包含一组 Endpoint,同一 Network 的 Endpoint 可以直接通信。Network 的实现可以是 Linux Bridge、VLAN 等。
flannel 是 CoreOS 开发的容器网络解决方案。flannel 为每个 host 分配一个 subnet,容器从此 subnet 中分配 IP,这些 IP 可以在 host 间路由,容器间无需 NAT 和 port mapping 就可以跨主机通信。
每个 subnet 都是从一个更大的 IP 池中划分的,flannel 会在每个主机上运行一个叫 flanneld 的 agent,其职责就是从池子中分配 subnet。为了在各个主机间共享信息,flannel 用 etcd(与 consul 类似的 key-value 分布式数据库)存放网络配置、已分配的 subnet、host 的 IP 等信息。
使用flannel来进行跨主机通信的数据流向:
从业务数据的角度看,容器可以分为两类:无状态(stateless)容器和有状态(stateful)容器。
无状态是指容器在运行过程中不需要保存数据,每次访问的结果不依赖上一次访问,比如提供静态页面的 web 服务器。
有状态是指容器需要保存数据,而且数据会发生变化,访问的结果依赖之前请求的处理结果,最典型的就是数据库服务器。
简单来讲,状态(state)就是数据,如果容器需要处理并存储数据,它就是有状态的,反之则无状态。
容器核心技术
包括容器规范、容器runtime、容器管理工具、容器定义工具、Registry以及容器OS
- 容器规范:OpenContainer Initiative(OCI)是制定开放的容器规范的组织。目前OCI发布了两个规范:runtime spec和image format spec,保证了容器的可移植性和互操作性。
- 容器runtime:runtime是容器真正运行的地方,需要跟操作系统kernel紧密协作,为容器提供运行环境。目前主流的三种容器runtime:lxc、runc和rkt。lxc是Linux上老牌的容器runtime;runc是Docker自己开发的容器runtime;rkt是CoreOS开发的容器runtime。
- 容器管理工具:对内与runtime交互,对外为用户提供interface。lxc的管理工具是lxd;runc的管理工具是docker engine;kt的管理工具是rkt cli。我们通常提到Docker,一般就是指的docker engine。
- 容器定义工具:docker image,dockerfile,ACI(App Container Image)。允许用户定义容器的内容和属性,使容器能够被保存、共享和重建。
- Registry:存放image的仓库,比如Docker Hub。
- 容器OS:专门运行容器的操作系统。比如CoreOS、Atomic和Ubuntu Core。
容器平台技术
容器平台技术包括容器编排引擎、容器管理平台和基于容器的PaaS。容器核心技术使得容器能够在单个host上运行,而容器平台技术能够让容器作为集群在分布式环境中运行。
容器支持技术
- 容器网络:管理容器与容器、容器与其他实体之间的连通性和隔离性。docker network是Docker原生的网络解决方案。
- 服务发现:容器会根据host的资源使用情况在不同host中迁移,容器的IP和端口也会随之发生变化。当负载增加时,集群会自动创建新的容器;负载减小,多余的容器会被销毁。
- 监控:Docker命令行监控工具docker ps/top/stats
- 数据管理:在不同的host上,保证持久化数据和动态迁移。
- 日志管理:Docker日志工具docker logs
- 安全性:容器安全工具OpenSCAP,能够对容器镜像进行扫描,发现潜在的漏洞。
安装Docker:阿里云部署并使用Docker
安装Docker的依赖库
yum install -y yum-utils device-mapper-persistent-data lvm2
添加Docker CE的软件源信息。
yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
安装Docker CE。
yum -y install docker-ce
启动Docker服务。
systemctl start docker
配置镜像加速器
sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<-'EOF'
{
"registry-mirrors": ["https://rr1l27vx.mirror.aliyuncs.com"]
}
EOF
重新加载服务配置文件。
sudo systemctl daemon-reload
重启Docker服务。
sudo systemctl restart docker
复制代码
运行容器:docker run
- -d: 后台运行容器,并返回容器ID;
- -p: 指定端口映射,格式为:主机(宿主)端口:容器端口
- -i: 以交互模式运行容器,通常与 -t 同时使用;
- –name="nginx-lb": 为容器指定一个名称;
[root@iZwz9f1ba8y91wed647jhlZ ~]# docker run -d -p 80:80 httpd //Docker客户端执行docker run命令
Unable to find image 'httpd:latest' locally //Docker daemon发现本地没有httpd镜像
latest: Pulling from library/httpd
7d63c13d9b9b: Already exists //daemon从Docker Hub下载镜像
ca52f3eeea66: Already exists
448256567156: Already exists
21d69ac90caf: Already exists
462e88bc3074: Already exists
Digest: sha256:f70876d78442771406d7245b8d3425e8b0a86891c79811af94fb2e12af0fadeb
Status: Downloaded newer image for httpd:latest //下载完成,镜像httpd被保存到本地
e321c2c27cd7108e3eedf1a16307e38178323fa7d648209bf211f2166c0e46c5 //Docker daemon启动容器
复制代码
其过程可以简单地描述为:
1. 从Docker Hub下载httpd镜像。镜像中已经安装好了Apache HTTP Server。
2. 启动httpd容器,并将容器的80端口映射到host的80端口。
复制代码
第2章 容器核心知识概述
Docker的核心组件包括:
- Docker客户端:Client,最常用的Docker客户端是docker命令。
- Docker服务器:Docker daemon,以Linux后台服务的方式运行,负责创建、运行、监控容器,构建、存储镜像。
- Docker镜像:Image,只读模板,通过它可以创建Docker容器。
- Registry:Registry是存放Docker镜像的仓库,Registry分私有和公有两种。
- Docker容器:Container,是Docker镜像的运行实例。
对于应用软件,镜像是软件生命周期的构建和打包阶段,而容器则是启动和运行阶段。
架构如图: Docker采用的是Client/Server架构。客户端向服务器发送请求,服务器负责构建、运行和分发容器。
docker info 用于查看服务器的信息
docker build <docker-file> 构建出Docker镜像
docker pull 从Registry下载镜像
docker run ... 先下载镜像(如果本地没有),然后再启动容器
docker images 查看镜像
docker ps或者docker container ls显示容器正在运行
docker history ... 显示镜像的构建历史,也就是Dockerfile的执行过程。
复制代码
第3章 Docker镜像
镜像的内部结构:
base镜像:
- 不依赖其他镜像,从scratch构建;scratch是一个空的镜像,可以用于构建busybox(一种嵌入式Linux)等超小镜像。
- 其他镜像可以以之为基础进行扩展。 linux操作系统由内核空间和用户空间组成。 内核空间是kernel, Linux刚启动时会加载bootfs文件系统,之后bootfs会被卸载掉。 用户空间的文件系统是rootfs,包含我们熟悉的 /dev、/proc、/bin等目录。 对于base镜像来说,底层直接用Host的kernel,自己只需要提供rootfs就行了。 镜像的分层结构:Docker通过从base镜像一层一层叠加扩展现有镜像,生成的新的镜像。每安装一个软件,就在现有镜像的基础上增加一层。分层结构最大的一个好处就是:共享资源。
当容器启动时,一个新的可写层被加载到镜像的顶部。这一层通常被称作“容器层”,“容器层”之下的都叫“镜像层”。只有容器层是可写的,记录对镜像的修改,容器层下面的所有镜像层都是只读的,所以镜像可以被多个容器共享。
添加文件:在容器中创建文件时,新文件被添加到容器层中。
读取文件:在容器中读取某个文件时,Docker会从上往下依次在各镜像层中查找此文件。一旦找到,打开并读入内存。
修改文件:在容器中修改已存在的文件时,Docker会从上往下依次在各镜像层中查找此文件。一旦找到,立即将其复制到容器层,然后修改之。这种特性被称作Copy-on-Write。
删除文件:在容器中删除文件时,Docker也是从上往下依次在镜像层中查找此文件。找到后,会在容器层中记录下此删除操作
复制代码
构建镜像的方法: docker commit命令与Dockerfile构建文件。 docker commit包含三个步骤:
- 运行容器。
- 修改容器。
- 将容器保存为新的镜像。
运行容器 ubuntu
[root@iZwz9f1ba8y91wed647jhlZ ~]# docker run -it ubuntu
Unable to find image 'ubuntu:latest' locally
latest: Pulling from library/ubuntu
7b1a6ab2e44d: Pull complete
Digest: sha256:626ffe58f6e7566e00254b638eb7e0f3b11d4da9675088f4781a50ae288f3322
Status: Downloaded newer image for ubuntu:latest
执行vim,命令找不到
root@11846efaf6b0:/# vim
bash: vim: command not found
安装vim,失败
root@11846efaf6b0:/# apt-get install -y vim
Reading package lists... Done
Building dependency tree
Reading state information... Done
E: Unable to locate package vim
查看版本
root@20856f23257f:/etc/apt# cat /etc/issue
Ubuntu 20.04.3 LTS \n \l
新建配置文件
root@20856f23257f:/etc/apt# touch /etc/apt/sources.list
修改源
echo deb http://mirrors.aliyun.com/ubuntu/ focal main restricted universe multiverse >> /etc/apt/sources.list
echo deb http://mirrors.aliyun.com/ubuntu/ focal-security main restricted universe multiverse >> /etc/apt/sources.list
echo deb http://mirrors.aliyun.com/ubuntu/ focal-updates main restricted universe multiverse >> /etc/apt/sources.list
echo deb http://mirrors.aliyun.com/ubuntu/ focal-proposed main restricted universe multiverse >> /etc/apt/sources.list
echo deb http://mirrors.aliyun.com/ubuntu/ focal-backports main restricted universe multiverse >> /etc/apt/sources.list
echo deb-src http://mirrors.aliyun.com/ubuntu/ focal main restricted universe multiverse >> /etc/apt/sources.list
echo deb-src http://mirrors.aliyun.com/ubuntu/ focal-security main restricted universe multiverse >> /etc/apt/sources.list
echo deb-src http://mirrors.aliyun.com/ubuntu/ focal-updates main restricted universe multiverse >> /etc/apt/sources.list
echo deb-src http://mirrors.aliyun.com/ubuntu/ focal-proposed main restricted universe multiverse >> /etc/apt/sources.list
echo deb-src http://mirrors.aliyun.com/ubuntu/ focal-backports main restricted universe multiverse >> /etc/apt/sources.list
激活和更新源
apt-get update
安装vim
apt-get install -y vim
查看vim版本
root@20856f23257f:/etc/apt# vim --version
VIM - Vi IMproved 8.1 (2018 May 18, compiled Sep 20 2021 11:42:42)
不要退出容器
新开一个窗口,查看镜像名称,great_beaver是Docker为容器随机分配的名字
[root@iZwz9f1ba8y91wed647jhlZ ~]# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
84803a53edd9 ubuntu "bash" 34 seconds ago Exited (0) 33 seconds ago great_beaver
执行docker commit命令将容器保存为镜像,新镜像命名为ubuntu-with-vi
[root@iZwz9f1ba8y91wed647jhlZ ~]# docker commit great_beaver ubuntu-with-vi
sha256:41eb93c761bf6b3bbf0a78d8372b04bd1fb07e142d23437d777159cec6ff8df1
查看本地镜像
[root@iZwz9f1ba8y91wed647jhlZ ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu-with-vi latest a6f66dc34962 6 seconds ago 230MB
ubuntu latest ba6acccedd29 3 weeks ago 72.8MB
从size上看到镜像因为安装了软件而变大了,从新镜像启动容器vim已经可以使用
root@97f3cc723809:/# which vi
/usr/bin/vi
复制代码
用Dockerfile创建上节的ubuntu-with-vi-dockerfile
当前文件夹为root
[root@iZwz9f1ba8y91wed647jhlZ ~]# pwd
/root
[root@iZwz9f1ba8y91wed647jhlZ ~]# vim Dockerfile
FROM ubuntu
RUN apt-get update && apt-get install -y vim
:wq保存退出
运行docker build命令,-t将新镜像命名为ubuntu-with-vi-dockerfile
[root@iZwz9f1ba8y91wed647jhlZ ~]# docker build -t ubuntu-with-vim-dockerfile .
Sending build context to Docker daemon 13.82MB
Step 1/2 : FROM ubuntu --执行FROM,将Ubuntu作为base镜像。Ubuntu镜像ID为f753707788c5。
---> ba6acccedd29
Step 2/2 : RUN apt-get update && apt-get install -y vim --执行RUN,安装vim,具体步骤为
---> Running in 28b2570fc24f
... ...
Setting up vim (2:8.1.2269-1ubuntu5.3) ... --启动临时容器,在容器中通过apt-get安装vim
... ...
Removing intermediate container 28b2570fc24f --删除临时容器9f4d4166f7e3
---> 203e55414406
Successfully built 203e55414406 -- 安装成功后,将容器保存为镜像,其ID为203e55414406
Successfully tagged ubuntu-with-vim-dockerfile:latest
查看镜像,ubuntu-with-vim-dockerfile
[root@iZwz9f1ba8y91wed647jhlZ ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu-with-vim-dockerfile latest 203e55414406 3 minutes ago 172MB
复制代码
Docker会在启动的临时容器中执行操作,并通过commit保存为新的镜像。
Dockerfile中最常用的指令:
FROM:指定base镜像
MAINTAINER:设置镜像的作者,可以是任意字符串。
COPY:将文件从build context复制到镜像
ADD:与COPY类似,从build context复制文件到镜像
ENV:设置环境变量
EXPOSE:指定容器中的进程会监听某个端口
VOLUME:将文件或目录声明为volume
WORKDIR:设置镜像中的当前工作目录
RUN:执行命令并创建新的镜像层,RUN经常用于安装软件包。
CMD:设置容器启动后默认执行的命令及其参数,多个CMD指令,但只有最后一个生效。CMD可以被docker run之后的参数替换。
ENTRYPOINT:配置容器启动时运行的命令,有多个ENTRYPOINT指令,但只有最后一个生效。CMD或docker run之后的参数会被当作参数传递给ENTRYPOINT。
复制代码
Dockerfile支持以“#”开头的注释。
分发镜像
将镜像上传到公共Registry,Docker Hub。 一个特定镜像的名字由两部分组成:repository和tag,如果执行docker build时没有指定tag,会使用默认值latest。通过docker tag命令方便地给镜像打tag。
[root@iZwz9f1ba8y91wed647jhlZ ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
httpd latest 1132a4fc88fa 2 weeks ago 143MB
复制代码
保存和分发镜像的最直接方法就是使用Docker Hub。 注册账户,docker login登录 镜像命名格式为:[username]/xxx:tag docker push将镜像上传到Docker Hub
docker commit ... 从容器创建新镜像
docker build ... 从Dockerfile构建镜像
docker tag ... 给镜像打tag
docker login -u ... -p ... 登录Docker Hub账户
docker push ... 上传镜像到Docker Hub
docker pull ... 从Docker Hub拉取镜像
docker rmi ... 删除Docker host中的镜像,不会删除registry的镜像。如果一个镜像对应了多个tag,只有当最后一个tag被删除时,镜像才被真正删除。
docker search ... 搜索Docker Hub中的镜像
复制代码
第4章 Docker容器
后台运行容器:docker run -d 有两种方法进入容器:attach和exec,主要区别如下:
(1)attach直接进入容器启动命令的终端,不会启动新的进程。
(2)exec则是在容器中打开新的终端,并且可以启动新的进程。
(3)如果想直接在终端中查看启动命令的输出,用attach;其他情况使用exec。
复制代码
容器各种状态之间的转换
docker logs 查看启动命令的输出
docker stop 可以停止运行的容器,通过docker start重新启动,会保留容器的第一次启动时的所有参数
docker restart 可以重启容器,其作用就是依次执行docker stop和docker start。只有当容器的启动进程退出时,--restart才生效
docker pause 让容器暂停工作一段时间,通过docker unpause恢复运行
docker rm 一次可以指定多个容器
docker rmi 删除镜像
docker create 创建的容器处于Created状态
docker run 实际上是docker create和docker start的组合。
复制代码
资源限制
与操作系统类似,容器可使用的内存包括两部分:物理内存和swap。
内存限额
Docker通过下面两组参数来控制容器内存的使用量。如果在启动容器时只指定-m,而不指定–memory-swap,那么–memory-swap默认为 -m的两倍。
-m或--memory:设置内存的使用限额,例如100MB,2GB。
--memory-swap:设置内存+swap的使用限额。
-vm 1:启动1个内存工作线程。
--vm-bytes 280M:每个线程分配280MB内存。
复制代码
下面使用progrium/stress镜像来学习如何为容器分配内存。该镜像可用于对容器执行压力测试。执行如下命令:
[root@iZwz9f1ba8y91wed647jhlZ ~]# docker run -it -m 300M --memory-swap=300M progrium/stress --vm 1 --vm-bytes 280M
stress: info: [1] dispatching hogs: 0 cpu, 0 io, 1 vm, 0 hdd
stress: dbug: [1] using backoff sleep of 3000us
stress: dbug: [1] --> hogvm worker 1 [7] forked
stress: dbug: [7] allocating 293601280 bytes ...
stress: dbug: [7] touching bytes in strides of 4096 bytes ...
stress: dbug: [7] freed 293601280 bytes
stress: dbug: [7] allocating 293601280 bytes ...
stress: dbug: [7] touching bytes in strides of 4096 bytes ...
stress: dbug: [7] freed 293601280 bytes
stress: dbug: [7] allocating 293601280 bytes ...
stress: dbug: [7] touching bytes in strides of 4096 bytes ...
stress: dbug: [7] freed 293601280 bytes
stress: dbug: [7] allocating 293601280 bytes ...
... ...
因为280MB在可分配的范围(300MB)内,所以工作线程能够正常工作,
其过程是:
(1)分配280MB内存。
(2)释放280MB内存。
(3)再分配280MB内存。
(4)再释放280MB内存。
(5)一直循环……
如果让工作线程分配的内存超过300MB
[root@iZwz9f1ba8y91wed647jhlZ ~]# docker run -it -m 300M --memory-swap=300M progrium/stress --vm 1 --vm-bytes 310M
stress: info: [1] dispatching hogs: 0 cpu, 0 io, 1 vm, 0 hdd
stress: dbug: [1] using backoff sleep of 3000us
stress: dbug: [1] --> hogvm worker 1 [6] forked
stress: dbug: [6] allocating 325058560 bytes ...
stress: dbug: [6] touching bytes in strides of 4096 bytes ...
stress: FAIL: [1] (416) <-- worker 6 got signal 9
stress: WARN: [1] (418) now reaping child worker processes
stress: FAIL: [1] (422) kill error: No such process
stress: FAIL: [1] (452) failed run completed in 0s
分配的内存超过限额,stress线程报错,容器退出。
复制代码
CPU限额
默认设置下,所有容器可以平等地使用host CPU资源并且没有限制。 Docker可以通过 -c或 –cpu-shares设置容器使用CPU的权重。如果不指定,默认值为1024。 最终能分配到的CPU资源取决于它的cpu share占所有容器cpu share总和的比例。按权重分配CPU只会发生在CPU资源紧张的情况下。如果A处于空闲状态,这时,为了充分利用CPU资源,B也可以分配到全部可用的CPU。
-c或--cpu 设置工作线程的数量。
--cpu-shares 设置容器使用CPU的权重
复制代码
下面继续用progrium/stress做实验
[root@iZwz9f1ba8y91wed647jhlZ ~]# docker run --name container_A -it -c 1024 progrium/stress --cpu 1
stress: info: [1] dispatching hogs: 1 cpu, 0 io, 0 vm, 0 hdd
stress: dbug: [1] using backoff sleep of 3000us
stress: dbug: [1] --> hogcpu worker 1 [7] forked
[root@iZwz9f1ba8y91wed647jhlZ ~]# docker run --name container_B -it -c 512 progrium/stress --cpu 1
stress: info: [1] dispatching hogs: 1 cpu, 0 io, 0 vm, 0 hdd
stress: dbug: [1] using backoff sleep of 3000us
stress: dbug: [1] --> hogcpu worker 1 [7] forked
复制代码
执行top,查看容器对CPU的使用情况,containerA消耗的CPU是containerB的两倍。
暂停container_A
[root@iZwz9f1ba8y91wed647jhlZ ~]# docker pause container_A
container_A
复制代码
top显示containerB在containerA空闲的情况下能够用满整颗CPU
docker top 查看容器对CPU的使用情况
ps axf 查看容器进程
docker pause ... 暂停容器
复制代码
Block IO带宽限额
Block IO即磁盘的读写,docker可通过设置权重、限制bps和iops的方式控制容器读写磁盘的带宽。默认情况下,所有容器能平等地读写磁盘,可以通过设置 –blkio-weight参数来改变容器block IO的优先级。–blkio-weight与 –cpu-shares类似,设置的是相对权重值,默认为500。
bps是byte per second,每秒读写的数据量
iops是io per second,每秒IO的次数
--blkio-weight参数来改变容器block IO的优先级
--device-read-bps:限制读某个设备的bps
--device-write-bps:限制写某个设备的bps
--device-read-iops:限制读某个设备的iops
--device-write-iops:限制写某个设备的iops
复制代码
实现容器的底层技术
cgroup实现资源限额(Control Group),namespace实现资源隔离
cgroup:–cpu-shares、-m、–device-write-bps实际上就是在配置cgroup。在 /sys/fs/cgroup/cpu/docker目录中,Linux会为每个容器创建一个cgroup目录,以容器长ID命名。目录中包含所有与cpu相关的cgroup配置。/sys/fs/cgroup/memory/docker和 /sys/fs/cgroup/blkio/docker中保存的是内存以及Block IO的cgroup配置
namespace:namespace管理着host中全局唯一的资源,并可以让每个容器都觉得只有自己在使用它。namespace实现了容器间资源的隔离。Linux使用了6种namespace,分别对应6种资源:Mount、UTS、IPC、PID、Network和User Mount namespace让容器看上去拥有整个文件系统 UTS namespace让容器有自己的hostname IPC namespace让容器拥有自己的共享内存和信号量(semaphore)来实现进程间通信,而不会与host和其他容器的IPC混在一起。 PID namespace容器拥有自己独立的一套PID Network namespace让容器拥有自己独立的网卡、IP、路由等资源 User namespace让容器能够管理自己的用户,host不能看到容器中创建的用户
ps axf 查看容器进程
复制代码
第5章 Docker网络
Docker安装时会自动在host上创建三个网络,我们可用docker network ls命令查看
[root@iZwz9f1ba8y91wed647jhlZ ~]# docker network ls
NETWORK ID NAME DRIVER SCOPE
60f16f858b56 bridge bridge local
8bc829e974c5 host host local
4f4a4bb4d47e none null local
复制代码
- none网络,就是什么都没有的网络。挂在这个网络下的容器除了lo,没有其他任何网卡。容器创建时,可以通过 –network=none指定使用none网络。对安全性要求高并且不需要联网的应用可以使用none网络。
- host网络,连接到host网络的容器共享Docker host的网络栈,容器的网络配置与host完全一样。可以通过 –network=host指定使用host网络。最大的好处就是性能,如果容器对网络传输效率有较高要求,则可以选择host网络。
- bridge网络,Docker安装时会创建一个命名为docker0的Linux bridge。如果不指定–network,创建的容器默认都会挂到docker0上。通过dockernetwork inspect bridge看一下bridge网络的配置信息。
Docker提供三种user-defined网络驱动:bridge、overlay和macvlan。 指定IP网段,只需在创建网段时指定 –subnet和 –gateway参数。
容器间通信
容器之间可通过IP, Docker DNS Server或joined容器三种方式通信。 外部世界访问容器:端口映射。docker可将容器对外提供服务的端口映射到host的某个端口,外网通过该端口访问容器。每一个映射的端口,host都会启动一个docker-proxy进程来处理访问容器的流量
ip -r 查看docker host的路由表
docker ps或者docker port查看host映射端口
复制代码
第6章 Docker存储
Docker为容器提供了两种存放数据的资源:
(1)由storage driver管理的镜像层和容器层。
(2)Data Volume。
复制代码
先来回顾一下Docker镜像的分层结构:
容器由最上面一个可写的容器层,以及若干只读的镜像层组成,容器的数据就存放在这些层中。这样的分层结构最大的特性是Copy-on-Write。
- 新数据会直接存放在最上面的容器层。
- 修改现有数据会先从镜像层将数据复制到容器层,修改后的数据直接保存在容器层中,镜像层保持不变。
- 如果多个层中有命名相同的文件,用户只能看到最上面那层中的文件。
storage driver
分层结构使镜像和容器的创建、共享以及分发变得非常高效,而这些都要归功于Docker storagedriver。storage driver实现了多层数据的堆叠,并为用户提供一个单一的合并之后的统一视图。Docker支持多种storage driver,有AUFS、Device Mapper、Btrfs、OverlayFS、VFS和ZFS。
storage driver适用于无状态的应用。无状态意味着容器没有需要持久化的数据,不需要保存数据供以后使用,使用完直接退出,容器删除时存放在容器层中的工作数据也一起被删除。
Data Volume
Data Volume本质上是Docker Host文件系统中的目录或文件,能够直接被mount到容器的文件系统中。适用于有状态的容器,即有持久化数据的需求,容器启动时需要加载已有的数据,容器销毁时希望保留产生的新数据。
bind mount:通过-v 将文件mount到容器,格式为 <host path>:<container path>
docker managed volume:不需要指定mount源,指明mountpoint就行,docker会生成一个随机目录作为mount源
通过docker inspect查看volume,或者用docker volume命令
复制代码
数据共享
容器与host共享数据:两种类型的data volume,它们均可实现在容器与host之间共享数据
bind mount:直接将要共享的目录mount到容器
docker managed volume:容器启动时将共享数据从host的目录复制到volume
复制代码
容器之间共享数据:
第一种方法是将共享数据放在bind mount中,然后将其mount到多个容器。
另一种在容器之间共享数据的方式是使用volume container。
volume container是专门为其他容器提供volume的容器。它提供的卷可以是bind mount,也可以是docker managed volume。
执行docker create命令,创建一个volume container。只是提供数据,它本身不需要处于运行状态
[root@iZwz9f1ba8y91wed647jhlZ ~]# docker create --name vc_data -v ~/htdocs:/usr/local/apache2/htdocs -v /other/useful/tools busybox
Unable to find image 'busybox:latest' locally
latest: Pulling from library/busybox
01c2cdc13739: Pull complete
Digest: sha256:139abcf41943b8bcd4bc5c42ee71ddc9402c7ad69ad9e177b0a9bc4541f14924
Status: Downloaded newer image for busybox:latest
86d3ec5270b9c91d3a86c109e44edbe58904f81aa261a19a797b0e8321227585
复制代码
通过docker inspect可以查看到这两个volume
[root@iZwz9f1ba8y91wed647jhlZ ~]# docker inspect vc_data
[
{
... ...
"Mounts": [
{
"Type": "bind",
"Source": "/root/htdocs",
"Destination": "/usr/local/apache2/htdocs",
"Mode": "",
"RW": true,
"Propagation": "rprivate"
},
{
"Type": "volume",
"Name": "2c77c1371f9adee9d9156387281c2f692df3aca2fd4d86362f18d6f1824920e0",
"Source": "/var/lib/docker/volumes/2c77c1371f9adee9d9156387281c2f692df3aca2fd4d86362f18d6f1824920e0/_data",
"Destination": "/other/useful/tools",
"Driver": "local",
"Mode": "",
"RW": true,
"Propagation": ""
}
],
... ...
}
]
复制代码
其他容器可以通过–volumes-from使用vc_data这个volume container
[root@iZwz9f1ba8y91wed647jhlZ ~]# docker run --name web1 -d -p 80 --volumes-from vc_data httpd
b173ab11e625dd889040d2c8aec21155da0e49c4f26bb3994f01605300ca8773
查看web1的volume
[root@iZwz9f1ba8y91wed647jhlZ ~]# docker inspect web1 | grep Mounts -A 20
"Mounts": [
{
"Type": "bind",
"Source": "/root/htdocs",
"Destination": "/usr/local/apache2/htdocs",
"Mode": "",
"RW": true,
"Propagation": "rprivate"
},
{
"Type": "volume",
"Name": "2c77c1371f9adee9d9156387281c2f692df3aca2fd4d86362f18d6f1824920e0",
"Source": "/var/lib/docker/volumes/2c77c1371f9adee9d9156387281c2f692df3aca2fd4d86362f18d6f1824920e0/_data",
"Destination": "/other/useful/tools",
"Driver": "local",
"Mode": "",
"RW": true,
"Propagation": ""
}
],
"Config": {
复制代码
web1容器使用的就是vc_data的volume,而且连mount point都是一样的
docker inspect ... 查看volume
grep -C 5 foo file 显示file文件里匹配foo字串那行以及上下5行
grep -B 5 foo file 显示foo及前5行
grep -A 5 foo file 显示foo及后5行
复制代码
data-packed volume container,将数据完全放到volumecontainer中,同时又能与其他容器共享。其原理是将数据打包到镜像中,然后通过docker managed volume共享。data-packed volume container是自包含的,不依赖host提供数据,具有很强的移植性,非常适合只使用静态数据的场景,比如应用的配置信息、Web server的静态文件等。
docker volume ls 查看容器使用的docker managed volume
docker volume rm删除孤儿volume
复制代码
第7章 多主机管理
用Docker Machine可以批量安装和配置docker host,这个host可以是本地的虚拟机、物理机,也可以是公有云中的云主机。
安装 Docker Machine
base=https://github.com/docker/machine/releases/download/v0.16.0 &&
curl -L $base/docker-machine-$(uname -s)-$(uname -m) >/tmp/docker-machine &&
sudo mv /tmp/docker-machine /usr/local/bin/docker-machine &&
chmod +x /usr/local/bin/docker-machine
复制代码
Docker Machine命令
docker-machine ls查看当前的machine
docker-machine create 创建机器
docker-machine ip ... 查看机器ip
docker-machine upgrade:更新machine的docker到最新版本
docker-machine config:查看machine的docker daemon配置
stop/start/restart:是对machine的操作系统操作
docker-machine scp:可以在不同machine之间复制文件
复制代码
第8章 容器网络
单个Docker Host内容器通信:通过none、host、bridge和joined容器。 跨主机网络方案包括:
- docker原生的overlay和macvlan;
- 第三方方案:常用的包括flannel、weave和calico
第9章 容器监控
Docker自带的几个监控子命令:ps、top和stats。
docker container ps \docker container ls 查看当前运行的容器
docker container top[container] : 某个容器中运行了哪些进程docker container top sysdig -au
docker container stats用于显示每个容器各种资源的使用情况
复制代码
Ps、top、stats这几个命令是Docker自带的,优点是运行方便,很适合想快速了解容器的运行状态的场景。其缺点是输出的数据有限,而且都是实时数据,无法反映历史变化和趋势。
开源监控工具sysdig、Weave Scope、cAdvisor和Prometheus。
sysdig:是一个轻量级的开源的系统监控、捕获、分析和排障的工具,同时它还原生支持容器。 优点:功能全面,全方面的系统参数:CPU、Memory、Disk IO、网络 IO,支持各种 IO 活动:进程、文件、网络连接等.能获取实时的系统数据,也能把信息保存到文件中以供后面分析.用户可以随意组合自己的过滤逻辑,还可以自己编写 Lua 脚本来自定义分析逻辑,基本上不受任何限制。
工作原理:sysdig容器是以privileged方式运行,而且会读取操作系统/dev、/proc等数据,这是为了获取足够的系统信息。Sysdig 通过在内核的 driver 模块注册系统调用的 hook,这样当有系统调用发生和完成的时候,它会把系统调用信息拷贝到特定的 buffer,然后用户模块的组件对数据信息处理(解压、解析、过滤等),并最终通过 Sysdig 命令行和用户进行交互。
缺点:sysdig显示的是实时数据,看不到变化和趋势,而且是命令行操作方式。
Weave Scope:最大特点是会自动生成一张Docker Host地图,让我们能够直观地理解、监控和控制容器。可以监控kubernetes集群中的一系列资源的状态、资源使用情况、应用拓扑、scale、还可以直接通过浏览器进入容器内部调试等,其提供的功能包括:交互式拓扑界面、图形模式和表格模式、过滤功能、搜索功能、实时度量、容器排错、插件扩展。可用于k8s监控。
cAdvisor:是google开发的容器监控工具,它被内嵌到k8s中作为k8s的监控组件。会显示当前host的资源使用情况,包括CPU、内存、网络、文件系统等。一个亮点是它可以将监控到的数据导出给第三方工具,由这些工具进一步加工处理。
Prometheus:提供了监控数据搜集、存储、处理、可视化和告警一套完整的解决方案,Prometheus广泛用于 Kubernetes 集群的监控系统中。
Prometheus具有以下特点:具有由指标名称和键/值对标识的时间序列数据的多维度数据模型。PromQL灵活的查询语言。不依赖分布式存储,单个服务器节点是自主的。通过基于HTTP的pull方式采集时序数据。可以通过中间网关(Pushgateway)进行时序列数据推送。通过服务发现或者静态配置来发现目标服务对象。支持多种多样的图表和界面展示,比如Grafana等。
功能比较:
第10章 日志管理
Docker logs:Docker自带的logs子命令,logging driver
日志管理方案:ELK、Fluentd和Graylog
ELK是开源实时日志分析平台,Elasticsearch,logash,kibana的结合。 Elasticsearch的功能:
- 搜索
- 全文检索
- 分析数据
- 处理海量数据PB,对海量数据进行近实时的处理(ES可以自动将海量数据分散到多台服务器上去存储和检索)
- 高可用高性能分布式搜索引擎数据库 Elasticsearch的应用场景:
- 网页搜索
- 新闻搜索
- 商品标签
- 日志收集分析展示
Fluentd是一个完全免费且完全开源的日志收集器,可以连接各种数据源和数据输出组件。Fluentd将日志视为JSON。
Graylog是一个开源的日志聚合、分析、审计、展现和预警工具。功能上和ELK类似,但又比ELK更加简洁,高效,部署使用简单。
第11章 数据管理
从业务数据的角度看,容器可以分为两类:无状态(stateless)容器和有状态(stateful)容器。
如果Docker Host宕机了,如何恢复容器?一个办法就是定期备份数据,但这种方案还是会丢失上次备份到宕机这段时间的数据。更好的方案是由专门的storage provider提供volume, Docker从provider那里获取volume并挂载到容器。这样即使Host挂了,也可以立刻在其他可用Host上启动相同镜像的容器,同时直接挂载之前使用的volume,这样不会有数据丢失。
跨主机管理data volume,第三方driver:Rex-Ray driver。
REX-Ray 是一个 EMC {code} 团队领导的开源项目,为 Docker、Mesos 及其他容器运行环境提供持续的存储访问。其设计旨在囊括通用存储、虚拟化和云平台,提供高级的存储功能。