张登友,张登友的博客,张登友的网站——
Docker的发展
Docker 使用 Google 公司推出的 Go 语言 进行开发实现,基于 Linux 内核的 cgroup,namespace,以及 OverlayFS类的 Union FS 等技术,对进程进行封装隔离,属于 操作系统层面的虚拟化技术。由于隔离的进程独立于宿主和其它的隔离的进程,因此也称其为容器。最初实现是基于 LXC,从 0.7 版本以后开始去除 LXC,转而使用自行开发的 libcontainer,从 1.11 版本开始,则进一步演进为使用 runC 和 containerd。
Docker和虚拟机的区别
Docker 和传统虚拟化方式的不同之处。传统虚拟机技术是虚拟出一套硬件后,在其上运行一个完整操作系统,在该系统上再运行所需应用进程;而容器内的应用进程直接运行于宿主的内核,容器内没有自己的内核,而且也没有进行硬件虚拟。因此容器要比传统虚拟机更为轻便。
Docker的优势
作为一种新兴的虚拟化方式,Docker 跟传统的虚拟化方式相比具有众多的优势。
一、更高效的利用系统资源
由于容器不需要进行硬件虚拟以及运行完整操作系统等额外开销,Docker 对系统资源的利用率更高。无论是应用执行速度、内存损耗或者文件存储速度,都要比传统虚拟机技术更高效。因此,相比虚拟机技术,一个相同配置的主机,往往可以运行更多数量的应用。
二、更快速的启动时间
传统的虚拟机技术启动应用服务往往需要数分钟,而 Docker 容器应用,由于直接运行于宿主内核,无需启动完整的操作系统,因此可以做到秒级、甚至毫秒级的启动时间。大大的节约了开发、测试、部署的时间。
三、一致的运行环境
开发过程中一个常见的问题是环境一致性问题。由于开发环境、测试环境、生产环境不一致,导致有些 bug 并未在开发过程中被发现。而 Docker 的镜像提供了除内核外完整的运行时环境,确保了应用运行环境一致性,从而不会再出现 「这段代码在我机器上没问题啊」 这类问题。
四、持续交付和部署
对开发和运维人员来说,最希望的就是一次创建或配置,可以在任意地方正常运行。
使用 Docker 可以通过定制应用镜像来实现持续集成、持续交付、部署。开发人员可以通过 Dockerfile 来进行镜像构建,并结合 持续集成(Continuous Integration) 系统进行集成测试,而运维人员则可以直接在生产环境中快速部署该镜像,甚至结合 持续部署(Continuous Delivery/Deployment) 系统进行自动部署。
而且使用 Dockerfile 使镜像构建透明化,不仅仅开发团队可以理解应用运行环境,也方便运维团队理解应用运行所需条件,帮助更好的生产环境中部署该镜像。
五、更轻松的迁移
由于 Docker 确保了执行环境的一致性,使得应用的迁移更加容易。Docker 可以在很多平台上运行,无论是物理机、虚拟机、公有云、私有云,甚至是笔记本,其运行结果是一致的。因此用户可以很轻易的将在一个平台上运行的应用,迁移到另一个平台上,而不用担心运行环境的变化导致应用无法正常运行的情况。
六、更轻松的维护和扩展
Docker 使用的分层存储以及镜像的技术,使得应用重复部分的复用更为容易,也使得应用的维护更新更加简单,基于基础镜像进一步扩展镜像也变得非常简单。此外,Docker 团队同各个开源项目团队一起维护了一大批高质量的 官方镜像,既可以直接在生产环境使用,又可以作为基础进一步定制,大大的降低了应用服务的镜像制作成本。
对比传统虚拟机表格
特性 | 容器 | 虚拟机 |
---|---|---|
启动 | 秒级 | 分钟级 |
硬盘使用 | 一般为MB |
一般为GB |
性能 | 接近原生 | 弱于原生 |
系统支持量 | 支持上千个 | 一般几十个 |
Docker 包括三个基本概念
- 镜像(
Image
) - 容器(
Container
) - 仓库(
Repository
)
镜像
操作系统分为 内核 和 用户空间。对于 Linux 而言,内核启动后,会挂载 root 文件系统为其提供用户空间支持。而 Docker 镜像(Image
),就相当于是一个 root 文件系统。比如官方镜像 ubuntu:20.04 就包含了完整的一套 Ubuntu 20.04 最小系统的 root 文件系统。
Docker 镜像 是一个特殊的文件系统,除了提供容器运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的一些配置参数(如匿名卷、环境变量、用户等)。镜像 不包含任何动态数据,其内容在构建之后也不会被改变。
容器
容器可以被创建、启动、停止、删除、暂停等。
容器的实质是进程,但与直接在宿主执行的进程不同,容器进程运行于属于自己的独立的命名空间。因此容器可以拥有自己的 root 文件系统、自己的网络配置、自己的进程空间,甚至自己的用户 ID 空间。容器内的进程是运行在一个隔离的环境里,使用起来,就好像是在一个独立于宿主的系统下操作一样。这种特性使得容器封装的应用比直接在宿主运行更加安全。
镜像使用的是分层存储,容器也是如此。每一个容器运行时,是以镜像为基础层,在其上创建一个当前容器的存储层,我们可以称这个为容器运行时读写而准备的存储层为 容器存储层。
按照 Docker 最佳实践的要求,容器不应该向其存储层内写入任何数据,容器存储层要保持无状态化。所有的文件写入操作,都应该使用 数据卷(Volume)、或者 绑定宿主目录,在这些位置的读写会跳过容器存储层,直接对宿主(或网络存储)发生读写,其性能和稳定性更高。
数据卷的生存周期独立于容器,容器消亡,数据卷不会消亡。因此,使用数据卷后,容器删除或者重新运行之后,数据却不会丢失。
仓库
镜像构建完成后,可以很容易的在当前宿主机上运行,但是,如果需要在其它服务器上使用这个镜像,我们就需要一个集中的存储、分发镜像的服务,Docker Registry 就是这样的服务。
一个 Docker Registry 中可以包含多个 仓库(Repository
);每个仓库可以包含多个 标签(Tag
);每个标签对应一个镜像。
通常,一个仓库会包含同一个软件不同版本的镜像,而标签就常用于对应该软件的各个版本。我们可以通过 <仓库名>:<标签>
的格式来指定具体是这个软件哪个版本的镜像。如果不给出标签,将以 latest
作为默认标签。
以 Ubuntu 镜像 为例,ubuntu 是仓库的名字,其内包含有不同的版本标签,如,16.04, 18.04。我们可以通过 ubuntu:16.04,或者 ubuntu:18.04 来具体指定所需哪个版本的镜像。如果忽略了标签,比如 ubuntu,那将视为 ubuntu:latest。
私有仓库(Docker Registry)
除了使用公开服务外,用户还可以在本地搭建私有 Docker Registry。Docker 官方提供了 Docker Registry 镜像,可以直接使用做为私有 Registry 服务。
开源的 Docker Registry 镜像只提供了 Docker Registry API 的服务端实现,足以支持 docker 命令,不影响使用。但不包含图形界面,以及镜像维护、用户管理、访问控制等高级功能。
除了官方的 Docker Registry 外,还有第三方软件实现了 Docker Registry API,甚至提供了用户界面以及一些高级功能。比如,Harbor 和 Sonatype Nexus。
安装Docker
安装命令
curl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun
也可以使用国内 daocloud 一键安装命令:
curl -sSL https://get.daocloud.io/docker | sh
测试系统为Debian10.5,其他系统可参考菜鸟教程
使用镜像
获取镜像
docker pull 选项 Docker Registry 地址:端口号/仓库名:标签
例如:
docker pull ubuntu
运行镜像
docker run -it ubuntu bash
-it:这是两个参数,一个是 -i
:交互式操作,一个是 -t
终端。我们这里打算进入 bash 执行一些命令并查看返回结果,因此我们需要交互式终端。
ubuntu:这是指用 ubuntu 镜像为基础来启动容器。如果需要使用特定版本,可以在后面跟上:版本号
,比如ubuntu:18.04
bash:放在镜像名后的是 命令,这里我们希望有个交互式 Shell,因此用的是 bash。
列出镜像
docker image ls
通过 docker system df
命令来便捷的查看镜像、容器、数据卷所占用的空间。
构建镜像
这里我使用Dockerfile构建
在项目根目录创建一个名为Dockerfile的文件并添加以下内容
# 基于node镜像为基础进行构建
FROM node:latest
# 创建运行目录app
WORKDIR /app
ADD . /app/
COPY . .
# 运行命令安装依赖
RUN npm install
# 暴露端口
ENV HOST 0.0.0.0
EXPOSE 6000
# 编译文件
RUN npm run build
# 运行命令
CMD ["npm", "run", "start"]
之后我们在项目根目录执行
docker build -t 项目名称 .
虚悬镜像
镜像列表中可能会有一个特殊的镜像,这个镜像既没有仓库名,也没有标签,均为 <none>
,这类无标签镜像也被称为 **虚悬镜像(dangling image)**。出现情况:
docker pull
可能导致这种情况,docker build
也同样可以导致这种现象。
原因: 随着官方镜像维护,发布了新版本后,重新 docker pull 时,镜像名被转移到了新下载的镜像身上,而旧的镜像上的这个名称则被取消,由于新旧镜像同名,旧镜像名称被取消,从而出现仓库名、标签均为
docker image ls -f dangling=true #查看虚悬镜像命令
docker image prune #删除虚悬镜像命令
删除镜像
docker image rm 941748a982d4 #最后一位是镜像ID, 通过列出镜像获取
操作容器
新建并启动容器
所需要的命令主要为 docker run
docker run -i -t ubuntu:20.04 /bin/bash
docker run -it ubuntu /bin/bash #可以简写-it
docker run -it ubuntu bash #可以省略交互终端的路径
docker run -d ubuntu:20.04 /bin/sh # 创建一个以进程方式运行的容器
参数解释:
- docker: Docker 的二进制执行文件。
- run: 与前面的 docker 组合来运行一个容器。
- -t: 在新容器内指定一个伪终端或终端。
- -i: 允许你对容器内的标准输入 (STDIN) 进行交互。
- -it:这是两个参数,一个是
-i
:交互式操作,一个是 -t 终端。我们这里打算进入bash
执行一些命令并查看返回结果,因此我们需要交互式终端。 - -d: 以进程方式运行的容器(后台运行)
- –rm:这个参数是说容器退出后随之将其删除。默认情况下,为了排障需求,退出的容器并不会立即删除,除非手动 docker rm。我们这里只是随便执行个命令,看看结果,不需要排障和保留结果,因此使用 –rm 可以避免浪费空间。
- ubuntu: 这是指用 ubuntu 镜像为基础来启动容器。
- bash:放在镜像名后的是 命令,这里我们希望有个交互式 Shell,因此用的是 bash。
列出容器
docker ps
查看正在运行的容器
docker ps -a
查看所有容器
输出详情介绍:
CONTAINER ID: 容器 ID。
IMAGE: 使用的镜像。
COMMAND: 启动容器时运行的命令。
CREATED: 容器的创建时间。
STATUS: 容器状态。状态有7种:
created(已创建)
restarting(重启中)
running 或 Up(运行中)
removing(迁移中)
paused(暂停)
exited(停止)
dead(死亡)
PORTS: 容器的端口信息和使用的连接类型(tcp\udp)。
NAMES: 自动分配的容器名称。
在宿主主机内使用 docker logs 命令,查看容器内的标准输出:
docker logs 2b1b7a428627
查看容器信息: docker container ls
启动已终止的容器
docker container start 命令来重新启动
docker container start 容器ID
停止容器
使用 docker container stop 命令来停止容器:
docker container stop 应用名或者ID
docker container ls -a #查看所有容器
进入容器
- -t: 在新容器内指定一个伪终端或终端。
- -i: 允许你对容器内的标准输入 (STDIN) 进行交互。
- -d: 以进程方式运行的容器(后台运行)
docker run -dit ubuntu
删除容器
docker container rm 容器ID或者容器名
下面的命令可以清理掉所有处于终止状态的容器。
docker container prune
docker仓库
首先需要注册docker账号 地址 https://hub.docker.com
之后我们在终端登录Docker
docker login
- 然后我们把镜像进行推送,这里需要注意,命名不规范会推送失败,如下
- 这里需要把镜像名进行规范
docker tag 原镜像名 用户名/镜像名
- 这时候就能成功推送了
- 查看dockerhub仓库,这里已经可以看见我们刚刚推送的镜像了