1. Docker 概述
1.1 引入 Docker
一款产品从开发到上线,从操作系统,到运行环境,再到应用配置。作为开发 + 运维之间的协作我们需要关心很多东西,这也是很多互联网公司都不得不面对的问题,特别是各种版本的迭代之后,不同版本环境的兼容,对运维人员都是考验。
我们写的代码会接触到好几个环境:开发环境、测试环境以及生产环境。
Docker 之所以发展如此迅速,也是因为它对此给出了一个标准化的解决方案。
环境配置如此麻烦,换一台机器,就要重来一次,费力费时。很多人想到,能不能从根本上解决问题,软件可以带环境安装?也就是说,安装的时候,把原始环境一模一样地复制过来。开发人员利用 Docker 可以消除协作编码时“在我的机器上可正常工作”的问题。
“水土”不服就把“水土”也迁过去。
Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中,然后发布到任何流行的 Linux 机器上。容器是完全使用沙箱机制,相互隔离;容器性能开销极低。
传统上认为,软件编码开发/测试结束后,所产出的成果即是程序或是能够编译执行的二进制字节码等(Java 为例)。而为了让这些程序可以顺利执行,开发团队也得准备完整的部署文件,让维运团队得以部署应用程式,开发需要清楚的告诉运维部署团队,用的全部配置文件+所有软件环境。不过,即便如此,仍然常常发生部署失败的状况。Docker 镜像的设计,使得 Docker 得以打破过去「程序即应用」的观念。透过「镜像(images)」将作业系统核心除外,运作应用程式所需要的系统环境,由下而上打包,达到应用程式跨平台间的无缝接轨运作。
1.2 Docker 说明
Docker 是一个基于 Go 语言实现的开源的应用容器引擎,诞生于 2013 年初,由 dotCloud 公司出品(后改名为 Docker Inc)。从 17.03 版本之后 Docker 分为 CE(Community Edition,社区版)和 EE(Enterprise Edition,企业版)。
Docker 的主要目标是“Build,Ship and Run Any App,Anywhere”,也就是通过对应用组件的封装、分发、部署、运行等生命周期的管理,使用户的 APP(可以是一个 WEB 应用或数据库应用等等)及其运行环境能够做到“一次封装,到处运行”。
Linux 容器技术的出现就解决了这样一个问题,而 Docker 就是在它的基础上发展过来的。将应用运行在 Docker 容器上面,而 Docker 容器在任何操作系统上都是一致的,这就实现了跨平台、跨服务器。只需要一次配置好环境,换到别的机子上就可以一键部署好,大大简化了操作。
小结:解决了运行环境和配置问题的软件容器,方便做持续集成并有助于整体发布的「容器虚拟化技术」。
1.3 Linux 容器
1.3.1 re: 虚拟机技术
虚拟机(Virtual Machine)就是带环境安装的一种解决方案。
它可以在一种操作系统里面运行另一种操作系统,比如在 Windows 系统里面运行 Linux 系统。应用程序对此毫无感知,因为虚拟机看上去跟真实系统一模一样,而对于底层系统来说,虚拟机就是一个普通文件,不需要了就删掉,对其他部分毫无影响。
这类虚拟机完美的运行了另一套系统,能够使应用程序、操作系统和硬件三者之间的逻辑不变。
虽然用户可以通过虚拟机还原软件的原始环境。但是,这个方案有几个缺点:资源占用多;冗余步骤多;启动慢。
1.3.2 容器虚拟化技术
由于前面虚拟机存在上述这些缺点,Linux 发展出了另一种虚拟化技术:Linux 容器(Linux Containers,LXC)。
Linux 容器不是模拟一个完整的操作系统,而是对进程进行隔离。或者说,在正常进程的外面套了一个保护层。对于容器里面的进程来说,它接触到的各种资源都是虚拟的,从而实现与底层系统的隔离。
有了容器,就可以将软件运行所需的所有资源打包到一个隔离的容器中。容器与虚拟机不同,不需要捆绑一整套操作系统,只需要软件工作所需的库资源和设置。系统因此而变得高效轻量并保证部署在任何环境中的软件都能始终如一地运行。
容器是进程级别的,相比虚拟机有很多优势:启动快;资源占用少;体积小。
比较了 Docker 和传统虚拟化方式的不同之处:
- 传统虚拟机技术是虚拟出一套硬件后,在其上运行一个完整操作系统,在该系统上再运行所需应用进程。
- 容器内的应用进程直接运行于宿主的内核,容器内没有自己的内核,而且也没有进行硬件虚拟。因此容器要比传统虚拟机更为轻便。
- 每个容器之间互相隔离,每个容器有自己的文件系统 ,容器之间进程不会相互影响,能区分计算资源。
re:Docker 是什么?
Docker 属于 Linux 容器的一种封装,提供简单易用的容器使用接口。它是目前最流行的 Linux 容器解决方案。
Docker 将应用程序与该程序的依赖,打包在一个文件里面。运行这个文件,就会生成一个虚拟容器。程序在这个虚拟容器里运行,就好像在真实的物理机上运行一样。有了 Docker,就不用担心环境问题。
总体来说,Docker 的接口相当简单,用户可以方便地创建和使用容器,把自己的应用放入容器。容器还可以进行版本管理、复制、分享、修改,就像管理普通的代码一样。
1.4 为什么要使用 Docker?
容器除了运行其中应用外,基本不消耗额外的系统资源,使得应用的性能很高,同时系统的开销尽量小。传统虚拟机方式运行 10 个不同的应用就要起 10 个虚拟机,而 Docker 只需要启动 10 个隔离的应用即可。
具体说来,Docker 在如下几个方面具有较大的优势 → DevOps(开发自运维)
- 【更快速的应用交付和部署】传统的应用开发完成后,需要提供一堆安装程序和配置说明文档,安装部署后需根据配置文档进行繁杂的配置才能正常运行。Docker 化之后只需要交付少量容器镜像文件,在正式生产环境加载镜像并运行即可,应用安装配置在镜像里已经内置好,大大节省部署配置和测试验证时间。
- 【更便捷的升级和扩缩容】随着微服务架构和 Docker 的发展,大量的应用会通过微服务方式架构,应用的开发构建将变成搭乐高积木一样,每个 Docker 容器将变成一块“积木”,应用的升级将变得非常容易。当现有的容器不足以支撑业务处理时,可通过镜像运行新的容器进行快速扩容,使应用系统的扩容从原先的天级变成分钟级甚至秒级。
- 【更简单的系统运维】应用容器化运行后,生产环境运行的应用可与开发、测试环境的应用高度一致,容器会将应用程序相关的环境和状态完全封装起来,不会因为底层基础架构和操作系统的不一致性给应用带来影响,产生新的 BUG。当出现程序异常时,也可以通过测试环境的相同容器进行快速定位和修复。
- 【更高效的计算资源利用】Docker 是内核级虚拟化,其不像传统的虚拟化技术一样需要额外的 Hypervisor 支持,所以在一台物理机上可以运行很多个容器实例,可大大提升物理服务器的 CPU 和内存的利用率。
1.5 企业级应用
2. Docker 组成
左边是 Docker 的客户端,类似我们操作 MySQL 的工具 Navicat,只不过我们这里的是没有图形化界面的命令终端。Docker 客户端是用户与 Docker 服务交互的窗口!我们能看到图中就是各种操作的命令。
中间的是 Docker 后台运行的服务,一个称为 Docker daemon 的守护进程。可以理解为我们 MySQL 的服务,我们的操作命令都是在这部分进行处理!Docker deamon 监听着客户端的请求,并且管理着 Docker 的镜像、容器、网络、磁盘(图中只列出了镜像与容器)等对象。同样,Docker 的客户端与服务可以运行在同一机器上,也可以用某台机器上的客户端远程连接另一台机器上的 Docker 服务,这跟我们的 MySQL 一样的呢。
右边部分是注册仓库,在远古时代做开发的都知道,我们以前需要一个第三方包的时候需要去网上下载对应的 jar 包,很麻烦不说,还容易下的包是不稳定的版本。有了 Maven 之后,我们只要在 Maven 配置文件中引入对应的依赖,就可以直接从远程仓库中下载对应版本的 jar 包了。Docker 中的仓库与 Maven 的仓库是一个概念,可以远程下载常用的镜像,也可以 push 包到远程仓库(如图中的 Redis、Nginx 等镜像),同一个镜像又可以有多个版本,在 Docker 中称为 Tag。
2.1 镜像
Docker 镜像(Image)就是一个只读的模板。镜像可以用来创建 Docker 容器,一个镜像可以创建很多容器。
容器与镜像的关系类似于面向对象编程中的对象和类:
Person p1 = new Person();
Person p2 = new Person();
Person p3 = new Person();
镜像是一种轻量级的、可执行的独立软件包,用来打包软件运行环境和基于运行环境开发的软件。它包含运行某个软件所需要的所有的内容,包括代码、运行时、库、环境变量、配置文件等。我们开发的 web 应用需要 JDK 环境、需要 Tomcat 容器、需要 Linux 操作系统,那我们可以把我们所需要的一切都进行打包成一个整体(包括自己开发的 web 应用 + JDK + Tomcat + CentOS/Ubuntu + 各种配置文件)。打包后的镜像在某台机器上能运行,那它就能够在任何装有 Docker 的机器上运行。
任何镜像的创建会基于其他的父镜像,也就是说镜像是一层套一层,比如一个 Tomcat 镜像,需要运行在 CentOS/Ubuntu 上,那我们的 Tomcat 镜像就会基于 CentOS/Ubuntu 镜像创建(在后面的操作部分我们可以通过命令查看),这样的结构就类似于我们吃的洋葱。// 详见《Docker 下篇》
2.2 容器
Docker 利用容器(Container)独立运行的一个或一组应用。「容器」是用「镜像」创建的“运行实例”。
容器可以被启动、开始、停止、删除。每个容器都是相互隔离的、保证安全的平台。
上一步我们构建的镜像只是一个静态的文件,这个文件需要运行就需要变为容器,我们可以把容器看做是一个简易版的 Linux 系统(包括 root 用户权限、进程空间、用户空间和网络空间等)和运行在其中的应用程序!就是前面看到的鲸鱼背上的一个一个的集装箱,每个集装箱都是独立的!
容器的定义和镜像几乎一模一样,也是一堆叠层的统一视角,唯一区别在于容器的最上面那一层是可读可写的。
2.3 仓库
仓库(Repository)是集中存放“镜像文件”的场所。
仓库(Repository)和仓库注册服务器(Registry)是有区别的。仓库注册服务器上往往存放着多个仓库,每个仓库中又包含了多个镜像,每个镜像有不同的标签(tag)。
仓库分为公开仓库(Public)和私有仓库(Private)两种形式。
最大的公开仓库是 Docker Hub(https://hub.docker.com),存放了数量庞大的镜像供用户下载。国内的公开仓库包括阿里云 、网易云等。
2.4 小结
Docker 本身是一个容器运行载体或称之为“管理引擎”。
我们把应用程序和配置依赖打包好形成一个可交付的运行环境,这个打包好的运行环境就是 image 镜像文件。
只有通过这个镜像文件才能生成 Docker 容器。image 文件可以看作是容器的模板。Docker 根据 image 文件生成容器的实例。同一个 image 文件,可以生成多个同时运行的容器实例。
image 文件生成的容器实例,本身也是一个文件,称为“镜像文件”。
一个容器运行一种服务,当我们需要的时候,就可以通过 Docker 客户端创建一个对应的运行实例,也就是我们的容器。
至于仓储,就是放了一堆镜像的地方,我们可以把镜像发布到仓储中,需要的时候从仓储中拉下来就可以了。
结合 Docker 的 logo:鲸鱼背上有好多集装箱
- 蓝色大海 → 宿主机操作系统
- 鲸鱼 → Docker
- 集装箱 → 容器实例(From 镜像模板)
2.5 原理
Q1:Docker 是怎么工作的?
Docker 是一个 Client-Server 结构的系统,Docker 守护进程运行在主机上, 然后通过 Socket 连接从客户端访问,守护进程从客户端接受命令并管理运行在主机上的容器。 容器,是一个运行时环境,就是我们前面说到的集装箱。
Q2:为什么 Docker 比 VM 快?
VM(Virtual Machine):VM 其实就是一个虚拟的硬件环境,在使用 VM 的时候我们都需要在 VM 中装一个完整的 OS 然后再在 OS 中部署我们的 Application,图片中的 Hypervisor 就是 VM 的核心,它用于虚拟硬件环境,然后可以在 Hypervisor 之上安装多个 OS。
Docker 有着比虚拟机更少的抽象层。由于 Docker 不需要 Hypervisor 实现硬件资源虚拟化,运行在 Docker 容器上的程序直接使用的都是实际物理机的硬件资源。因此在 CPU、内存利用率上 Docker 将会在效率上有明显优势。
Docker 利用的是宿主机的内核,而不需要 Guest OS。因此,当新建一个容器时,Docker 不需要和虚拟机一样重新加载一个操作系统内核,从而避免引寻、加载操作系统内核返个比较费时费资源的过程。当新建一个虚拟机时,虚拟机软件需要加载 Guest OS,整个新建过程是分钟级别的。而 Docker 由于直接利用宿主机的操作系统,则省略了整个过程,因此新建一个 Docker 容器只需要几秒钟。
Docker 没有模拟整个硬件环境,它只需要在宿主主机中装 Docker Engine(Docker 引擎),然后就可以在 Image(镜像)创建多个 Container(容器),各个容器运行环境都是独立的。简单来说,每个 Container 都是一个独立的 OS,每个 Container 与宿主使用同一个内核,对进程与资源进行了隔离;Docker 也不是新技术,Docker 只是在 LXC(Linux Container)的基础上进行封装发展起来的。
3. Docker 安装
3.1 前提说明
目前,CentOS 仅发行版本中的内核支持 Docker。
- Docker 运行在 CentOS 7 上,要求系统为 64 位、系统内核版本为 3.10 以上。
- Docker 运行在 CentOS 6.5 或更高的版本的 CentOS 上,要求系统为 64 位、系统内核版本为 2.6.32-431 或者更高版本。
查看自己内核的命令:uname -r
3.2 安装步骤
- yum 安装 gcc 相关:
yum -y install gcc
,yum -y install gcc-c++
- 卸载旧版本:
yum -y remove docker docker-common docker-selinux docker-engine
- 安装需要的软件包:
yum install -y yum-utils device-mapper-persistent-data lvm2
- 设置 stable 镜像仓库:
yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
- 更新 yum 软件包索引:
yum makecache fast
- 安装 Docker 社区版:
yum -y install docker-ce
- 启动 Docker:
systemctl start docker
- 测试:
docker version
,docker run hello-world
3.3 卸载步骤
systemctl stop docker
yum -y remove docker-ce
rm -rf /var/lib/docker
3.4 镜像加速
鉴于国内网络问题,后续拉取 Docker 镜像十分缓慢,我们可以需要配置加速器来解决,我使用的是阿里云的镜像加速,每个阿里云账户有一个属于自己的镜像地址。
- 递归创建目录:
mkdir -p /etc/docker
- 创建上图蓝框标出的文件:
vim /etc/docker/daemon.json
,将红框标出的部分复制到该文件中。 - 重新加载 Docker 守护进程:
systemctl daemon-reload
- 重启 Docker 服务:
systemctl restart docker
3.5 HelloWorld
docker run hello-world
4. Docker 常用命令
4.1 帮助命令
docker version
docker info
docker --help
4.2 镜像命令
4.2.1 docker images
字段说明:
- REPOSITORY:表示镜像的仓库源
- TAG:镜像的标签
- IMAGE ID:镜像 ID
- CREATED:镜像创建时间
- SIZE:镜像大小
同一仓库源可以有多个 TAG,代表这个仓库源的不同个版本,我们使用 REPOSITORY:TAG
来定义不同的镜像。如果你不指定一个镜像的版本标签,例如你只使用 Ubuntu,Docker 将默认使用 Ubuntu:latest
镜像。
OPTIONS 说明:
-a
列出本地所有的镜像(含中间映像层)-q
只显示镜像 ID--digests
显示镜像的摘要信息--no-trunc
显示完整的镜像信息
4.2.2 docker search
docker search [OPTIONS] <镜像名字>
OPTIONS 说明:
--no-trunc # 显示完整的镜像描述
-s N # 列出收藏数不小于指定值 N 的镜像
--automated # 只列出 automated build 类型的镜像
搜索 tomcat 镜像:
4.2.3 docker pull
docker pull <镜像名字[:TAG]>
没写版本号,则默认拉的是最新版本,即:docker pull <镜像名字>:latest
。
4.2.4 docker rmi
- 删除单个:
docker rmi -f <镜像名字ID>
- 删除多个:
docker rmi -f <镜像名1:TAG> <镜像名2:TAG> ...
- 删除全部:
docker rmi -f $(docker images -qa)
// 组合命令
4.3 容器命令
4.3.1 新建&启动/退出
docker run [OPTIONS] <镜像名/镜像ID> [COMMAND] [ARG...]
OPTIONS 说明(有些是一个减号,有些是两个减号):
--name="容器新名字"
为容器指定一个名称;-d
后台运行容器,并返回容器 ID,也即启动“守护式”容器;-i
以“交互式”运行容器,通常与-t
同时使用;-t
为容器重新分配一个伪输入终端,通常与-i
同时使用;-P
随机端口映射;-p
指定端口映射,有以下 4 种格式ip:hostPort:containerPort
ip::containerPort
hostPort:containerPort
containerPort
(1)创建并以“交互方式”启动 CentOS 容器
(2)启动守护式容器,以 CentOS 为例 docker run -d centos
,然后通过 docker ps -a
进行查看,会发现容器已经退出?
Docker 容器后台运行,就必须有一个前台进程。容器运行的命令如果不是那些一直挂起的命令(比如运行 top,tail),就是会自动退出的。
这个是 Docker 的机制问题,以 Web 容器 —— Nginx 为例,正常情况下,我们配置启动服务只需要启动响应的 Service 即可。例如:systemctl start nginx
。但是这样做,Nginx 为后台进程模式运行,就导致 Docker 前台没有运行的应用,这样的容器后台启动后,会立即自杀,因为它觉得没事可做了。
可添加 -t
标志将阻止容器在后台运行时退出:docker run -t -d <image> <command>
。 然后,可以使用 docker exec -i -t 容器ID /bin/bash
进入 shell 提示符。
(3)退出容器
exit
容器停止退出CTRL + P + Q
容器不停止退出
使用 exit
退出:
4.3.2 查询容器列表
docker ps [OPTIONS]
OPTIONS 说明:
-a
列出当前所有正在运行的容器 + 历史上运行过的-l
显示最近创建的容器-n
显示最近 n 个创建的容器-q
静默模式,只显示容器编号--no-trunc
不截断输出
4.3.3 进入正在运行的容器并以命令行交互
使用“退出容器方式二:CTRL + P + Q” 退出后,再进入:
[方式一] docker exec -it <容器ID> <bashShell>
[方式二] docker attach <容器ID>
二者区别:attach
直接进入容器启动命令的终端,不会启动新的进程;exec
是在容器中打开新的终端,并且可以启动新的进程。
4.3.4 启动/重启/停止容器
docker start <容器ID/容器名>
docker restart <容器ID/容器名>
docker stop <容器ID/容器名>
docker kill <容器ID/容器名>
docker run
后面指定的是一个镜像,而 docker start
指定的是一个容器。
docker start
是启动一个之前生成过的容器,而 docker run
相当于执行了两步操作:将镜像放入容器中(docker create),然后将容器启动,使之变成运行时容器(docker start)。
4.3.5 删除已停止的容器
- 删除一个:
docker rm <容器ID/容器名>
// 删除正在运行的容器要加-f
强制删除 - 删除多个:
docker rm -f $(docker ps -a -q)
|docker ps -a -q | xargs docker rm
4.3.5 容器操作
(1)查看容器日志
docker logs [-f -t --tail N] <容器ID>
-t 加入时间戳
-f 追加最新的日志打印
--tail N 显示最后多少条
(2)查看容器内运行的进程
docker top <容器ID>
(3)查看容器内部细节
docker inspect <容器ID>
(4)从容器内拷贝文件到主机上
docker cp <容器ID:容器内路径> <目的主机路径>
attach Attach to a running container
# 当前 shell 下 attach 连接指定运行镜像
build Build an image from a Dockerfile
# 通过 Dockerfile 定制镜像
commit Create a new image from a container changes
# 提交当前容器为新的镜像
cp Copy files/folders from the containers filesystem to the host path
#从容器中拷贝指定文件或者目录到宿主机中
create Create a new container
# 创建一个新的容器,同 run,但不启动容器
diff Inspect changes on a container's filesystem
# 查看 docker 容器变化
events Get real time events from the server
# 从 docker 服务获取容器实时事件
exec Run a command in an existing container
# 在已存在的容器上运行命令
export Stream the contents of a container as a tar archive
# 导出容器的内容流作为一个 tar 归档文件[对应 import ]
history Show the history of an image
# 展示一个镜像形成历史
images List images
# 列出系统当前镜像
import Create a new filesystem image from the contents of a tarball
# 从tar包中的内容创建一个新的文件系统映像[对应export]
info Display system-wide information
# 显示系统相关信息
inspect Return low-level information on a container
# 查看容器详细信息
kill Kill a running container
# kill 指定 docker 容器
load Load an image from a tar archive
# 从一个 tar 包中加载一个镜像[对应 save]
login Register or Login to the docker registry server
# 注册或者登陆一个 docker 源服务器
logout Log out from a Docker registry server
# 从当前 Docker registry 退出
logs Fetch the logs of a container
# 输出当前容器日志信息
port Lookup the public-facing port which is NAT-ed to PRIVATE_PORT
# 查看映射端口对应的容器内部源端口
pause Pause all processes within a container
# 暂停容器
ps List containers
# 列出容器列表
pull Pull an image or a repository from the docker registry server
# 从docker镜像源服务器拉取指定镜像或者库镜像
push Push an image or a repository to the docker registry server
# 推送指定镜像或者库镜像至docker源服务器
restart Restart a running container
# 重启运行的容器
rm Remove one or more containers
# 移除一个或者多个容器
rmi Remove one or more images
# 移除一个或多个镜像[无容器使用该镜像才可删除,否则需删除相关容器才可继续或 -f 强制删除]
run Run a command in a new container
# 创建一个新的容器并运行一个命令
save Save an image to a tar archive
# 保存一个镜像为一个 tar 包[对应 load]
search Search for an image on the Docker Hub
# 在 docker hub 中搜索镜像
start Start a stopped containers
# 启动容器
stop Stop a running containers
# 停止容器
tag Tag an image into a repository
# 给源中镜像打标签
top Lookup the running processes of a container
# 查看容器中运行的进程信息
unpause Unpause a paused container
# 取消暂停容器
version Show the docker version information
# 查看 docker 版本号
wait Block until a container stops, then print its exit code
# 截取容器停止时的退出状态值