我们先看看谷歌热度上Docker关键字的趋势,从2014年开始,Docker 热度持续不减。
https://trends.google.com/trends/explore?date=all&geo=CN&q=Docker
1. Docker的场景
1.1 模板配置
Docker 提供了一个通用配置文件dockerfile。在初次配置成功后,它可以作为一个模板文件增加不同应用的相关文件。
随着应用逐渐增多,研发人员只需维护好与之对应的 Docker 配置文件即可,而这个配置文件则固定的分为几个标签,每个标签标识docker的一些属性,同时存储了Docker 运行、启动的命令。
1.2 CI/CD
Docker 提供了一组应用打包构建、传输及部署的方法和框架,用户只需要关注自己的逻辑功能,框架会自动被触发构建和运行。Docker 还提供了跨越这些异构环境以满足一致性的微环境——从开发到部署、再到流畅发布,同时提高资源的配置和隔离方案。
目前通用的实践就是和Jenkins、GitLab 等相关工作流平台串联起来,融入项目开发的CI/CD(持续集成与持续发布)流程中,让一键部署成为可能。
1.3 DevOps节点运行能力
在传统软件开发过程中,开发、测试和运维是不同的软件开发的流程,甚至不是一个团队成员。对于互联网公司而言,由于业务迭代非常的快,需要通过“小步快跑”的方式进行敏捷开发,以此来满足用户差异化的需求、应对竞争对手的产品策略。如果在周级别的迭代过程中需要管理环境、人员调配,资源评估,那将是非常糟糕的。因此,在极端情况下,如:每周发布多次甚至每天发布多次的场景,高效的团队协作就显得尤为重要。
DevOps 正式在这种场景下应运而生,它打破了开发人员和运维人员之间的壁垒,通过“节点服务”组成工作流,串联项目完整生命周期,涵盖了研发、构建、测试、发布、监控及反馈等流程,从而促进了软件的一致性和标准化,支持节点可重入,可回滚,可灰度发布的能力,严格管控传统低效的开发流程,让这一切变得更智能化。
标准的“节点服务”依赖一些隔离的执行环境:既要快速启停、又要服务稳定执行、还要支持高并发调度,这一切都可以通过 Docker 来轻松实现。
1.4 自动化测试
测试是软件开发过程中非常关键的一环,直接影响产品的质量。测试工程师每天都需要完成大量的测试任务,手动执行测试会耗费大量的时间,这时考虑使用 Docker 进行自动化改造。自动化的成本是首次自动化程序的编写和维护,而收益则是解放人力、提高生产力。
测试人员在进行一些功能测试、性能测试以及UI测试时,需要快速搭建不同的运行环境、掌握Docker技术,可以让测试人员如虎添翼。
1.5 应用服务隔离
隔离是当今微服务发展与部署的核心考核点,服务之间的资源刚性交付和保证是服务上线后稳定运行的基本。例如,服务器上混部了两个服务:
- Node服务,用来启动Web应用服务;
- Java服务,用来提供前后端分离的API接口和业务逻辑。
如果服务混部就可能出现这种情况——两个服务争夺服务器的CPU资源,无论哪一方失败都将造成灾难。如下图所示,服务器的CPU资源已经被Java服务占满,此时node服务就会饥饿等待。
在资源有限的前提下,操作系统没法同一时间满足多个应用进程的突发流量使用。当然,在这种场景下可以通过独立部署、为虚拟机设定资源优先级等方案解决,但是这个扩容能力和效率会比较低,同时资源也不好准确评估。
但在 Docker中,这些完全没有必要担心,因为Docker提供了进程级的隔离,可以更加精细地设置CPU和内存的使用率,进而更好地利用服务器的资源。
1.6 弹性扩缩容
在生产实践中,业务不可避免的产生突发流量,比如双11的整点抢购、游戏服务的活动大礼包开放、节假日的各种优惠活动等都会带来流量的波动。在大促或流量不均的场景,服务器的自动扩容/缩容就会很关键。
在流量低谷时进行自动缩容,可以大幅度减少服务器成本。在峰值来临时,通过可观测性预测自动触发服务器扩容操作,从而保证服务器稳定运行。
在传统的虚拟机或物理机的实战中,这些操作显得非常的繁琐和低效。而在Docker中却非常灵活并且高效,因为,每个容器都可作为单独的进程运行,并且可以共享底层操作系统的系统资源。这样可以提高容器的启动和停止效率,扩容也就毫不费力了。
1.7 节省成本
成本优化也是很多企业使用Docker的原因之一。传统企业一般会使用虚拟机,虚拟机虽然可以隔离出很多“子系统”,但占用的空间更大,启动更慢。在线和离线服务没有混部充分利用服务器资源。
Docker技术不需要虚拟出整个操作系统,只需要虚拟出一个小规模的环境,与“沙箱”类似,达到开箱即用的效果。
此外,虚拟机一般要占用很大的存储空间(可以达到数十GB);而容器只需要占用很小的存储空间(最小的仅为几KB),这样就能节省出更多的服务器资源,从根本上节省成本。
可以看出,Docker已经渗透到了日常开发的方方面面。
2. Docker技术体系
2.1 Docker架构
我们看如下图:
Docker引擎(Engine)结构
docker架构
docker架构主要包含:
Docker Client:docker client 是docker架构中用户用来和docker daemon建立通信的客户端,用户使用的可执行文件为docker,通过docker命令行工具可以发起众多管理container的请求。docker client发送容器管理请求后,由docker daemon接受并处理请求,当docker client 接收到返回的请求相应并简单处理后,docker client 一次完整的生命周期就结束了,当需要继续发送容器管理请求时,用户必须再次通过docker可以执行文件创建docker client。
Docker Server:在Docker的启动过程中,通过包gorilla/mux(golang的类库解析),创建了一个mux.Router,提供请求的路由功能。在Golang中,gorilla/mux是一个强大的URL路由器以及调度分发器。该mux.Router中添加了众多的路由项,每一个路由项由HTTP请求方法(PUT、POST、GET或DELETE)、URL、Handler三部分组成。
- Daemon:docker daemon 是docker架构中一个常驻在后台的系统进程,功能是:接收处理docker client发送的请求。该守护进程在后台启动一个server,server负载接受docker client发送的请求;接受请求后,server通过路由与分发调度,找到相应的handler来执行请求。docker daemon启动所使用的可执行文件也为docker,与docker client启动所使用的可执行文件docker相同,在docker命令执行时,通过传入的参数来判别docker daemon与docker client。
- Containerd:Docker 引擎中的 Containerd 组件确保了 Docker 镜像能够以正确的 OCI Bundle 的格式传递给 Runc;
- Runc:实质上是一个轻量级的、针对 Libcontainer 进行了包装的命令行交互工具,可以理解为一个独立的容器运行时工具。作用只有一个——创建容器。
Docker Driver:
- Execdrive:存储了容器定义的配置信息了,Libcontainer 拿到这些配置信息后,将会调用底层的 Namespace 和 Cgroup 等技术来完成容器的创建和管理;
- Networkdirver:主要作用是完成 Docker 容器的网络环境配置,包括容器的 IP 地址、端口、防火墙策略,以及与主机的端口映射等;
- Graphdriver:主要负责对容器镜像的管理。
Linux Kernel:
- Chroot:即 change root directory (主要用来更改 root 目录)。在 Linux 系统中,系统默认的目录结构都是以 /,即以根 (root) 开始的。而在使用 Chroot 之后,系统的目录结构将以指定的位置作为 / 位置;
- Namespace:是 Linux 内核用来隔离内核资源的方式,目的就是实现轻量级虚拟化(容器)服务,为 Docker 等容器技术提供了基础条件;
- Cgroup:是 Linux 内核提供的一种可以限制单个进程或者多个进程所使用资源的机制,可以对 CPU、内存等资源实现精细化的控制,Docker 就使用了 Cgroup提供的资源限制能力来完成 CPU,内存等部分的资源控制;
- Network:为 Docker 容器提供多种网络解决方案;
- Capability:Linux 引入了 Capabilities 机制对 root 权限进行细粒度的控制,实现按需授权,从而减小系统的安全攻击面;
- Seccomp:即安全计算模式,可以使用它来限制容器内可用的操作。seccomp()系统调用在调用进程的seccomp状态下运行。Docker 中使用此功能来限制应用程序的访问;
- Filesystem:UnionFS 通过 Linux Filesystem 为容器提供高效的管理能力。Docker 的镜像就采用了 UnionFS 技术,从而实现了分层的镜像。
Docker的生命周期:
2.2 Docker 周边生态
如果需要搭建“企业级容器化标准”,那么一定要对 Docker 周边生态有足够的了解,不妨一起从下图中感受整个技术体系。
从生态图中我们看到,Docker涉及到很多工具或平台。但万变不离其宗,“企业容器化标准”可以归纳以下几方面:
- 服务发现:Etcd、ZooKeeper、Consul、Eureka、istio
- 负载均衡:Nginx、F5、LVS、DNS
- 日志管理:两种模式Daemonset集中式和Sidecar自定义,ELK
- 链路追踪:Pinpoint、Jaeger 、SkyWalking、Zipkin、SkyWalking
- 容器编排:Swarm、Kubernetes、Mesos、consul
- 数据存储:Redis、MySQL、MongoDB、Nebula
- 弹性部署:定时扩容、手动扩容、自动扩容
- 网络选型:Flannel、Calico、Weave、自研
- 存储系统:PV/PVC声明文件、StorageClass动态供给
- 部署发布:Jenkins、GitLab、Registry
- 服务监控:Zabbix、Nagios、cAdvisor、Prometheus
- 集群可靠性:资源预留、集群预留、集群组件(API Server、Etcd集群方案、kube-scheduler与controller-manager)
3. Dockerfile
我们可以使用Dockfile构建一个镜像,然后直接在docker中运行。Dockerfile文件为一个文本文件,里面包含构建镜像所需的所有的命令,首先我们来认识一下Dockerfile文件中几个重要的指令。
3.1 指令详解
FROM
基础镜像,然后在基础镜像上进行修改
RUN
RUN指令用来执行命令行命令的。它有以下两种格式:
- shell 格式:RUN <命令>,就像直接在命令行中输入的命令一样。RUN echo <h1>Hello, Docker!</h1> > /usr/share/nginx/html/index.html
- exec 格式:RUN [“可执行文件”, “参数1”, “参数2”],这更像是函数调用中的格式。
CMD
此指令就是用于指定默认的容器主进程的启动命令的。CMD指令格式和RUN相似,也是两种格式
- shell 格式:CMD <命令>
- exec 格式:CMD [“可执行文件”, “参数1”, “参数2″…]
- 参数列表格式:CMD [“参数1”, “参数2″…]。在指定了 ENTRYPOINT 指令后,用 CMD 指定具体的参数。
ENTRYPOINT
ENTRYPOINT 的格式和RUN指令格式一样,分为 exec 格式和 shell 格式。 ENTRYPOINT 的目的和 CMD 一样,都是在指定容器启动程序及参数。ENTRYPOINT 在运行时也可以替代,不过比 CMD 要略显繁琐,需要通过 docker run 的参数 –entrypoint 来指定。当指定了 ENTRYPOINT 后,CMD 的含义就发生了改变,不再是直接的运行其命令,而是将 CMD 的内容作为参数传给 ENTRYPOINT指令,换句话说实际执行时,将变为:
<ENTRYPOINT> "<CMD>"
COPY & ADD
这2个指令都是复制文件,它将从构建上下文目录中 <源路径> 的文件/目录 复制到新的一层的镜像内的 <目标路径> 位置。ADD指令比COPY高级点,可以指定一个URL地址,这样Docker引擎会去下载这个URL的文件,如果ADD后面是一个tar文件的话,Dokcer引擎还会去解压缩。
EXPOSE
声明容器运行时的端口,这只是一个声明,在运行时并不会因为这个声明应用就会开启这个端口的服务。在 Dockerfile 中写入这样的声明有两个好处,一个是帮助镜像使用者理解这个镜像服务的守护端口,以方便配置映射;另一个用处则是在运行时使用随机端口映射时,也就是 docker run -P 时,会自动随机映射 EXPOSE 的端口。要将 EXPOSE 和在运行时使用-p <宿主端口>:<容器端口> 区分开来。-p是映射宿主端口和容器端口,换句话说,就是将容器的对应端口服务公开给外界访问,而 EXPOSE 仅仅是声明容器打算使用什么端口而已,并不会自动在宿主进行端口映射。
ENV
这个指令很简单,就是设置环境变量,无论是后面的其它指令,如 RUN,还是运行时的应用,都可以直接使用这里定义的环境变量。它有如下两种格式:
- ENV <key> <value>
- ENV <key1>=<value1> <key2>=<value2>…
VOLUME
该指令使容器中的一个目录具有持久化存储的功能,该目录可被容器本身使用,也可共享给其他容器。当容器中的应用有持久化数据的需求时可以在Dockerfile中使用该指令。如VOLUME /tmp这里的 /tmp 目录就会在运行时自动挂载为匿名卷,任何向 /tmp 中写入的信息都不会记录进容器存储层,从而保证了容器存储层的无状态化。当然,运行时可以覆盖这个挂载设置。比如:docker run -d -v mydata:/tmp xxxx
LABEL
镜像添加labels,用来组织镜像,记录版本描述,或者其他原因,对应每个label,增加以LABEL开头的行,和一个或者多个键值对。如下所示:
LABEL version="1.0"
LABEL description="test"
4. Docker的常见操作
启动、重启和开机docker自启动systemctl enable docker
# Docker的启动
systemctl start docker
# Docker的重启
systemctl restart docker
# Docker的开机自启动
systemctl enable docker # 一般我们使用开机自启动的形式
4.1 镜像的基本操作
- 使用search命令来检索中央仓库中收录的镜像,这里以nginx为例
# 检索镜像:docker search [镜像名称]
docker search nginx
- 拉取(下载)镜像:docker pull nginx(默认最新版本),如果需要其他版本可在中央仓库中查阅
# 拉取镜像:docker pull [镜像名称]
docker pull nginx
# 默认拉取的是最新版本,如果需要特定版本,在后面指定即可,nginx7.0.1为例
docker pull nginx:7.0.1
- 查看已经下载的本地镜像:
# 查看已经下载的本地镜像
docker images
删除本地镜像
# 删除本地镜像: docker rmi 镜像名称/IMAGE ID
docker rmi nginx
4.2 容器的基本操作
- 根据镜像启动对应的容器
# 根据镜像启动对应的容器
docker run -d --name mynginx nginx
# --name 对容器起一个别名
# -d 对指定的容器进行后台运行
- 停止运行的容器
# 停止运行的容器:docker stop 容器名称/CONTAINER ID
docker stop mynginx
- 查看正在运行的容器
docker ps # 查看正在运行的容器
docker ps -a # 查看本地所有的容器
- 删除容器
# 注:删除容器是使用rm,删除镜像是rmi,且删除镜像之前需要停止运行容器并删除
docker rm mynginx
- 启动一个做了端口映射的容器,在之前创建容器之后,我们无法通过ip:端口的形式来访问Docker中所开启的服务,因为每一个容器他都是独立,所以要想访问,我们则需要通过端口的映射来访问容器。
docker run -d --name mynginx -p 8888:8080 nginx
# --name:对容器起一个别名
# -p:将主机的端口映射到容器的一个端口 主机端口:容器内部的端口
# -d:后台运行
- 查看容器的日志docker logs mytomcat
- 容器开机自起动:
docker update mynginx --restart=always
- 进入对应的容器
docker exec -it mynginx /bin/bash
- 本地文件(是centos不是windows)与docker容器中文件之间的互传,以将ik分词器插件上传至elasticsearch容器为例:
# 先将windows上的文件使用xftp上传到vmware linux中,然后将文件使用docker命令上传到docker容器中
# docker cp 本地路径 容器名:容器路径
docker cp ./elasticsearch-analysis-ik-6.5.4.zip elasticsearch:/usr/share/elasticsearch/plugins
- 文件的挂载
Docker容器是独立,且其相当于是一个及其精简版的Linux,在我们通过exec命令之后,我们是无法使用vim、vi等命令来对其内部文件进行编辑,在一般情况下我们在创建好容器之后一般会对其配置文件进行编辑,此时我们可以使用Docker中的挂载来将容器内文件挂载到宿主机中。当我们在宿主机中对挂载的文件进行编辑的时候,容器中所被挂载的文件也会做出相应的修改,下面就是docker挂载文件的-v操作(以挂载Es的配置文件和数据文件为例):
mkdir -p ./resources/elasticsearch/config
mkdir -p ./resources/elasticsearch/data
docker run --name elasticsearch -p 9200:9200
-e "discovery.type=single-node"
-e ES_JAVA_OPTS="-Xms256m -Xmx256m"
-v /resources/elasticsearch/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml
-v /resources/elasticsearch/data:/usr/share/elasticsearch/data -d elasticsearch:5.6.8
4.3 镜像、容器的导入和导出
export:可将docker容器通过export导出为tar文件
docker export mynginx > mynginx.tar
import:基于tar文件来创建一个新的镜像
docker import - mynginx < mynginx.tar
注:以下表格总结了Docker容器中常用的一些命令。
api |
描述 |
docker logon |
登录到docker registry |
docker logout |
从docker registry退出登录 |
docker pull |
从docker registry中拉取docker镜像 |
docker image ls |
列出本地已经下载的镜像列表 |
docker image rm |
删除本地镜像 |
docker push |
将本地镜像推送的docker registry |
docker attach |
将本地标准的输入,输出和错误流关联到一个正在运行的容器 |
docker build |
使用dockerfile构建一个docker镜像(image) |
docker cp |
实现容器与本地文件系统之间文件跟目录的拷贝 |
docker commit |
将对容器修改部分添加到docker镜像中并创建新的docker镜像 |
docker config |
管理docker配置文件 |
docker create |
创建一个新的容器 |
docker start |
启动某个已经stopped的容器 |
docker run |
启动一个新的容器并执行某个command |
docker exec |
在某一个正在运行的docker容器中执行某个command |
docker rm |
移除某容器 |
docker restart |
重启某个docker容器 |
docker pause |
暂停某个容器中的所有进程 |
docker unpause |
将容器中已暂停的所有进程恢复执行状态 |
docker stop |
停止某个docker容器 |
docker kill |
杀掉某个正在运行的容器 |
docker info |
显示系统层面的信息 |
docker logs |
抓取docker容器中的所有日志 |