Docker是Go语言开发实现的容器。2013年发布至今,备受推崇。相关文档、学习资料十分详尽。近期有docker相关项目,得重新学习一下。博客以笔记。
1 容器诞生背景及优势
(1)软件开发和运维中,环境部署、配置,不胜其烦。举例说明,Python开发和部署都必须配置Python解释器;运维过程中,有时测试环境能通过,但是到线上却报错,究其原因,是环境不一致。
传统运维过程中,线上有十台机器,每增加一台都需要重新部署一次,简直就是“体力劳动”。
(2)虚拟机在一定程度可以解决这些问题,但是存在几个缺点:
- 资源占用多 虚机启动需要占用几百M的内存。
- 冗余步骤多 系统级别的操作步骤,往往无法跳过,比如用户登录。
- 启动慢,往往几分钟 启动操作系统需要多久,启动虚拟机就需要多久。
(3)容器优势
容器不是模拟一个操作系统,而是对进程进行隔离。属于进程级别。
- 启动快, 相当于启动本机底层系统的一个进程,而不是虚拟机内部的进程,速度快很多。
- 占用资源少,容器只占用需要的资源,不占用那些没有用到的资源;多个容器可以共享资源,虚拟机是独享资源。
- 体积小,容器只要包含用到的组件即可,而虚拟机是整个操作系统的打包,所以容器文件比虚拟机文件要小很多。
传统虚拟化示意图:
docker虚拟化示意图:
2 Docker 基本概念
(1)镜像image
Docker 把应用程序及其依赖,打包在 image 文件里面。只有通过这个文件,才能生成 Docker 容器。image 文件可以看作是容器的模板。Docker 根据 image 文件生成容器的实例。同一个 image 文件,可以生成多个同时运行的容器实例。
image 是二进制文件。实际开发中,一个 image 文件往往通过继承另一个 image 文件,加上一些个性化设置而生成。举例来说,你可以在 Ubuntu 的 image 基础上,往里面加入 Apache 服务器,形成你的 image。
image 文件是通用的,一台机器的 image 文件拷贝到另一台机器,照样可以使用。一般来说,为了节省时间,我们应该尽量使用别人制作好的 image 文件,而不是自己制作。即使要定制,也应该基于别人的 image 文件进行加工,而不是从零开始制作。
为了方便共享,image 文件制作完成后,可以上传到网上的仓库。Docker 的官方仓库 Docker Hub 是最重要、最常用的 image 仓库。此外,出售自己制作的 image 文件也是可以的。
(2)容器Container
image 文件生成的容器实例,本身也是一个文件,称为容器文件。也就是说,一旦容器生成,就会同时存在两个文件: image 文件和容器文件。而且关闭容器并不会删除容器文件,只是容器停止运行而已。
(3)仓库Repository
镜像构建完成后,可以很容易的在当前宿主机上运行,但是,如果需要在其它服务器上使用这个镜像,我们就需要一个集中的存储、分发镜像的服务,Docker Registry 就是这样的服务。
一个 Docker Registry 中可以包含多个仓库(Repository);每个仓库可以包含多个标签(Tag);每个标签对应一个镜像。
通常,一个仓库会包含同一个软件不同版本的镜像,而标签就常用于对应该软件的各个版本。我们可以通过 <仓库名>:<标签> 的格式来指定具体是这个软件哪个版本的镜像。如果不给出标签,将以 latest 作为默认标签。
3 安装docker
以CentOS为例:
Docker CE 支持 64 位版本 CentOS 7,并且要求内核版本不低于 3.10。 CentOS 7 满足最低内核的要求,但由于内核版本比较低,部分功能(如 overlay2
存储层驱动)无法使用,并且部分功能可能不太稳定。
(1)卸载旧版本
sudo yum remove docker docker-client docker-client-latest docker-common docker-latest docker-latest-logrotate docker-logrotate docker-selinux docker-engine-selinux docker-engine
(2)安装依赖包
yum install -y yum-utils device-mapper-persistent-data lvm2
(3)安装国内源
yum-config-manager --add-repo https://mirrors.ustc.edu.cn/docker-ce/linux/centos/docker-ce.repo
(4)安装docker-ce
yum makecache fast yum install docker-ce
也可以使用脚本自动安装:
curl -fsSL get.docker.com -o get-docker.sh sh get-docker.sh --mirror Aliyun
(5)启动docker-ce
systemctl enable docker systemctl start docker
示例:
(6)创建docker用户组
默认情况下,docker 命令会使用 Unix socket 与 Docker 引擎通讯。而只有 root 用户和 docker 组的用户才可以访问 Docker 引擎的 Unix socket。出于安全考虑,一般 Linux 系统上不会直接使用 root 用户。
因此,更好地做法是将需要使用 docker 的用户加入 docker 用户组。
# 建立 docker 组: sudo groupadd docker # 将当前用户加入 docker 组: sudo usermod -aG docker $USER # 退出当前终端并重新登录,进行如下测试
注意:本次使用的是root用户。
(7)测试docker是否安装成功
docker run hello-world
如果出现如下信息表明安装成功了:
Unable to find image 'hello-world:latest' locally latest: Pulling from library/hello-world ca4f61b1923c: Pull complete Digest: sha256:97ce6fa4b6cdc0790cda65fe7290b74cfebd9fa0c9b8c38e979330d547d22ce1 Status: Downloaded newer image for hello-world:latest Hello from Docker! This message shows that your installation appears to be working correctly. To generate this message, Docker took the following steps: 1. The Docker client contacted the Docker daemon. 2. The Docker daemon pulled the "hello-world" image from the Docker Hub. (amd64) 3. The Docker daemon created a new container from that image which runs the executable that produces the output you are currently reading. 4. The Docker daemon streamed that output to the Docker client, which sent it to your terminal. To try something more ambitious, you can run an Ubuntu container with: $ docker run -it ubuntu bash Share images, automate workflows, and more with a free Docker ID: https://cloud.docker.com/ For more examples and ideas, visit: https://docs.docker.com/engine/userguide/
(8)配置内核参数
默认配置下,如果在 CentOS 使用 Docker CE 看到下面的这些警告信息:
WARNING: bridge-nf-call-iptables is disabled WARNING: bridge-nf-call-ip6tables is disabled
添加内核配置参数以启用这些功能:
tee -a /etc/sysctl.conf <<EOF net.bridge.bridge-nf-call-ip6tables = 1 net.bridge.bridge-nf-call-iptables = 1 EOF
重新加载内核配置
sysctl -p
4 获取镜像
从 Docker 镜像仓库获取镜像的命令是 docker pull
。其命令格式为:
docker pull [选项] [Docker Registry 地址[:端口号]/]仓库名[:标签]
具体的选项可以通过 docker pull --help
命令看到,这里我们说一下镜像名称的格式。
- Docker 镜像仓库地址:地址的格式一般是
<域名/IP>[:端口号]
。默认地址是 Docker Hub。 - 仓库名:如之前所说,这里的仓库名是两段式名称,即
<用户名>/<软件名>
。对于 Docker Hub,如果不给出用户名,则默认为library
,也就是官方镜像。
例如,从镜像仓库获取nginx:
[@sjs_123_183 ~]# docker pull nginx Using default tag: latest latest: Pulling from library/nginx 2a72cbf407d6: Pull complete e19f9e910af9: Pull complete 2f3d26a87e79: Pull complete Digest: sha256:e36d7f5dabf1429d84135bb8a8086908e1150f1a178c75719a9e0e53ebb90353 Status: Downloaded newer image for nginx:latest
(1)上诉命令中没有指定 Docker 镜像仓库地址,因此将会从 Docker Hub 获取镜像。
(2)而镜像名称是 nginx,因此将会获取官方镜像 library/nginx 仓库中标签为 latest 的镜像。
(3)从下载过程中可以看到我们之前提及的分层存储的概念,镜像是由多层存储所构成。下载也是一层层的去下载,并非单一文件。
下载过程中给出了每一层的 ID 的前 12 位。并且下载结束后,给出该镜像完整的 sha256 的摘要,以确保下载一致性。
(4)官方镜像是一直在维护,你所看到的层 ID 以及 sha256 的摘要可能和此处不一样。
我们再获取一个官方的Ubuntu镜像看看:
[@sjs_123_183 ~]# docker pull ubuntu:16.04 16.04: Pulling from library/ubuntu 22dc81ace0ea: Pull complete 1a8b3c87dba3: Pull complete 91390a1c435a: Pull complete 07844b14977e: Pull complete b78396653dae: Pull complete Digest: sha256:e348fbbea0e0a0e73ab0370de151e7800684445c509d46195aef73e090a49bd6 Status: Downloaded newer image for ubuntu:16.04
以ubuntu镜像为例,运行镜像
docker run -it --rm ubuntu:16.04 bash
(1)docker run 就是运行容器的命令,具体格式可在后面阐述。
(2)-it:这是两个参数,一个是 -i:交互式操作,一个是 -t 终端。我们这里打算进入 bash 执行一些命令并查看返回结果,因此我们需要交互式终端。
(3)--rm:这个参数是说容器退出后随之将其删除。默认情况下,为了排障需求,退出的容器并不会立即删除,除非手动 docker rm。我们这里只是随便执行个命令,看看结果,不需要排障和保留结果,因此使用 --rm 可以避免浪费空间。
(4)ubuntu:16.04:这是指用 ubuntu:16.04 镜像为基础来启动容器。
(5)bash:放在镜像名后的是命令,这里我们希望有个交互式 Shell,因此用的是 bash。
运行上诉命令后会进入ubuntu镜像示例的的shell界面,我们查看系统,结果如下:
root@a1318dadbea3:/# cat /etc/os-release NAME="Ubuntu" VERSION="16.04.4 LTS (Xenial Xerus)" ID=ubuntu ID_LIKE=debian PRETTY_NAME="Ubuntu 16.04.4 LTS" VERSION_ID="16.04" HOME_URL="http://www.ubuntu.com/" SUPPORT_URL="http://help.ubuntu.com/" BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/" VERSION_CODENAME=xenial UBUNTU_CODENAME=xenial root@a1318dadbea3:/#
exit退出当前容器。
5 列出镜像
列出镜像的命令:
docker image ls
示例:
[@sjs_123_183 ~]# docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE nginx latest c5c4e8fa2cf7 6 days ago 109MB ubuntu 16.04 f975c5035748 4 weeks ago 112MB hello-world latest f2a91732366c 4 months ago 1.85kB
每列的含义:
REPOSITORY:仓库名 TAG:标签, 一个镜像可以对应多个标签 IMAGE ID:镜像 ID,是镜像的唯一标识 CREATED:创建时间 SIZE:所占用的空间
注意:此处看到所占用空间与Docker Hub 上看到的镜像大小不同。
比如,ubuntu:16.04 镜像大小,在这里是 112 MB,但是在 Docker Hub 显示的却是 43 MB。这是因为:
(1)Docker Hub 中显示的体积是压缩后的体积。减小网络传输流量。
(2)docker image ls 显示的是镜像下载到本地后,展开的大小,准确说,是展开后的各层所占空间的总和,因为镜像到本地后,查看空间的时候,更关心的是本地磁盘空间占用的大小。
(3)docker image ls 列表中的镜像体积总和并非是所有镜像实际硬盘消耗。由于 Docker 镜像是多层存储结构,并且可以继承、复用,因此不同镜像可能会因为使用相同的基础镜像,从而拥有共同的层。
由于 Docker 使用 Union FS,相同的层只需要保存一份即可,因此实际镜像硬盘占用空间很可能要比这个列表镜像大小的总和要小的多。
6 查看镜像、容器、数据卷所占用的空间
命令:
docker system df
示例:
[@sjs_123_183 ~]# docker system df TYPE TOTAL ACTIVE SIZE RECLAIMABLE Images 3 2 221.4MB 112.4MB (50%) Containers 2 0 0B 0B Local Volumes 0 0 0B 0B Build Cache 0B 0B
7 虚悬镜像
仓库名和标签均为 <none>,这类无标签、无仓库名镜像被称 虚悬镜像(dangling image) 。
镜像原本是有镜像名和标签的,比如原来为 mongo:3.2,随着官方镜像维护,发布了新版本后,重新 docker pull mongo:3.2 时,mongo:3.2 这个镜像名被转移到了新下载的镜像身上,而旧的镜像上的这个名称则被取消,从而成为了 <none>。
除了 docker pull 可能导致这种情况,docker build 也同样可以导致这种现象。由于新旧镜像同名,旧镜像名称被取消,从而出现仓库名、标签均为 <none> 的镜像。
虚悬镜像示例:
<none> <none> 00285df0df87 5 days ago 342 MB
显示虚悬镜像命令:
docker image ls -f dangling=true
删除虚悬镜像:
docker image prune
8 中间层镜像
为了加速镜像构建、重复利用资源,Docker 会利用 中间层镜像。所以在使用一段时间后,可能会看到一些依赖的中间层镜像。默认的 docker image ls 列表中只会显示顶层镜像,如果希望显示包括中间层镜像在内的所有镜像的话,需要加 -a 参数。
docker image ls -a
这样会看到很多无标签的镜像,与之前的虚悬镜像不同,这些无标签的镜像很多都是中间层镜像,是其它镜像所依赖的镜像。这些无标签镜像不应该删除,否则会导致上层镜像因为依赖丢失而出错。
实际上,这些镜像也没必要删除,因为之前说过,相同的层只会存一遍,而这些镜像是别的镜像的依赖,因此并不会因为它们被列出来而多存了一份,无论如何你也会需要它们。只要删除那些依赖它们的镜像后,这些依赖的中间层镜像也会被连带删除。
9 按需列出镜像
(1)列出部分镜像
命令:
docker image ls redis 按仓库名过滤 docker image ls redis:3.2 按仓库名和标签过滤 docker image ls -f since=redis:3.2.11 列出redis:3.2.11之后建立的镜像 docker image ls -f before=redis:3.2 列出redis:3.2之前建立的镜像
示例:
[@sjs_123_183 ~]# docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE nginx latest c5c4e8fa2cf7 2 weeks ago 109MB redis latest c5355f8853e4 3 weeks ago 107MB redis 3.2 b05c3d76c8b3 5 weeks ago 99.7MB redis 3.2.11 b05c3d76c8b3 5 weeks ago 99.7MB ubuntu 16.04 f975c5035748 6 weeks ago 112MB hello-world latest f2a91732366c 5 months ago 1.85kB [@sjs_123_183 ~]# docker image ls redis REPOSITORY TAG IMAGE ID CREATED SIZE redis latest c5355f8853e4 3 weeks ago 107MB redis 3.2 b05c3d76c8b3 5 weeks ago 99.7MB redis 3.2.11 b05c3d76c8b3 5 weeks ago 99.7MB [@sjs_123_183 ~]# docker image ls redis:3.2 REPOSITORY TAG IMAGE ID CREATED SIZE redis 3.2 b05c3d76c8b3 5 weeks ago 99.7MB [@sjs_123_183 ~]# docker image ls -f since=redis:3.2.11 REPOSITORY TAG IMAGE ID CREATED SIZE nginx latest c5c4e8fa2cf7 2 weeks ago 109MB redis latest c5355f8853e4 3 weeks ago 107MB [@sjs_123_183 ~]# docker image ls -f before=redis:3.2 REPOSITORY TAG IMAGE ID CREATED SIZE ubuntu 16.04 f975c5035748 6 weeks ago 112MB hello-world latest f2a91732366c 5 months ago 1.85kB
(2)自定义输出格式
docker images --filter "条件" 按条件过滤
示例:
[@sjs_123_183 ~]# docker images --filter "since=redis:3.2" REPOSITORY TAG IMAGE ID CREATED SIZE nginx latest c5c4e8fa2cf7 2 weeks ago 109MB redis latest c5355f8853e4 3 weeks ago 107MB
(3)使用go语言模板语法
docker images --format "{{.ID}} <--> {{.Repository}}" # 按 ID <--> 仓库名格式列出 docker images --format "table {{.ID}} {{.Repository}} {{.Tag}}" # 按ID 仓库 标签的格式列出
示例:
[@sjs_123_183 ~]# docker images --format "{{.ID}} <--> {{.Repository}}" c5c4e8fa2cf7 <--> nginx c5355f8853e4 <--> redis b05c3d76c8b3 <--> redis b05c3d76c8b3 <--> redis f975c5035748 <--> ubuntu f2a91732366c <--> hello-world [@sjs_123_183 ~]# docker images --format "table {{.ID}} {{.Repository}} {{.Tag}}" IMAGE ID REPOSITORY TAG c5c4e8fa2cf7 nginx latest c5355f8853e4 redis latest b05c3d76c8b3 redis 3.2 b05c3d76c8b3 redis 3.2.11 f975c5035748 ubuntu 16.04 f2a91732366c hello-world latest
常用go语言模板:
.ID # 镜像ID .Repository # 镜像仓库名 .Tag # 镜像标签 .Digest # 镜像摘要 .CreatedSince # 镜像创建到现在的耗时 .CreatedAt # 镜像创建时间 .Size # 镜像大小
10 删除镜像
10.1 单台删除
删除本地镜像的命令是docker image rm,用法如下:
docker image rm [选项] <镜像1> [<镜像2> ...]
(1)<镜像> 可以是 镜像短 ID、镜像长 ID、镜像名 或者 镜像摘要。
(2)也可以使用镜像名,即 <仓库名>:<标签> 删除镜像。
(3)更精确的是使用 镜像摘要 删除镜像。
注意:删除行为分为两类,一类是 Untagged,另一类是 Deleted。
镜像的唯一标识是其 ID 和摘要,而一个镜像可以有多个标签。
因此当我们使用上面命令删除镜像的时候,实际上是在要求删除某个标签的镜像。
(1)所以首先需要做的是将满足我们要求的所有镜像标签都取消,这就是我们看到的 Untagged 的信息。因为一个镜像可以对应多个标签,因此当我们删除了所指定的标签后,可能还有别的标签指向了这个镜像,如果是这种情况,那么 Delete 行为就不会发生。所以并非所有的 docker rmi 都会产生删除镜像的行为,有可能仅仅是取消了某个标签而已。
(2)当该镜像所有的标签都被取消了,该镜像很可能会失去了存在的意义,因此会触发删除行为。镜像是多层存储结构,因此在删除的时候也是从上层向基础层方向依次进行判断删除。镜像的多层结构让镜像复用变动非常容易,因此很有可能某个其它镜像正依赖于当前镜像的某一层。这种情况,依旧不会触发删除该层的行为。直到没有任何层依赖当前层时,才会真实的删除当前层。这就是为什么,有时候会奇怪,为什么明明没有别的标签指向这个镜像,但是它还是存在的原因,也是为什么有时候会发现所删除的层数和自己 docker pull 看到的层数不一样的源。
(3)除了镜像依赖以外,还需要注意的是容器对镜像的依赖。如果有用这个镜像启动的容器存在(即使容器没有运行),那么同样不可以删除这个镜像。之前讲过,容器是以镜像为基础,再加一层容器存储层,组成这样的多层存储结构去运行的。因此该镜像如果被这个容器所依赖的,那么删除必然会导致故障。如果这些容器是不需要的,应该先将它们删除,然后再来删除镜像。
示例:
[@sjs_123_183 ~]# docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE nginx latest c5c4e8fa2cf7 2 weeks ago 109MB redis latest c5355f8853e4 3 weeks ago 107MB redis 3.2 b05c3d76c8b3 5 weeks ago 99.7MB redis 3.2.11 b05c3d76c8b3 5 weeks ago 99.7MB ubuntu 16.04 f975c5035748 6 weeks ago 112MB hello-world latest f2a91732366c 5 months ago 1.85kB [@sjs_123_183 ~]# [@sjs_123_183 ~]# [@sjs_123_183 ~]# docker image rm redis:3.2.11 Untagged: redis:3.2.11 [@sjs_123_183 ~]# [@sjs_123_183 ~]# [@sjs_123_183 ~]# docker image rm f97 Untagged: ubuntu:16.04 Untagged: ubuntu@sha256:e348fbbea0e0a0e73ab0370de151e7800684445c509d46195aef73e090a49bd6 Deleted: sha256:f975c50357489439eb9145dbfa16bb7cd06c02c31aa4df45c77de4d2baa4e232 Deleted: sha256:0bd983fc698ee9453dd7d21f8572ea1016ec9255346ceabb0f9e173b4348644f Deleted: sha256:08fe90e1a1644431accc00cc80f519f4628dbf06a653c76800b116d3333d2b6d Deleted: sha256:5dc5eef2b94edd185b4d39586e7beb385a54b6bac05d165c9d47494492448235 Deleted: sha256:14a40a140881d18382e13b37588b3aa70097bb4f3fb44085bc95663bdc68fe20 Deleted: sha256:a94e0d5a7c404d0e6fa15d8cd4010e69663bd8813b5117fbad71365a73656df9
10.2 批量删除
用 docker image ls 命令来配合批量删除本地镜像。
docker image rm $(docker image ls -q redis) # 删除所有仓库名为 redis 的镜像 docker image rm $(docker image ls -q -f before=mongo:3.2) # 删除所有在 mongo:3.2 之前的镜像
示例:
[@sjs_123_183 ~]# docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE nginx latest c5c4e8fa2cf7 2 weeks ago 109MB redis latest c5355f8853e4 3 weeks ago 107MB redis 3.2 b05c3d76c8b3 5 weeks ago 99.7MB hello-world latest f2a91732366c 5 months ago 1.85kB [@sjs_123_183 ~]# docker image rm $(docker image ls -q redis) Untagged: redis:latest Untagged: redis@sha256:6b9f935e89af002225c0dcdadf1fd74245b4cc1e3e91222f7e4769c236cf80d4 Deleted: sha256:c5355f8853e4174a55144edfec23ac37f5bb2200ed838dab53c13c7cc835ab15 Deleted: sha256:65f4f6d6a5f66e424046ccfe359ba5668f2905ec688a8ca62079dfd7946bb989 Deleted: sha256:3f490bf3919344e7ba4acd58824bd24a9479374d6b1d67b59bab7cda118572cf Deleted: sha256:678c0094f27aa1100c30946b9d55981537a107639e71527b5f54ff3de54c12d7 Untagged: redis:3.2 Untagged: redis@sha256:fd310669f5f1be3d96cfe560264cacd5067d6b03b35764b608ada2db98be6208 Deleted: sha256:b05c3d76c8b3ef3af8974edda3941a4a028d244681cc420e5304a3829519f1b6 Deleted: sha256:c7310f74bc14105a3f09a921aeec7feb031f70a95be81ceacc42690efcdc146d Deleted: sha256:7d7786b85eeb28eba62636c3088e535ddbe1640a9632aab550f9a58c0c011faa Deleted: sha256:635b643d26091e3396661ab3b2e08d59110d5ecec236eb100168ced03cf7630b Deleted: sha256:05ffc1bd56d27d29a8a9c36c77dca93ba19dabc77adaf9d3a1ff44c760c3690d Deleted: sha256:1df131a973126fb31c35ca41b2071da4bffcbed7ab8456635b699772e9ad0d8b Deleted: sha256:43efe85a991cac5894f91ee8f45b328bbacd14966d89a8a00b0d06060c64b5ad [@sjs_123_183 ~]# docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE nginx latest c5c4e8fa2cf7 2 weeks ago 109MB hello-world latest f2a91732366c 5 months ago 1.85kB