Docker 实际本质也就是一个非常轻量级的虚拟机,用于将一个或多个程序的全套运行环境整个打包起来,与实际系统分离,大幅简化应用部署。不管物理机是运行的什么系统版本,统统可以完美运行。
从实际应用来看,Docker 适合那种搭建复杂,依赖环境还一堆的程序。这一类经常是哪怕有现成教程参考,搭建过程也非常的不容易,而利用 Docker ,则直接下载一个现成镜像,立即可以投入使用,整个流程简化为几分钟就搞定。后面假如想把这个程序分享给别人,或者转移到另一台系统环境完全不同的机子上,也只需要简单的打包一下镜像,到新机上重新运行即可。
亦或是一台物理机需要运行多个程序,然而每个程序又需要各自不同的一堆依赖,所有程序全装上的话,整个物理机系统会变得混乱不堪,同时也导致出问题的概率大幅提升,后续维护困难。如果使用传统的 VMware 这类虚拟机,每个程序各开一个虚拟机,那又会消耗大量硬件资源,物理机无法承受。这种时候如果利用 docker ,则可以将每个程序隔离开,各自运行所需的环境,对硬件要求也较低。
Docker 的几个名词概念:
镜像(Image)
Docker 的官方 logo 是鲸鱼背上驮着一堆集装箱,镜像就相当于是一个个不同用途的空集装箱,A 箱标着用来装水果,B 箱标着蔬菜,C 箱标着海鲜……各种完全不同的东西,由于有集装箱的依托存储,可以和谐的共同存放在一起,而且运输极为方便。
集装箱等于是一个固定框架,还可以在框架的基础上干自己所需要的事(也就是上面说的装各种货物进去),在修改完工后(装完货后),迁移整个环境依旧非常方便(整个集装箱可以随意搬动)。
类比到实际应用里,就是各种例如 Nginx 镜像,MySQL 镜像,Ubuntu 镜像等。
这些镜像里,有的是整套程序环境(已经装有部分货物的箱子)
有的则只包含最基础的系统环境(空箱子)
而利用镜像创建的容器环境里,用户又可以继续进行随意修改(把箱子里已有货物拿出来换成别的,或者放更多货物进去等),改完还能再次打包后进行方便的传输。
总体来说,Docker 中的镜像就是相当于一个框架蓝本,选用不同的蓝本,就可以直接构建出各种不同功能的现成系统环境。
容器 (Container)
容器是由镜像创建而成的虚拟机,一个镜像可以创建无数个对应的容器,比如下载了一个 CentOS 镜像,那么就可以创建出无数个容器,每个容器都等于一个小虚拟机,里面运行着 CentOS 系统。
仓库 (Repository)
仓库实际也就是一个镜像市场的作用,从市场里可以下载到各种所需的镜像,自己修改完的镜像也可以选择上传到仓库里提供他人下载。Docker 默认官方仓库为 Docker Hub 。仓库还分为公共和私人的,私人仓库可以自己搭建,用于存放私有镜像。
介绍完了三大基础概念,接下来以实际例子来说明 Docker 的基本使用。
Docker 本身可以运行在各种操作系统上,Windows/Linux/macOS 均可,常用管理命令也都是全部通用的。
如果是国内机子,还建议操作之前先把系统软件源更改为阿里云/中科大这类国内源。
Docker 的安装非常简单,常见的几大 Linux 发行版都是几句命令就能搞定安装。Debian 系统的安装方法在之前的文章里已经说过,这次就用 CentOS 7.7 作为演示,用户名为 lishuma。
切换到 root 用户,先安装一些基本环境:
yum install -y yum-utils device-mapper-persistent-data lvm2
然后添加阿里云软件源:
yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
然后安装 Docker 社区版
yum -y install docker-ce
接着分别运行:
systemctl start docker #启动 docker
systemctl enable docker #设定开机自动启动 docker
systemctl status docker #查看 docker 运行状态
以上三条命令分别运行后,可以看到 docker 服务显示为绿色的 running 了,表示运行中。
运行以下命令,查看 docker 版本信息,结果如下图的话就是安装成功了:
docker version
但此时普通用户依旧无法运行 docker 相关操作命令,因为 docker 默认要求必须使用 root 用户或者 docker 用户组里的用户,才能进行操作,因此需要把当前用户加入 docker 用户组,运行:
sudo usermod -a -G docker lishuma #将用户 lishuma 添加到 docker 用户组
sudo systemctl restart docker #重启 docker 服务
其中 lishuma 是本次操作使用的用户名,请根据实际情况改成自己的,然后注销当前用户,重新登录即可。
还建议把 Docker 的仓库源也换成阿里或者网易这类国内的,阿里云有免费的容器加速服务:https://cr.console.aliyun.com ,提供了现成的教程。
最后可以运行一个 hello-world 进行测试:
docker run hello-world
如果运行结果如上图所示,那么就代表整个 Docker 配置全部完成,后面就可以投入使用了。
Docker pull 命令:
在配置完 docker 后,接着就是拉取所需要的镜像了,拉取命令为 docker pull ,通常用法非常简单,就是直接后面再跟随镜像名称即可。基本格式为:
docker pull [镜像名][: 标签]
标签是用来标记仓库里镜像创建者和版本号的,默认访问的仓库为 Docker Hub,版本为 latest,也就是自动获取最新版本。
Docker Hub 官网为:https://hub.docker.com/ ,官网可以直接搜索需要的镜像,查看版本号信息等。
要注意的的是官网仓库的镜像主要分为官方和第三方两种,如下图红框里那种,有官方镜像标识的,那么拉取最新版时可以直接输入镜像名称。
比如想拉取一个最新的 Ubuntu 镜像,那么直接运行:
docker pull ubuntu
而有的镜像不属于官方认证,属于用户上传的,比如一个用户名为 testuser 的用户,上传了一个名叫 testimage 的镜像,那么想要拉取这个镜像的话,则需要斜杠隔开用户名和镜像名,运行:
docker pull testuser/testimage
再比如有的镜像既不是官方发布,版本还特别多,想要拉取特定版本的,比如之前文章提到过的更新狂魔 HomeAssistant,那就运行:
docker pull homeassistant/home-assistant:0.93.1
上面这句命令代表的意思为拉取用户名为 homeassistant 的用户所创建的名为 home-assistant 的镜像,并且指定版本号为 0.93.1,实际上还是非常好理解的。
另外,很多镜像并不是只有一个标签,继续拿 Ubuntu 为例,访问 Hub 的 Ubuntu 项目页面查看详情。
说明里可以看到 18.04,bionic-20191010,bionic,latest 这四个标签实际都指代的同一镜像,都是最新版的意思,实际拉取下来的镜像也是一样的。
比如执行用默认 latest 标签拉取一个最新版 Ubuntu:
docker pull ubuntu
接着换成 18.04 标签来拉取:
docker pull ubuntu:18.04
可以看到会提示镜像已经存在:
再替换成其他同类标签拉取也会是一样的结果。docker 的拉取还有其他多种方式,除了标签名以外,还可以使用 sha256 值这类,但一般入门初期用的很少,不做赘述。
这时候运行:
docker images
这个命令可以查看本地已下载镜像,如下图会发现有两个一模一样的镜像,标签分别为 18.04 和 latest,镜像 ID 一样。实际上这两个相同 ID,不同标签的镜像,本质就是同一个,只是有不同的名称标签。
为了演示我给一个镜像创建了两个标签,这种时候就出现了一个新问题,如何删掉其中某个标签的镜像。
普通的镜像删除命令格式为:docker rmi < 镜像 id>
,在本地只有单个镜像文件的前提下(也就是只有唯一 ID),可以使用这个命令直接删除镜像文件本身。
比如要删掉一开始测试用的那个 hello-world 镜像,这个镜像 ID 为 fce289e99eb9,那就执行:
docker rmi fce289e99eb9
如果遇到了上面这种两个镜像除了 tag 其他全一样的情况,那么就需要使用如下命令来删除:docker rmi 镜像名: 标签名
,这里运行:
docker rmi ubuntu:latest
再次查看镜像列表,可以发现标签为 latest 的镜像已经被删除。
Docker tag 命令:
tag 也就是标签的意思,这个系列的命令用于给镜像重命名创建一个新的。
镜像默认标签一般是 latest 或者版本号,有时候不利于区分,可以改成自定义的。
对于已经下载的镜像,可以利用 docker tag 命令,改名并创建一个新副本,命令格式为:
docker tag 现有镜像: 标签 目标镜像名称: 标签
比如我想把之前下载的标签为 18.04 的 Ubuntu 镜像,改名创建一个副本,名为 test,新标签为 v1,那么可以运行以下命令:
docker tag ubuntu:18.04 test:v1
查看镜像列表就可以发现多出来了个 test 镜像,标签为 v1。
现有镜像名称也可以用镜像 ID 替代,输入 docker tag cf0f3ca922e0 test:v1
也是一样的效果,如下图所示。
Docker 里是以镜像 ID 来作为判断依据,如果发现几个镜像有着不同名称或标签,然而 ID 全都一样,那么它们实际就是同一个镜像,本地也只保存了一份文件。
如上图我用 ubuntu:18.04 的镜像,创建了 test:v1-v5 这 5 个新镜像,可以看到这总共 6 个镜像的 ID 都是一样的,实际上本地也只保存了一份文件。
这时如果对这 6 个相同 ID 的 ubuntu 镜像进行删除操作,在删除其中任意 5 个的时候,实际都只是删除了标签内容,只有删除最后一个时,才会真的删除本地保存的镜像文件。
容器的基本管理命令:
上面说了一堆基础概念,终于到了创建和运行容器的步骤了。容器的管理本身其实是有图形化界面的工具的,比如我之前 Debian 安装 HA 服务器那篇文章里说过的 Portainer 面板,借助面板可以非常简单的进行镜像和容器的管理工作,不过懂一些基本的命令行操作还是有必要的,所以本文并不介绍面板的操作。
容器的运行命令为 :docker run [OPTIONS] IMAGE [COMMAND]
,
docker run 命令整体比较复杂,对应不同需求,用法非常的多,可以对容器进行很详细的定制化,具体参照 Docker 官方文档说明:http://docs.docker.com/engine/reference/commandline/run/
作为基础入门,本文只说明几个常用的指令,毕竟一般情况下个人测试时很多参数还是极少用到的:
-d 指定容器运行于前台还是后台
--name= 指定容器名字
-p 指定容器映射的端口。 注意写的时候是物理机端口在前,
容器端口在后,比如-p 8888:80,那么意思是物理机的
8888 端口映射给容器的 80 端口,反过来说也一样
--restart= 指定容器停止后的策略,可用于设定容器开机自启
-v 映射容器的某个目录到物理机指定目录
--rm 容器运行完毕后自动删除
-i 开启容器输入功能
-t 开启一个容器内部的终端,一般此命令与-i
连用
关于 Docker 前台运行和后台的区别:前台运行的意思是容器里运行的指令会直接显示在物理机终端界面里,安装好容器后测试运行的那个 hello-world 容器就是个典型的例子;后台运行的意思就显而易见了,个人入门的情况下一般是使用后台运行情况居多。
用一个实际例子来说明,比如拉取一个 centos7 镜像, 并创建一个对应的容器,后台运行,名称为 centos7,将容器里的/home 目录映射到外层物理机的/home/lishuma/centos 目录,将容器的 22 端口映射到外层物理机的 2222 端口,那么就可以运行:
docker run -d -p 2222:22 --name=centos7 -v /home/lishuma/centos:/home centos:7
注意:创建容器时如果不指定名称,那么会随机生成一个。如果本地没有对应名称镜像,则会自动拉取对应版本(默认 latest 最新版),所以要么先拉取对应版本镜像,要么创建容器时注意指定镜像版本。
运行后会看到一串完整的容器 ID,位数非常的长,不过在实际管理中是只用前 12 位。
接着会发现问题来了,用 docker ps
命令查看运行中的容器并没有看到 centos7,用 docker ps -a
查看全部容器会发现创建的容器运行后立马就退出了。
这是因为 Docker 下载的这类官方纯净系统镜像可以说是非常的精简,很多命令都不包含。而 Docker 的规范是当容器里没有前台服务运行时,则认为当前无任务,会自动关闭容器。所以如果用到这类纯净系统镜像,则应该使用 docker run -dit
命令,运行:
docker run -dit -p 2222:22 --name=centos7 -v /home/lishuma/centos:/home centos:7
需要注意的是一般只有纯净的系统镜像这类,创建时需要使用 docker run -dit
命令,对于大部分时候本身就是成品软件包的镜像,都只要使用普通的 docker run -d
命令即可,比如前面提到过的 Portainer 面板,HomeAssistant 套件这种。
对容器追加参数:
要是加入创建容器时忘了加某项参数,或者后期需要加入时,可以使用 docker update
命令来追加参数。
例如创建容器时忘了添加开机自启,以上面创建的那个 centos7 容器为例子,可以看到创建时没有加入开机启动参数,假如后面想加上的话,命令格式为 docker update 命令参数 镜像 ID
,
实际如下运行即可:
docker update --restart=always 2261a52b5aa1
再来举个复杂例子,用 centos:7 镜像创建一个对应的容器附加以下要求:
后台运行; 开机自启; 名称为 cos7; 容器里的/home 目录映射到外层物理机的/home/lishuma/centos 目录; 容器里的/home/test 目录映射到外层物理机的/home/lishuma/test 目录; 容器的 22 端口映射到外层物理机的 2222 端口,80 端口映射到外层物理机的 8080 端口。
那么就可以运行:
docker run -dit --restart=always -p 2222:22 -p 8080:80 --name=cos7 -v /home/lishuma/centos:/home -v /home/lishuma/test:/home/test centos:7
结果如下图所示,可以看到一次同时映射两个端口,以此类推,-p
映射端口和-v
映射目录命令都可以多个连用来达到自己所需要求。
容器管理常用命令:
实际容器的基本管理命令都非常简单,列举几个常用的:
docker ps 显示所有运行中的容器 docker ps -a 显示所有容器 docker rm 容器 ID 或名称 删除容器 docker start 容器 ID 或名称 启动指定容器 docker stop 容器 ID 或名称 停止指定容器 docker restart 容器 ID 或名称 重启指定容器
Docker rename 命令:
如果想把现有的容器改个名字,那么改名命令格式为:
docker rename 原容器名或容器 ID 新容器名
比如把名为 cos7 的镜像,改名为 newcos7,那么就运行:
docker rename cos7 newcos7
或者docker rename 150da3e4b6dc newcos7
如何进入容器进行管理:
在创建了容器后,如果想对容器内部进行修改,搭建自己环境等,就需要进入容器进行管理。(最简单粗暴的路子可以直接把容器的 22 端口映射出来 SSH 连接,不过此方法和 Docker 理念不符,安全性也有隐患,除了自己玩玩其他时候都不建议。)
进入容器的命令有 docker attach
和 docker exec
两种。
docker attach
的命令格式为: docker attach 容器 ID 或名称
进入之后操作就和常规 Linux 系统的 SSH 连接一样了,退出则直接输入 exit 或者 Ctrl+P+Q 即可,但要注意 exit 指令退出后,容器也会一起停止运行,除非创建时设定了自动重启参数。 Ctrl+P+Q 则不会导致容器一起退出。
另一个进入容器的命令为 docker exec
,此命令不能单独使用,后面需要跟随参数,常用的有-i
和-t
等,-i 表示终端输入功能开启,-t 表示分配一个终端窗口。跟随的参数大部分和 docker run
可用的一样,具体可参考官方文档。
如果单独使用-i
,那就等同于向容器里发送指令,物理机的终端界面里只会反馈回来指令运行结果,类似于一半盲操作,与-t
连用后分配了终端,才会变成普通的 SSH 样式。
此命令格式为: docker exec 参数 容器 ID 或名称 对容器内部的指令
比如同样是进入名为 centos 的容器,那么就运行:
docker exec -it centos bash
退出容器的方法和上面一样,直接输入 exit 或者 Ctrl+P+Q 快捷键组合均可,区别是以 exec 指令进入的容器,任意方法退出均不会导致容器停止运行。
容器打包:
在对容器进行了修改后,如果需要将容器重新打包成镜像,保存起来或者作为备份,那么可以使用 docker commit
命令进行打包,命令格式为:
docker commit 容器 ID 或名称 新镜像名称
比如想把名为 centos 的镜像打包起来,新镜像名称为 new-centos,那么可以运行:
docker commit centos new-centos
运行结果如下图所示,可以看到镜像列表里出现了创建的新镜像:
打包命令本身也可以跟随多种参数,不过一般只是入门的情况下,也不想着给仓库提交镜像这类,直接打包命令基本能满足大多数情况。
Docker 容器虽然类似虚拟机,但如果想要物理机和容器内系统进行文件传输时,还是有点区别的。
如果只是单纯对容器内的系统进行本地文件管理的话,那么用 docker exec
这类命令直接进入容器内部,用常规命令行进行管理即可。
但很多时候都是突然遇到某个需求,需要外部物理机向容器内部拷贝文件,或者容器里的文件拷贝出来,而 Docker 的缺点就是容器一旦创建后,再修改参数比较麻烦,而创建时显然不可能预估到后续所有需求,提前映射所有目录啥的。
好在 Docker 本身就提供了对应的文件传输命令,那就是 copy 和 add 指令,这两个指令用法相似但又存在部分不同。
copy 命令顾名思义,是基础的指令,可以用于容器和外界物理机之间拷贝文件,命令格式视不同目标,可以有多种组合:
docker cp 容器 ID 或名称: 容器目录 物理机目录
docker cp 物理机目录 容器 ID 或名称: 容器目录
比如创建了一个镜像,容器 ID 为 b2860e937844。
比如想将物理机的/home/lishuma 目录拷贝到容器的/home 目录下,那么就运行:
docker cp /home/lishuma b2860e937844:/home/
运行后进入容器查看对应目录,可以看到了对应的 lishuma 目录:
如果是把上一条命令结尾斜杠去掉,那么意思就变成了将物理机/home/lishuma 目录拷贝到容器根目录中,并且拷贝进去的目录重命名为 home。
接着反过来,如果要将容器 b2860e937844 的/home/lishuma 目录(里面有一个 test.zip 文件)拷贝到物理机的/home/lishuma/test 目录中,那么命令格式为:
docker cp 容器 ID 或名称: 容器目录 物理机目录
运行:
docker cp b2860e937844:/home/lishuma /home/lishuma/test/
运行后可以看到物理机对应目录下出现了所需的目录:
和上面一样,反过来容器向外拷贝的命令如果去掉最后一个斜杠,那么意思同样是变成拷贝出来后,重命名为 test。
注意事项:
- 如果源路径是个文件,且目标路径是以 / 结尾, 则 docker 会把目标路径当作一个目录,会把源文件拷贝到该目录下(如果不存在则自动创建)。
- 如果目标文件是个存在的文件,会被源文件覆盖,文件名则为目标文件名。
Docker 文件交互的另一个命令为 add,简单来说就是 copy 命令的进阶版,用法完全一样,但 add 有两个特殊功能,可以传输后自动解压压缩包,以及从 url 地址下载文件(不过下载文件这用法官方文档里不推荐使用)。
结语:
关于基本的 Docker 操作,本文就初步介绍到这里了,掌握上面说的各种指令后,对于镜像和容器的基本管理足以满足大部分时候的需求。
Docker 本身并不难,只是缺乏简单明了易懂的教程说明,很多指令其实都是固定模板,照着输入几次外加自己多研究即可。在实践中也尽量大胆尝试,毕竟 Docker 指令输错了并不会造成什么严重后果,大不了就是报错而已,常见问题靠搜索引擎也基本可以解决。