首先 Docker 是由 go 语言编写,基于 Linux 容器技术(LXC)、Namespace、Cgroup 和 UnionFS(联合文件系统)等技术的轻量级操作系统虚拟化解决方案。
对于 docker 的概念,可以简单理解如下:
- 镜像(Image) 类似于虚拟机的快照,它是只读,可以以镜像为模板创建容器,在容器中的更改不会影响到原镜像。实际镜像是 UnionFS 的层级文件系统。
- 容器(Container) 类似于轻量级的虚拟机,由 docker 镜像实例化而来,docker 推荐一个容器运行一个进程,可见其轻量程度。
- 注册服务器(Registry) 提供在线存放 docker 镜像的在线服务,可以理解为 github 所提供的 repository 作用,当我们使用 docker run 运行一个本地不存在的镜像时,默认情况下 docker 会从 docker 官方的 registry 拉取该镜像,然后创建并运行一个容器。
- Dockerfile 可以理解为用于构建镜像的命令和设置组合
本文将从安装开始,记录docker日常使用的一些命令、问题及深一点的理解
安装
官方有非常详细的安装方法https://www.docker.com,由于某些共知原因,通常会出现各种问题,或者会很慢,这里推荐阿里云提供的 Docker CE 镜像站,安装如下:
Ubuntu 14.04 16.04 (使用 apt-get 进行安装)
1 | # step 1: 安装必要的一些系统工具 |
CentOS 7 (使用 yum 进行安装)
1 | # step 1: 安装必要的一些系统工具 |
验证安装
1 | $ docker version |
最好将当前用户添加到 docker 用户组:
1 | sudo usermod -aG docker your_username |
然后我们再使用上阿里云的镜像加速器:
1 | sudo mkdir -p /etc/docker |
注册地址:https://cr.console.aliyun.com/#/accelerator
然后运行下hello-world
试试:
1 | $ docker run hello-world |
快速入门
基本应用
查看本地镜像
1 | docker image ls |
搜索远程仓库中名为 centos 的镜像
1 | docker search centos |
从远程仓库将相应镜像拉回本地
1 | docker pull centos |
默认会将 docker.io 官方的最新版本的 centos 取回本地
创建容器
从刚才拉回本地的 centos 创建一个容器,并在其中启动一个 bash,让该容器在后台运行
1 | docker run -itd --name centos7 -v /home/blueyi/docker:/home/docker -p 20080:80 centos /bin/bash |
上面的参数解释如下:
-itd
:是-i -t -d
的简写。-i
表示保持打开标准输入流(stdin),无论是否连接到此容器。-t
为容器分配一个虚拟的 tty。-d
表示后台模式运行容器,即容器启动后将断开与当前终端的连接。--name
:这是为容器起一个名字,之后与容器的交互需要用到,当然用容器的 Id 也是可以的(就是 run 命令后那个巨长的回显)。当然,不写的话也会有默认的名字。-v
:表示挂载宿主主机目录到容器的目录(宿主机目录路径:容器目录路径)。当然可以设置读写属性。-p
:表示映射主机端口至容器端口(主机端口:容器端口)。这个参数可以重复出现,映射多个端口。centos
:本地镜像名称(centos),如果本地没有名为 centos 我镜像,Docker 会自动搜索并下载远程仓库中的最新镜像。/bin/bash
:这个是本次启动的镜像要执行的任务。这个门道比较多,我在后面会说一下。
查看运行中的容器
1 | docker container ls |
查看所有容器
1 | docker container ls -a |
连接容器
1 | docker attach centos7 |
然后我们查看一下容器 centos7 的系统信息:
1 | [root@c5eb1263ab93 /]# cat /etc/*release |
查看当前容器中的运行的进程:
1 | [root@c5eb1263ab93 /]# ps -aux |
从未见过如此少的一次系统运行的所有进程,后面会详细说明原因。
现在想退出容器怎么办呢,使用exit
或者给个ctrl+d
,但一但 exit 出来这个 bash,也就表示容器中运行的唯一进程被关闭,代表着容器中的任务运行完成,容器就会停止。
启动停止的容器
1 | docker start centos7 |
在容器中执行命令
通过docker exec
命令可以在容器中执行命令,而不用进入容器的 bash。
查看容器 centos7 中运行的进程:
1 | docker exec centos7 ps -aux |
启动 bash 并挂载输入输出流(前台模式):
1 | docker exec -it centos7 bash |
这样我们就相当于另起一个 bash,并直接登录,需要退出时正常 exit 或者ctrl+d
即可。当然也可以运行一个带-d
参数的相应命令,但我们通过docker attach centos7
连接时,依然连接的是最初创建容器时的 bash。
停止容器
1 | docker stop centos7 |
删除容器
1 | docker rm centos7 |
默认只能删除停止后的容器,如果要强行删除,可以使用-f
参数。
所以针对容器名的操作都可以通过CONTAINER ID
来操作,CONTAINER ID
使用时只要不会冲突,可以只用前几位,类似于 git 中的commit id
例如:
1 | docker container ls -a |
我想启动最后一个容器:
1 | docker start 8 |
因为容器 ID 中以8
开始的只有这一个,并不会歧义。
强制删除刚刚启动的容器:
1 | docker rm -f 8 |
Docker 官方 Get Started
来自官方Get Started
包含了 docker 的常用使用场景下的使用方式
Dockerfile
Dockerfile 用于定义在容器运行后的内部环境应该包含哪些东西,例如下面这个用于运行 flask 程序的 python 环境的 Dockerfile:
1 | # Use an official Python runtime as a parent image |
然后在requirements.txt
中放入我们的依赖:
1 | Flask |
flask 的 hellowrld 的app.py
代码如下:
1 | from flask import Flask |
创建 docker 镜像
创建名为 hello 的镜像:
1 | docker build -t hello . |
然后会看到一下下载和配置过程,最后有一个Successfully
的提示,并且跟了一个 id 和 tag,该 id 即为该镜像的唯一标识符,同一个 Dockerfile 可以创建同一镜像 ID 但不同名的多个镜像。
查看镜像:
1 | docker images # 或者 docker image ls |
运行镜像:
1 | docker run -p 4000:80 hello |
-p
为端口映射,运行成功后可以通过http://localhost:4000
访问,或者你的主机 IP 跟4000
的端口号。
后台运行:
1 | docker run -d -p 4000:80 hello |
查看运行中的容器:
1 | docker container ls |
停止运行中的容器:
1 | docker container stop <id> |
push 自己的镜像到docker repository
登录到cloud.docker.com
:
1 | docker login |
为镜像添加标签:
1 | docker tag image username/repository:tag |
然后 push 就可以了:
1 | docker push username/repository:tag |
在新环境中,从远程 pull 并且运行:
1 | docker run -p 4000:80 username/repository:tag |
对 docker 深一点的理解
先查看一下我当前的系统环境:
1 | $ cat /etc/*release |
现在我们尝试拉取一个官方的 centos 镜像并运行:
1 | docker run -t -i centos:latest /bin/bash |
一段时间下载之后会进入新镜像 centos 创建的容器,并在其中运行 bash。前面已经细解释过该命令参数的意义。
现在我们再来查看一下系统信息:
1 | [root@ed454bbb94fc /]# cat /etc/*release |
神奇的事情发生了,我们运行着的 centos 容器,竟然使用的是本地操作系统 ubuntu 的 linux 内核,所以说 docker 可以看成是超轻量级的虚拟机。事实上,它只是使用 linux 容器和命名空间等技术,帮助我们实现进程的资源隔离。下面我们做一些验证。
在 centos 容器中运行 top,可以看到进程中只有一个 top 和一个 bash 在运行。
然后我们再来查看一下宿主系统(ubuntu)中是否有 top 在运行:
1 | ps -ef | grep top |
下面来自网上的一段引用:
这个 top 进程就是我们在容器中运行的 top,其实 docker 容器中运行的进程实际上就是宿主机上的进程。docker 实际上使用了命名空间(namespace)来对进程进行隔离,使不同 namespace 的进程彼此不可见,同时使用 cgroup 来对彼此隔离的进程的资源进行限制,docker 的容器(container)其实就是一个进程的容器,而并不是一个全虚拟化的操作系统,所以他不会有什么 init 进程。docker 将进程、进程所需要的操作系统、运行环境称为容器。所以它比传统的基于 hypervisor 的虚拟机拥有更高的效率,并使用更低的资源。它实际上是一个内核级别的虚拟化技术,容器还是在使用宿主机的内核,为了证实上述内容,我们可以在容器中用如下命令查看 docker 的内核版本:
你也可以使用free -hm
等命令查看容器的硬件信息,发现是与宿主主机信息是一样的。
Docker 其他常用命令
仅涉及前面没有用到或者没有仔细解释的命令
命令中name
与id
一样,下述中的container
指容器的名称或 ID,image
指镜像的名称或 ID
镜像相关
删除镜像
1 | docker rmi <image> |
删除镜像前必须先删除容器,或者使用-f
参数。
删除所有镜像
1 | docker rmi $(docker images | awk '{if (NR>4) {print $3}}') |
如果镜像有关系容器未删除,可以使用-f
参数强制删除
查看镜像历史
1 | docker image history <image> |
保存和恢复镜像
应用场景,当创建自己的镜像之后希望迁移到其他机器上,除了可以通过推送到公网,然后再 pull 回来之外,还可以保存到本地文件,然后拷贝过去之后再恢复
保存和恢复镜像有2组命令:save-load
和export-import
save-load
支持一次保存多个镜像,当然使用load
恢复的时候也会一次恢复多个镜像
保存镜像
1 | docker save centos:latest > my_centos.tar |
save命令后面也可以跟容器
恢复镜像:
1 | docker load < my_centos.tar |
export-import
docker export
的操作对象需要是容器,而不是镜像,所以适用于制作基础镜像的场景
导出镜像:
1 | docker export -o my_centos_ex.tar centos7 |
导入镜像:
1 | docker import my_centos_ex.tar blueyi/centos7:latest |
两种方式的区别
save
导出的镜像tar包会包含所有历史信息和层,这样就可以支持层回滚了,但export
导致的镜像tar包不包含历史信息,所以它的导出文件也会小一些,可以在导入后使用后面的镜像历史命令查询。
import
导入export
的镜像时支持指定镜像名
显示镜像历史
1 | docker history <image> |
保存容器为一个新的镜像
1 | docker commit <container> <NEW_IMAGE_NAME> |
容器相关
删除所有容器
1 | docker rm $(docker -ps -a -q) |
强制 kill 掉容器
1 | docker kill 《容器名 /ID> |
它与前面用过的docker stop
的区别是,docker stop
执行时会先向容器中 PID 为 1 的进程发送系统信号SIGTERM
,然后等待容器中的应用程序终止,如果等待时间达到设定的时间,或者默认的 10 秒,则会继续发送SIGKILL
系统信号强行 kill 掉进程。如docker stop --time=20 <container_name>
。
而docker kill
类似于 Linux 系统的kill
,如:docker kill --singal=SIGINT <container_name>
,默认不加指定信号相当于kill -9
或 kill -SIGKILL
,强行终止进程。
后台运行一个容器,并映射端口和文件夹
前面已经使用过了,再举一例
1 | docker run -idt --name debian8 -p 8080:80 -p 8022:22 -v /var/cpp:/mnt/cpp -v /home/blueyi/download:/mnt/download debian:latest /bin/bash |
文件夹映射也称数据卷挂载,这样就可以使容器中使用的数据能够持久化地保存到本地
为了避免运行后它立即退出,所以打开了输入流-i
,分配虚拟 tty-t
,在其中执行/bin/bash
端口映射
如果只是-p 80
则docker会随机从宿主主机中选择一个端口映射到该容器中的80端口
如果端口使用使用大写的 P(即-P
),则 docker 会随机映射一个49000~49900
的端口到内部容器开放的网络端口
端口映射也可以通过指定范围一次映射多个端口,例如-v 2000-3000:2000-3000
,注意在我的测试中如果这个范围太大,比如超过10000个端口映射会导致容器启动失败
显示容器的内容的改变
1 | docker diff <container> |
显示容器中的进程信息
1 | docker top <container> |
重启容器
1 | docker restart <container> |
attach
到容器
1 | docker attach <container> |
前面已经用过,这样进入容器后当需要退出时,使用exit
会导致容器停止。我们可以使用快捷键:先按ctrl+p
,再按ctrl+q
来退出容器
连接容器
docker 的容器连接分为 2 种情况,一种是同一主机上不同容器之间的连接,使用--link
的方式进行;另一种是跨主机连接,使用ambassador
实现,这里只试验使用link
方式连接,跨主机连接以后用到了再学习。
1 | docker run --name=mysql_client1 --link=mysql_server:db -t -i mysql_client /usr/bin/mysql |
表示从名为mysql_client
的镜像创建一个名为mysql_client1
的容器,并将其链接到 mysql_server
,同时将 mysql_server
的别名命名为db
容器连接后可以查看其/etc/hosts
文件中对相应容器的 IP 容器名以及容器 id 进行了映射
查看容器日志
1 | docker logs <container> |
从容器中拷贝数据到本地
1 | docker cp <container>:/etc/profile . |
会将容器上的/etc/profile
拷贝到当前目录,支持目录拷贝
其他
查看 docker 对象的底层信息
通过docker inspect
可以查看较低层的 docker 容器以及 docker 镜像的内部信息:
1 | docker inspect <image_name/container_name> |
当然id
也一样。
数据卷
关于数据卷前面已经多次使用,就是通过-v 宿主机文件 / 目录:容器里对应的文件 / 目录:权限
参数在运行容器之前为其添加数据卷映射,容器中不存在的挂载目录会自动创建,不指定权限时默认为读写。
如果-v
后面只跟一个目录,该目录将会在容器在创建,并在本地的/var/lib/docker/volumes
目录下产生相应的一个对应目录,目录名随机,可以通过以下命令查看具体挂载信息及相应目录
1 | docker inspect --format='{{.Mounts}}' <container> |
或者使用命令:
1 | docker inspect <container> | grep volumes |
注意:/var/lib/docker/volumes
下的文件夹,也就是挂载的数据卷只有在以下情况才会被删除,否则该目录中会遗留很多名称很长的目录
docker rm -v
删除容器时添加了-v
选项docker run --rm
运行容器时添加了--rm
选项
数据卷容器
如果用户需要在多个容器之间共享一些持续更新的数据,最简单的方式是使用数据卷容器。数据卷容器也是一个容器,但是它的目的是专门用来提供数据卷供其他容器挂载。
例如,创建一个数据卷容器 dbdata
,并在其中创建一个数据卷挂载到 /dbdata
:
1 | docker run -it -v /dbdata --name dbdata centos |
然后,可以在其他容器中使用 --volumes-from
来挂载 dbdata
容器中的数据卷。
例如创建 db1 和 db2 两个容器,并从 dbdata 容器挂载数据卷:
1 | docker run -it --volumes-from dbdata --name db1 ubuntu |
此时,容器 db1 和 db2 都挂载同一个数据卷到相同的 /dbdata
目录。三个容器任何一方在该目录下的写入,其他容器都可以看到。
可以多次使用--volumes-from
参数来从多个容器挂载多个数据卷。还可以从其他已经挂载了容器卷的容器来挂载数据卷。
使用--volumes-from
参数所挂载数据卷的容器自身并不需要保持在运行状态。
如果删除了挂载的容器(包括 dbdata、db1 和 db2),数据卷并不会被自动删除。如果要删除一个数据卷,必须在删除最后一个还挂载着它的容器时显式使用docker rm -v
命令来指定同时删除关联的容器。
利用数据卷容器来迁移数据
可以利用数据卷容器对其中的数据卷进行备份、恢复,以实现数据的迁移。
备份
使用下面的命令来备份 dbdata 数据卷容器内的数据卷:
1 | docker run --volumes-from dbdata -v $(pwd):/backup --name worker centos tar cvf /backup/backup.tar /dbdata |
首先利用 centos 镜像创建了一个容器 worker。使用 –volumes-from dbdata 参数来让 worker 容器挂载 dbdata 容器的数据卷(即 dbdata 数据卷), 使用 -v $(pwd):/backup
参数来挂载本地的当前目录到 worker 容器的 /backup
目录。worker 容器启动后,使用了 tar cvf /backup/backup.tar /dbdata
命令来将 /dbdata
下内容备份为容器内的 /backup/backup.tar
,即宿主主机当前目录下的 backup.tar
。
恢复
如果要将数据恢复到一个容器,可以按照下面的步骤操作。
首先创建一个带有数据卷的容器 dbdata2:
1 | docker run -v /dbdata --name dbdata2 centos /bin/bash |
然后创建另一个新的容器,挂载 dbdata2 的容器,并使用 untar 解压备份文件到所挂载的容器卷中:
1 | docker run --volumes-from dbdata2 -v $(pwd):/backup --name worker centos bash |
其他问题
64位系统中的docker是否可以使用32位镜像
经测试是可以的,需要内核依然使用的是宿主主机的64位内核,但其中的32程序和库都可以正常使用
参考
- Docker CE 镜像源站
- Docker Get Started
- 其他大量互联网资源