什么是容器?
- 一种虚拟化的方案
- 操作系统级别的虚拟化
- 只能运行相同或相似内核的操作系统
- 依赖于 Linux 内核特性:Namespace 和 Cgroups
更多介绍可以参考 Docker 官网说明:什么是容器?
什么是 Docker?
Docker 是基于 Go 语言实现的开源容器项目,是开发人员和系统管理员使用容器开发,部署和运行应用程序的平台。
为什么使用 Dokcer?
举个简单的例子,假设用户试图基于最常见的 LAMP(Linu+xApache +MySQL+PHP
)组合来构建一个网站。
按照传统的做法,首先,需要安装Apache
、MySQL
和PHP
以及它们各自运行所依赖的环境;之后分别对它们进行配置(包括创建合适的用户、配置参数等);经过大量的操作后,还需要进行功能测试,查看是否工作正常;若不正常,则进行调试追踪,这将耗费更多的时间并带来不可控的风险。
可以想象,若应用数目变多,事情会变得更加难以处理。
更为可怕的是,一旦需要服务器迁移(例如从亚马逊云迁移到其他云),则往往需要对每个应用都进行重新部署和调试。
这些琐碎而无趣的“体力活”,极大地降低了工作效率。
究其根源,是这些应用直接运行在底层操作系统上,无法保证同一份应用在不同的环境中行为一致。
而 Docker 提供了一种更为聪明的方式,通过容器来打包应用,解耦应用和运行平台。意味着迁移的时候,只需要在新的服务器上启动需要的容器就可以了,无论新旧服务器是否是同一类型的平台。这无疑将节约大量的宝贵 时间,并降低部署过程出现问题的风险。
通过 Docker 可以很轻松地部署应用程序,这意味着:
- 更快的项目部署速度
- 更高效的开发生产力
- 更低的 IT 基础设施成本
- 更快速高效的开发生命周期
更多介绍请参考 Docker 官网:为什么使用 Docker ?
容器和虚拟机的比较
一个容器中运行原生 的 Linux 并且共享主机与其它容器的内核。它运行一个独立的进程,不占用任何其他可执行文件的内存,这使得它非常轻量级。
相比之下,虚拟机(VM)运行一个完整的“客户”操作系统,通过虚拟机管理程序对主机资源进行虚拟访问。通常,VM提供的环境比大多数应用程序需要的资源更多,如下是容器和 VM 的比较:
容器和虚拟机的性能对比如下表:
特性 | 容器 | 虚拟机 |
---|---|---|
启动速度 | 秒级 | 分钟级 |
性能 | 接近原生 | 较弱 |
内存代价 | 很小 | 较多 |
磁盘使用 | 一般为 MB | 一般为 GB |
运行密度 | 单机支持上千个容器 | 一般几十个 |
隔离性 | 安全隔离 | 完全隔离 |
迁移性 | 优秀 | 一般 |
Docker 应用场景
- 使用 Docker 容器开发、测试、部署服务
- 创建隔离的运行环境
- 搭建测试环境
- 构建多用户的平台即服务(PaaS)基础设施
- 提供软件即服务(SaaS)应用程序
- 高性能、超大规模的宿主机部署
Docker 三大核心概念
Docker 使用客户端-服务器 ( C/S ) 架构模式,使用远程 API 来管理和创建 Docker 容器。
Docker 镜像可以用来创建 Docker 容器。
镜像与容器的关系类似于面向对象编程中的类与对象。
Docker 的大部分操作都围绕着它的三大核心概念——镜像、容器和仓库而展开,下面将分别介绍:
Docker Image (镜像)
在 Docker 中,可以通过获取的镜像来启动容器。
一个镜像即一个可执行的包,其中包括运行应用程序所需所有内容的代码,运行时间,库,环境变量和配置文件。
镜像是创建 Docker 容器的基础。
通过版本管理和增量的文件系统,Docker 提供了一套十分简单的机制来创建和更新现有的镜像,用户可以从网上下载一个已经做好的应用镜像直接使用。
Docker Container (容器)
一个容器是一个镜像的运行时实例。
换个角度来看,镜像是静态的只读文件,而容器则带有运行时需要的可写文件层。
若认为虚拟机是模拟运行的一整套操作系统(包括内核、应用运行态环境和其他系统环境)和跑在上面的应用,那么 Docker 容器就是独立运行的一个(或一组)应用,以及它们必需的运行环境。
当镜像被执行时将会进入内存(这代表一个状态的镜像或一个用户进程)。
可以使用docker ps
命令查看正在运行的容器列表。
Docker Registry (仓库)
Docker 仓库类似于代码仓库,它是 Docker 集中存放镜像文件的场所。
根据所存储的镜像公开分享与否, Docker 仓库可以分为:
- 公开仓库 ( Public )
- 私有仓库 ( Private )
目前,最大的公开仓库是官方提供的 Docker Hub ,其中存放了数量庞大的镜像供用户下载。
当然,用户若不希望公开分享自己的镜像文件, Docker 也允许用户在本地网络内创建一个只能自己访问的私有仓库。
当用户创建了自己的镜像之后可以使用 push 命令将它上传到指定的公有或者私有仓库。
这样用户下次在另外一台机器上使用该镜像时,只需要将其从仓库上 pull 下来即可使用。
安装:CentOS 7一键安装 Docker 脚本
本人服务器使用的是 Centos 7 ,,所以将 Docker 安装命令写成一键安装脚本,默认安装最新稳定版。
首先在命令行输入:1
vim dockerInstall.sh
之后将下面代码复制进去:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# 移除掉旧的版本
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
# 删除所有旧的数据
sudo rm -rf /var/lib/docker
# 安装依赖包: yum-utils 提供了 yum-config-manager 效用,而 device-mapper-persistent-data 和 lvm2 被 devicemapper 存储驱动程序需要。
sudo yum install -y yum-utils \
device-mapper-persistent-data \
lvm2
# 添加源,使用阿里云镜像下载速度更快
sudo yum-config-manager \
--add-repo \
http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
# 配置缓存
sudo yum makecache fast
# 安装最新版本的Docker CE和containerd,或者转到下一步安装特定版本:
sudo yum install docker-ce docker-ce-cli containerd.io
# 配置镜像加速器
sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<-'EOF'
{
"registry-mirrors": ["http://hub-mirror.c.163.com"]
}
EOF
# 启动 docker 并设置开机启动
sudo systemctl start docker
sudo systemctl enable docker
# 增加用户组 docker
sudo groupadd docker
# root 加入 docker 用户组
sudo gpasswd -a root docker
# 当前用户加入 docker 用户组,使当前用户拥有执行权限
sudo gpasswd -a $USER docker
sudo chgrp docker /var/run/docker.pid
sudo chgrp docker /var/run/docker.sock
sudo chgrp -R docker /var/run/docker
# 重启 docker
sudo systemctl restart docker
之后执行该文件即可:1
sh dockerInstall.sh
注意
:国内访问 Docker 太慢,一般会配置加速器,此处配置的加速器是 163 的加速器:1
http://hub-mirror.c.163.com
当然也可以配置阿里云的加速器(推荐哦!)
安装过程中若出现提示,一直回车或输入y
即可。
权限问题
非 root 用户运行 docker 命令报如下错误:1
Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Get "http://%2Fvar%2Frun%2Fdocker.sock/v1.24/images/search?limit=25&term=redis": dial unix /var/run/docker.sock: connect: permission denied
通过将用户添加到 docker 用户组可以将 sudo 去掉:1
2
3
4
5
6# 添加 docker 用户组
sudo groupadd docker
# 将登陆用户加入到 docker 用户组中
sudo gpasswd -a $USER docker
# 更新用户组
newgrp docker
测试 Docker 版本
运行docker --version
并确保您拥有受支持的Docker版本:
1 | docker --version |
运行docker info
以查看有关Docker安装的更多详细信息:
1 | docker info |
测试 Docker 安装是否有效
①通过运行简单的 Docker 镜像hello-world
测试您的安装是否有效 :
1 | $ docker run hello-world |
②列出下载到您的计算机的镜像如hello-world
:
1 | docker image ls |
③列出hello-world
在显示其消息后退出的容器(由镜像生成)。若它仍在运行,您将不需要--all
选项:1
2
3
4docker container ls --all
CONTAINER ID IMAGE COMMAND CREATED STATUS
54f4984ed6a8 hello-world "/hello" 20 seconds ago Exited (0) 19 seconds ago
配置 Docker 服务
为了避免每次使用 Docker 命令都要用特权身份,可以将当前用户加入安装中自动创建的 Docker 用户组:1
$ sudo usermod -aG docker USER_NAME
Docker 容器操作
使用 Docker 镜像
Docker 运行容器前需要本地存在对应的镜像,若镜像没保存在本地, Docker 会尝试先从默认镜像仓库下载(默认使用 Docker Hub公共注册服务器中的仓库),用户亦可通过配置来使用自定义的镜像仓库。
搜寻镜像
使用docker search
命令可以搜索远端仓库中共享的镜像,默认搜索官方仓库中的镜像。
比如搜索mysql
的镜像时:1
2
3
4
5
6
7
8
9docker search mysql
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
mysql MySQL is a widely used, open-source relation… 8330 [OK]
mariadb MariaDB is a community-developed fork of MyS… 2850 [OK]
mysql/mysql-server Optimized MySQL Server Docker images. Create… 620 [OK]
percona Percona Server is a fork of the MySQL relati… 438 [OK]
centurylink/mysql Image containing mysql. Optimized to be link… 60 [OK]
centos/mysql-57-centos7 MySQL 5.7 SQL database server 56
...
可以看到返回了很多包含关键字的镜像,其中包括:
- 镜像名字
- 描述
- 星级(表示该镜像的受欢迎程度))
- 是否由官方创建
- 是否自动创建等
注
:默认的输出结果将按照星级评价进行排序。
获取镜像
搜寻到想要的镜像后,自然是拉取下来啦!
可以使用docker pull
命令直接从 Docker Hub 镜像源来下载镜像。该命令的格式为:1
docker pull [NAME] [TAG]
其中,2 个参数的说明:
NAME
:镜像仓库的名称(用来区分镜像)TAG
:镜像的标签(往往用来表示版本信息)。若不显式指定,默认选择latest
标签,这将下载仓库中最新版本的镜像。因为最新版意味着非稳定版,从稳定性上考虑,在生产环境中应该指定TAG
。
下面我们举个栗子说明一下,从 Docker Hub 拉取个最新的ubuntu
镜像:1
2
3
4
5
6
7
8
9$ docker pull ubuntu
Using default tag: latest
latest: Pulling from library/ubuntu
5b7339215d1d: Pull complete
14ca88e9f672: Pull complete
a31c3b1caad4: Pull complete
b054a26005b7: Pull complete
Digest: sha256:9b1702dcfe32c873a770a32cfd306dd7fc1c4fd134adfb783db68defc8894b3c
Status: Downloaded newer image for ubuntu:latest
该命令实际上下载的就是ubuntu:latest
镜像.
下载过程中可以看出,镜像文件一般由若干层(layer)组 成,6c953ac5d795
这样的串是层的唯一id
(实际上完整的id
包括256
比特,由64
个十六进制字符组成)。 docker pull
命令下载时会获取并输出镜像的各层信息。当不同的镜像包括相同的层时,本地仅存储层的一份内容,以减小需要的存储空间。
注
:严格地讲,镜像的仓库名称中还应该添加仓库地址(即registry
,注册服务器)作为前缀,只是我们默认使用的是 Docker Hub 服务,该前缀可以忽略。
例如,docker pull ubuntu
命令相当于docker pull registry.hub.docker.com/ubuntu
命令,即从默认的注册服务器 Docker Hub Registry 中的 ubuntu 仓库来下载标记为 14.04 的镜像。
若从非官方的仓库下载,则需要在仓库名称前指定完整的仓库地址。
下载镜像到本地后,即可随时使用该镜像了。
查看镜像信息
列出镜像命令—— images
docker images
命令可以列出本地主机上已有镜像的基本信息。1
2
3
4
5$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu 16.04 13c9f1285025 11 days ago 119MB
ubuntu latest 4c108a37151f 11 days ago 64.2MB
mysql latest c7109f74d339 2 weeks ago 443MB
在列出的信息中,可以看到以下几个字段信息:
REPOSITORY
:来自于哪个仓库,比如ubuntu
仓库用来保存ubuntu
系列的基础镜像;TAG
:镜像的标签信息,比如16.04、latest
用来标注不同的版本信息。标签只是标记,并不能标识镜像内容;IMAGE ID
:镜像的 ID(唯一标识镜像),如4c108a37151f
标识为ununtu
镜像;CREATED
:创建时间,说明镜像最后的更新时间;SIZE
:镜像大小,优秀的镜像往往体积都较小。
其中镜像的 ID 信息十分重要,它唯一标识了镜像。
在使用镜像 ID 的时候,一般可以使用该 ID 的前若干个字符组成的可区分串来替代完整的ID。
TAG
信息用来标记来自同一个仓库的不同镜像。例如ubuntu
仓库中有多个镜像,通过 TAG 信息来区分发行版本,包括10.04、12.04、12.10、13.04、14.04、16.04
等标签。
添加镜像标签命令—— tag
为了方便在后续工作中使用特定镜像,还可以使用docker tag
命令来为本地镜像任意添加新的标签。例如添加一个新的myubuntu:latest
镜像标签:1
2
3
4
5
6
7$ docker tag ubuntu:latest myubuntu:latest
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu 16.04 13c9f1285025 11 days ago 119MB
myubuntu latest 4c108a37151f 11 days ago 64.2MB
ubuntu latest 4c108a37151f 11 days ago 64.2MB
mysql latest c7109f74d339 2 weeks ago 443MB
再次使用docker images
列出本地主机上镜像信息,可以看到多了一个拥有myubuntu:latest
标签的镜像.之后,用户就可以直接使用myubuntu:latest
来表示这个镜像了。
细心的读者可能注意到,这些myubuntu:latest
镜像的 ID 跟ubuntu:latest
完全一致。它们实际上指向同一个镜像文件,只是别名不同而已。
换而言之,docker tag
命令添加的标签实际上起到了类似链接的作用。
查看详细信息命令—— inspect
docker inspect
命令可以获取该镜像的详细信息,包括制作者、适应架构、各层的数字摘要,容器 IP 地址等,这将返回一个 JSON 格式的消息:1
2
3
4
5
6
7
8
9$ docker inspect mysql
[
{
"Id": "sha256:c7109f74d339896c8e1a7526224f10a3197e7baf674ff03acbab387aa027882a",
"RepoTags": [
"mysql:latest"
],
"RepoDigests": [
...省略...
查看镜像历史命令—— history
既然镜像文件由多个层组成,那么怎么知道各个层的内容具体是什么呢?
这时候可以使用history
子命令,该命令将列出各层的创建信息。
例如,查看mysql
镜像的创建过程,可以使用如下命令:1
2
3
4
5
6
7$ docker history mysql
IMAGE CREATED CREATED BY SIZE COMMENT
c7109f74d339 2 weeks ago /bin/sh -c #(nop) CMD ["mysqld"] 0B
<missing> 2 weeks ago /bin/sh -c #(nop) EXPOSE 3306 33060 0B
<missing> 2 weeks ago /bin/sh -c #(nop) ENTRYPOINT ["docker-entry… 0B
<missing> 2 weeks ago /bin/sh -c ln -s usr/local/bin/docker-entryp… 34B
...
删除镜像
使用docker rmi
命令可以删除镜像,命令格式为1
docker rmi IMAGE[IMAGE...]
其中[IMAGE]
可以为镜像标签或镜像 ID。
镜像标签删除
例如,要删除掉myubuntu:latest
镜像,即标签为latest
的myubuntu
镜像可以使用如下命令:1
2$ docker rmi myubuntu:latest
Untagged: myubuntu:latest
读者可能会担心,本地的ubuntu:latest
镜像是否会受此命令的影响。无需担心,当同一个镜像拥有多个标签的时候,docker rmi
命令只是删除该镜像多个标签中的指定标签而已,并不影响镜像文件。因此上述操作相当于只是删除了镜像4c108a37151f
的一个标签而已。
但当镜像只剩下一个标签的时候就要小心了,此时再使用docker rmi
命令会彻底删除镜像。
镜像 ID 删除
当使用docker rmi
命令,并且后面跟上镜像的 ID (也可以是能进行区分的部分 ID 串前缀)时,会先尝试删除所有指向该镜像的标签,然后删除该镜像文件本身。
下面为删除镜像 ID 为4c108a37151f
的ubuntu
镜像,会先删除所有指向该镜像的标签,然后删除该镜像文件本身:
1 | $ docker rmi ubuntu 4c108a37151f |
注意:
当有该镜像创建的容器存在时,镜像文件默认是无法被删除的,此时 Docker 会提示有容器正在运行,无法删除,但若要想强行删除镜像,可以使用- f
参数。1
$ docker rmi -f ubuntu:latest
通常并不推荐使用-f
参数来强制删除一个存在容器依赖的镜像。 正确的做法是,先删除依赖该镜像的所有容器,再来删除镜像。
存出和载入镜像、上传镜像暂略
使用 Docker 容器
新建容器
docker create [commond] [image]
命令可以新建一个容器,例如:1
2$ docker create -it ubuntu:latest
5bbc639c21aab99607cb60a92e86b18ccdc47d793f52c1c590aa49d0268dfe2a
当命令执行完之后,会返回一条字符串,这代表创建容器的 ID。
create
命令和后续的run
命令支持的选项参数都十分复杂,主要包括如下几大类:与容器运行模式相关、与容器和环境配置相关、与容器资源限制和安全保护相关。
前面的-it
参数指的是创建交互式容器:
-i --interactive=true|false
:表示运行容器,即告诉 Docker 守护进程始终打开标准输入,默认是false
-t --tty = true|false
:表示容器启动后会进入命令行,即为容器分配一个 tty 终端, 默认是false
启动容器
新建的容器处于停止状态,可用docker start [容器ID]
命令来启动它:1
2$ docker start 5bbc639c21aab99607cb60a92e86b18ccdc47d793f52c1c590aa49d0268dfe2a
5bbc639c21aab99607cb60a92e86b18ccdc47d793f52c1c590aa49d0268dfe2a
当然,可以简写:1
2$ docker start 5bbc639c
5bbc639c21aab99607cb60a92e86b18ccdc47d793f52c1c590aa49d0268dfe2a
查看容器状态
docker ps [OPTIONS]
命令可用来查看容器状态信息:1
2
3docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
5bbc639c21aa ubuntu:latest "/bin/bash" 3 hours ago Up 4 seconds angry_proskuriakova
该命令后面可以添加参数,不同的参数意义不同:
-a
(常用):显示所有(包括未运行)的容器-q
(常用):静默模式,只显示容器编号-l
(常用) :显示最近创建的容器-f
:根据条件过滤显示的内容--format
:指定返回值的模板文件-n
:列出最近创建的 n 个容器--no-trunc
:不截断输出-s
:显示总的文件大小
新建并启动容器
除了创建容器后再通过start
命令来启动他,也可以直接新建并启动容器。
相关命令为docker run
,等价于先执行docker create
命令,再执行docker start
命令。1
docker run -it ubuntu:16.04
其中,参数作用:
-d
:创建一个守护式容器再后台运行(这样创建容器后不会自动登录容器,若加-it
参数,创建后就会自动进入容器)-i
:表示运行容器,即让容器的标准输入保持打开-t
:表示容器启动后会进入命令行,即分配一个伪终端并绑定到容器的标准输入上-p
:表示端口映射,如-p 33306:3306
,前者是宿主机端口,后者是容器内的映射端口,可以做多个映射-it
加起来即让运行的容器实现”对话”的能力,上面命令即运行交互式的容器
执行该命令时, Docker 在后台运行的标准操作包括:
- 检查本地是否存在指定的镜像,不存在就从公有仓库下载
- 利用镜像创建一个容器,并启动该容器
- 分配一个文件系统给容器,并在只读的镜像层外面挂载一层可读写层
- 从宿主主机配置的网桥接口中桥接一个虚拟接口到容器中
- 从网桥的地址池配置一个 IP 地址给容器
- 执行用户指定的应用程序
- 执行完毕后容器被自动终止
自定义容器名
有时候我们想要给创建的容器定义一个名字以方便后续操作,可以使用如下命令:1
docker run --name=自定义名 -it [IMAGE]
下面为一个示例:1
2
3
4
5
6
7
8
9$ docker run --name=myubuntu -it ubuntu
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
30391c72e826 ubuntu "/bin/bash" 9 seconds ago Up 8 seconds myubuntu
$ docker attach myubuntu
root@30391c72e826:/# exit
exit
$ docker rm myubuntu
myubuntu
守护式容器
守护式容器指的是退出容器时让容器可以一直挂在后台,能够长期运行,没有交互式会话,适合运行应用程序和服务。
当我们通过命令启动交互式容器后,通过ctrl+p ctrp+q
退出容器即可将其挂在后台。
终止容器
docker stop
命令可用来终止一个运行中的容器:1
2$ docker stop 5bbc639c21aa
5bbc639c21aa
该命令首先向容器发送SIGTERM
信号,等待一段超时时间(默认为 10 秒)后, 再发送SIGKILL
信号来终止容器。
也可使用docker kill
命令会直接发送 SIGKILL 信号来强行终止容器(不推荐):1
2$ docker kill 5bbc639c21aa
5bbc639c21aa
此外,当 Docker 容器中指定的应用终结时,容器也会自动终止。
例如启动了一个终端的容器,用户通过exit
命令或ctrl+d
来退出终端时,所创建的容器立刻终止,处于stopped
状态。
重启容器
docker restart
命令可以将一个运行态的容器先终止,然后再重新启动:1
2$ docker restart 5bbc639c21aa
5bbc639c21aa
进入容器内执行操作
在使用-d
参数时,容器启动后会进入后台,用户无法看到容器中的信息,也无法进行操作。
此时若需进入容器进行操作,有多种方法,包括使用官方的attach
或exec
命令,以及第三方的nsenter
工具等。
attach 命令(了解)
attach
是 Docker 自带的命令,命令格式为:1
docker attach [--detach-keys[=[]]] [--no-stdin] [--sig-proxy[=true]] CONTAINER
支持三个主要选项(一般默认即可,即不加参数选项):
--detach-keys[=[]]
:指定退出attach
模式的快捷键序列,默认是CTRL+p
+CTRL+q
;--no-stdin=true|false
:是否关闭标准输入,默认是保持打开;--sig-proxy=true|false
:是否代理收到的系统信号给应用进程,默认为 true。
下面为运行ubuntu
容器并进入该容器的代码:1
2
3
4$ docker run -dit ubuntu
8b18614a220bbab7906204c5031ba73c66ff32e9fd277aed7219ec0ff5cec55b
$ docker attach 8b18
root@8b18614a220b:/#
但是使用attach
命令有时候并不方便。
当多个窗口同时用attach
命令连到同一个容器的时候,所有窗口都会同步显示。当某个窗口因命令阻塞时,其他窗口也无法执行操作了。
exec 命令(推荐)
Docker 从 1.3.0 版本起提供了一个更加方便的 exec 命令,可以在容器内直接执行任意命令。该命令的基本格式为:1
docker exec [-d|--detach] [--detach-keys[=[]]] [-i|--interactive] [--privileged] [-t|--tty] [-u|--user[=USER]] CONTAINER COMMAND [ARG...]。
比较重要的参数有:
-i,--interactive=true|false
:打开标准输入接受用户输入命令,默认为false
;-t,--tty=true|false
:分配伪终端,默认为false
;--privileged=true|false
:是否给执行命令以高权限,默认为false
;-u,--user=""
:执行命令的用户名或ID。
下面为一个例子,以后台模式创建并启动一个交互式容器,并启动一个bash
:1
2
3
4$ docker run -dit ubuntu
5a35b974b0f055e764f1af83eb992ae8bfbc643026376f5c9da76793cf1b6f79
$ docker exec -it 5a35 /bin/bash
root@5a35b974b0f0:/#
可以看到,一个 bash 终端打开了,在不影响容器内其他应用的前提下, 用户可以很容易与容器进行交互。
通过指定-d
参数以后台模式来创建并启动容器,通过指定-it
参数来保持标准输入打开,并且分配一个伪终端。
删除容器
可以使用docker rm
命令来删除处于终止或退出状态的容器,命令格式为:1
docker rm[-f|--force][-l|--link][-v|--volumes]CONTAINER[CONTAINER...]。
主要支持的选项包括:
-f,--force=false
:是否强行终止并删除一个运行中的容器;-l,--link=false
:删除容器的连接,但保留容器;-v,--volumes=false
:删除容器挂载的数据卷。
例如,查看处于终止状态的容器,并删除:1
2
3
4
5
6
7docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
5a35b974b0f0 ubuntu "/bin/bash" 8 minutes ago Exited (0) 17 seconds ago goofy_goldstine
a60c99b35d7a ubuntu "/bin/bash" 9 minutes ago Up 9 minutes admiring_edison
dd4bd9645748 ubuntu "/bin/bash" 10 minutes ago Exited (0) 9 minutes ago suspicious_einstein
$ docker rm 5a35
5a35
默认情况下,docker rm
命令只能删除处于终止或退出状态的容器,并不能删除还处于运行状态的容器。
若要直接删除一个运行中的容器,可以添加-f
参数。此种做法不推荐,一般应该先停止容器之后再删除容器。
若想要删除 Docker 中所有已经停止的容器,可以使用如下命令:1
docker container prune
文件拷贝出入容器
若我们需要将文件拷贝到容器内,则可以使用 cp 命令:1
2
3
4
5
6
7$ docker run -dit --name=mycentos centos:7
f041ef8a57623dcb45bee932d25e908e9eb93edcc00b0cb2c94fb292563f3ed1
$ docker cp abc.txt mycentos:/usr/local
$ docker exec -it mycentos /bin/bash
[root@f041ef8a5762 /]# cd /usr/local/
[root@f041ef8a5762 local]# dir
abc.txt bin etc games include lib lib64 libexec sbin share src
也可以将文件从容器内拷贝出来到当前目录:1
$ docker cp mycentos:/root/anaconda-ks.cfg test_cp.cfg
目录挂载
我们可以在创建容器的时候,将宿主机的目录与容器内的目录进行映射,这样我们就可以通过修改宿主机某个目录的文件从而去影响容器。
创建容器时添加-v
参数,后面紧跟宿主机目录:容器目录
(宿主机目录的路径必须为绝对路径),如:
1 | docker run -dit --name=mycentos -v /usr/local/myhtml:/usr/local/myhtml centos:7 |
当在宿主机的myhtml
目录创建文件后,容器中的myhtml
目录也会多出那个文件。
注意:
请先将这两个目录保持同步。若你共享的是多级的目录,可能会出现权限不足的提示。因为 Centos 7 中的安全模块把权限禁掉了,需要添加参数--privileged=true
来解决这个问题。
导出和导入容器
某些时候,需要将容器从一个系统迁移到另外一个系统,此时可以使用 Docker 的导入和导出功能。
导出容器
导出容器是指导出一个已经创建的容器到一个文件,无论当前该容器是否处于运行状态。
可以使用docker export
命令,该命令的格式为:1
docker export[-o|--output[=""]]CONTAINER
其中,可以通过-o
选项来指定导出的tar
文件名,也可以直接通过重定向来实现。
首先查看所有的容器(一个是运行状态,一个是停止状态):1
2
3
4$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
53a6205630a6 ubuntu "/bin/bash" About a minute ago Exited (0) 5 seconds ago stoic_kowalevski
4e366ed1e39f ubuntu "/bin/bash" About a minute ago Up About a minute amazing_lewin
分别导出4e366ed1e39f
容器和53a6205630a6
容器到文件test_for_run.tar
文件和test_for_stop.tar
文件(使用了不同的方式导出):1
2
3
4
5$ docker export -o test_for_run.tar 4e3
$ docker export 53a > test_for_stop.tar
$ ll
-rw------- 1 wksky staff 63M 6 30 21:54 test_for_run.tar
-rw-r--r-- 1 wksky staff 63M 6 30 21:54 test_for_stop.tar
之后,可将导出的 tar 文件传输到其他机器上,然后再通过导入命令导入到系统中,从而实现容器的迁移。
导入容器
导出的文件使用docker import
命令导入后又将变成镜像,该命令格式为:1
2docker import [-c|--change[=[]]] [-m|--message[=MESSAGE]] file|URL|-[REPOSITORY
[:TAG]]
下面将导出的test_for_run.tar
文件导入到系统中:1
2
3
4
5$ docker import test_for_run.tar - test/ubuntu:v1.0
sha256:9d37a6082e97aec4c3239c67f615f31849d072bcd9dc25d733621854983267fa
$ docker images
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
test/ubuntu v1.0 9d37a6082e97 About a minute ago 171.3 MB
查看容器日志
若想要获取容器的输出信息(日志),可以如下使用如下命令:1
docker logs container_name
Docker 定制镜像
Dockerfile 是一个文本格式的配置文件,用户可以使用 Dockerfile 来快速创建自定义的镜像。
基本结构
Dockerfile 由一行行命令语句组成,并且支持以#
开头的注释行。
一般而言,Dockerfile 分为四部分:
- 基础镜像信息
- 维护者信息
- 镜像操作指令
- 容器启动时执行指令
下面介绍一些比较常见的命令。
FROM
FROM 指定所创建镜像的基础镜像,若本地不存在,则默认会去 Docker Hub 下载指定镜像。
FROM 语法格式如下:1
2
3
4# 3 种写法
FROM image
FROM image:tag
FROM image@digest
MAINTAINER
MAINTAINER 指定维护者信息,格式如下:1
MAINTAINER image_author@docker.com
该信息会写入生成镜像的 Author 属性域中。
WORKDIR
WORKDIR 为后续的 RUN、CMD 和 ENTRYPOINT 指令配置工作目录。
可以使用多个 WORKDIR 指令,后续命令如果参数是相对路径,则会基于之前命令指定的路径。例如:1
2
3
4WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd
则最终路径为 /a/b/c
EXPOSE
EXPOSE 用于声明镜像内服务所监听的端口。比如:1
2
3EXPOSE 3306
# 允许同时声明多个
EXPOSE 3306 8080
注意,该指令只是起到声明作用,并不会自动完成端口映射。
在启动容器时需要使用-P
,Docker 主机会自动分配一个宿主机的临时端口转发到指定的端口;使用-p
则可以具体指定哪个宿主机的本地端口会映射过来。
ENV
ENV 指定环境变量,在镜像生成过程中会被后续 RUN 指令使用,在镜像启动的容器中也会存在。
示例代码如下:1
2ENV JAVA_HOME /user/local/openjdk-8
ENV PATH $JAVA_HOME/bin/bin:$PATH
ADD
格式为 ADD <src> <dest>
。
该命令将复制指定的<src>
到容器中的<dest>
。其中<src>
可以是Dockerfile
所在目录的一个相对路径(文件或目录);也可以是一个 URL;还可以是一个tar
文件(自动解压为目录)。
COPY
COPY 复制本地主机的(Dockerfile 所在目录的相对路径)文件或目
录下的内容到镜像中的目标路径下。
若目标路径不存在时,会自动创建。路径同样支持正则格式。当使用本地目录为源目录时,推荐使用 COPY。
RUN
RUN 运行指定命令,比如:1
2
3RUN apt-get update \
&& apt-get install -y libsnappy-dev zlib1g-dev libbz2-dev \
&& rm -rf /var/cache/apt
注意哦
:该命令可以多次使用,每次使用都会构建一层镜像,推荐合并它。
CMD
CMD 指令用来指定启动容器时默认执行的命令。
举个栗子, tomcat 容器启动时应该执行startup.sh
来启动 tomcat
注意哦
:每个 Dockerfile 只能有一条 CMD 命令,若指定了多条,只有最后一条会被执行。
ENTRYPOINT
有两种格式:
ENTRYPOINT ["executable", "param1", "param2"]
ENTRYPOINT command param1 param2 ( shell中执行)
配置容器启动后执行的命令,并且不可被docker run
提供的参数覆盖。
注意:每个Dockerfile
中只能有一个ENTRYPOINT
,当指定多个ENTRYPOINT
时,只有最后一个生效。
创建镜像
下面我们编写一个基于jdk8
镜像上打一个 Java 应用包的 Dockerfile 文件:1
2
3
4
5
6
7
8FROM java:8-alpine
ADD start.sh start.sh
ADD target/*.jar /app.jar
RUN chmod +x start.sh
ENV LANG=C.UTF-8
ENV TZ=Asia/Shanghai
EXPOSE 8080
ENTRYPOINT ["sh", "./start.sh"]
编写完 Dockerfile ,就可以通过docker build
命令来创建镜像。比如1
docker build -t javaApp .
其中:
-t
代表给镜像起名,格式为name:tag
,不指定tag
时默认为latest
.
代表指定Dockerfile
所在目录,即在当前目录找到 Dockerfile 配置文件,并指定上下文目录并打包到 Docker Server
注意
要想定制适合自己、高效方便的镜像,最好:
- 精简镜像用途:尽量让每个镜像的用途都比较集中、单一,避免构造大 而复杂、多功能的镜像
- 选用合适的基础镜像:过大的基础镜像会造成生成臃肿的镜像,一般推荐较为小巧的 debian 镜像
- 提供足够清晰的命令注释和维护者信息:Dockerfile 也是一种代码,需要考虑他人后续的扩展使用
- 正确使用版本号:使用明确的版本号信息,如 1.0,2.0,而非 latest, 将避免内容不一致可能引发的惨案;
- 减少镜像层数:若希望所生成镜像的层数尽量少,则要尽量合并指令,比如多个 RUN 指令可以合并为一条;
- 及时删除临时文件和缓存文件:特别是在执行apt-get指令后,
/var/cache/apt
下面会缓存一些安装包 - 提高生成度:如合理使用缓存,减少内容目录下的文件,或使用
.dockerignore
文件指定等 - 调整合理的指令顺序:在开启缓存的情况下,内容不变的指令尽量放在前面,这样可以尽量复用
- 减少外部源的干扰:若确实要从外部引入数据,需要指定持久的地址,并带有版本信息,让他人可以重复而不出错
应用部署(推荐使用 docker-compose)
部署 MySQL 数据库
MySQL 数据库在 Docker 的部署步骤如下:
① 拉取 mysql 镜像:1
docker pull mysql:5.7
② 创建 mysql 容器:1
docker run -di --name=mysql57 -p 3308:3306 -e MYSQL_ROOT_PASSWORD=123456 mysql:5.7
其中:
-p
:端口映射,格式为宿主机映射端口:容器运行端口,上面将容器的 3306 端口映射到宿主机的3308
端口,之后即可在宿主机上通过3308
端口连接该mysql
-e
:添加环境变量,MYSQL_ROOT_PASSWORD
指定root
用户的登录密码
③ 远程登录 mysql :1
mysql -h xx.xx.xx.xx -P 3306 -uroot -p
注
:-h
后的xx.xx.xx.xx
为要连接宿主机的 IP,-P
指定端口为3306
中文乱码
Docker MySQL 镜像默认编码大部分是 latin1,写入中文数据会以???
显示,需要修改编码格式为UTF-8
修改时我们需要找到 MySQL 的配置文件,一般在/etc/mysql/conf.d/mysql.cnf
若 Docker 内置的没有 vi 编辑器,可以退出 Docker,通过 Find 命令查找这个配置文件,然后进行修改。1
2
部署 Tomcat 服务器
Tomcat 服务器在 Docker 的部署步骤如下:
① 拉取 Tomcat 官方镜像:1
docker pull tomcat:9.0.24
② 创建容器:1
docker run -it --name=mytomcat -p 8080:8080 tomcat:9.0.24
③ 将相关 war 文件复制到 tocam 容器目录下:1
docker cp logistics.war mytomca:/usr/local/tomcat/webapps/
④ 之后即可通过 ip(或域名)加 8080 端口及路径访问该项目,如:1
https://ip:8080/logistics
注
:Tomcat 镜像可以在官方选择版本:
部署 Nginx 服务器
Nginx 服务器在 Docker 的部署步骤如下:
① 拉取 Nginx 镜像:1
docker pull nginx
② 创建容器:1
docker run -di --name=mynginx -p 80:80 nginx
部署 Redis 数据库
Redis 数据库在 Docker 的部署步骤如下:
① 拉取 Redis 镜像:
1 | docker pull redis |
② 创建容器:1
docker run -di --name=myredis -p 6379:6379 redis
时区修改
Docker 拉取镜像的时区可能和当地时间不同,通过下面操作可以修改为中国时区:
第一步,进入相关容器:1
docker exec -it containerName /bin/bash
第二步,修改时区1
2ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
echo "Asia/Shanghai" > /etc/timezone
第三步:查看当前时间:1
date
Docker Compose
Docker Compose 是用于定义和运行多容器的云原生应用程序工具。
为什么使用 Docker Compose ?
在上文使用 Docker 容器时,我们需要定义并 build dockerfile 文件,然后启动运行它(不同容器可能还需要配置相关参数)。
但是,我们的系统一般都包含上百的服务,而每个服务又有多个实例,若全部采用手动方式来启动关闭的话,工作量之大可想而知。
因此,为了把程序员从这些繁杂的工作当中解脱出来,Docker Compose 便应运而生!
现状与作用
目前,Docker Compose 已被数百万的开发人员所使用,并在 GitHub 上拥有超过 650,000 个文件,由于它定义了一种基于多容器应用程序简单的与云和编排器无关的方式,因此被开发人员广泛接受。
Docker Compose 通过允许开发人员在单个文件中定义一个复杂的堆栈并使用单个命令运行它,极大地简化了开发人员到云程序和工具链的代码。
这样,便无需手动构建和启动每个容器,从而节省了开发团队的宝贵时间。
安装
不论你通过 Windows 还是 Mac 安装完 Docker Desktop ,都会默认自动安装好 docker-compose。
Linux 可以通过 docker-compose 下载地址下载,之后执行以下命令即可:1
2
3
4sudo mv docker-compose-linux-x86_64 /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
# 检测是否安装成功
docker-compose version
基本概念
Docker Compose 允许用户通过一个单独的docker- compose.yml
模板文件来定义一组相关联的应用容器为一个项目。
Docker Compose 有两个重要的概念:
- 服务(service):一个应用的容器,实际上可以包括若干运行相同镜像的容器
- 实例项目(project):由一组关联的应用容器组成的一个完整业务单元
Docker Compose 的默认管理对象是项目,通过子命令对项目中的一组容器进行便捷地生命周期管理。
Docker Compose 项目由 Python 编写,实现上调用了 Docker 服务提供的 API 来对容 器进行管理。因此,只要所操作的平台支持 Docker API,就可以在其上利用 Docker Compose 来进行编排管理。
基本使用
Docker Compose 的使用步骤如下:
- 根据容器相关参数来编写
docker-compose.yml
文件 - 执行
docker-compose.yml
文件
下面以 MySQL 容器为例:
首先,根据 MySQL 相关参数来编写docker-compose.yml
文件:
1 | version: '3.1' |
注意哦
:vim 下输入:set paste
则粘贴不会出现格式错误
之后就可以执行如下命令来构建容器啦:1
docker-compose up -d
该命令默认会根据当前目录下的docker-compose.yml
文件来构建容器。
Docker Compose 命令
以下命令前缀均为docker-compose
(如docker-compose ps
):
命令 | 说明 |
---|---|
ps | 显示所有容器 |
up -d tomcat | 构建并启动 tomcat 容器 |
exec -it tomcat /bin/bash | 以交互式方式进入 tomcat 容器 |
start tomcat | 启动 tomcat 容器 |
restart tomcat | 重新启动 tomcat 容器 |
stop tomcat | 停止 tomcat 容器 |
rm tomcat | 删除容器(删除前必须关闭容器) |
down | 删除所有 tomcat 容器及镜像 |
build tomcat | 构建镜像 |
logs tomcat | 查看 tomcat 的日志 |
pause tomcat | 暂停 tomcat 容器 |
unpause tomcat | 恢复 tomcat 容器 |
Docker Compose 网络
默认情况下,Compose会为我们的应用创建一个网络,服务的每个容器都会加入该网络中。
这样,容器就可被该网络中的其他容器访问,不仅如此,该容器还能以服名称作为 hostname 被其他容器访问。
网络名称
默认情况下,应用程序的网络名称基于 Compose 的工程名称,而项目名称基于docker-compose.yml
文件所在目录的名称。
如需修改工程名称,可使用–project-name标识或COMPOSE_PORJECT_NAME环境变量。
举个例子,假如一个应用程序在名为myapp
的目录中,并且docker-compose.yml如下所示:
1 | version: '2' |
当我们运行docker-compose up
命令时,将会执行以下几步:
- 创建一个名为
myapp_default
的网络; - 使用
web
服务的配置创建容器时,它将以web
为姓名加入myapp_default
网络 - 使用
db
服务的配置创建容器时,它将以db
为姓名加入myapp_default
网络
myapp_default
网络的容器之间可使用服务名称(web
或db
)作为hostname
相互访问。
举个例子,web这个服务可使用postgres://db:5432
访问db
容器。
更新容器
当服务的配置发生更改时,可使用docker-compose up
命令更新配置。
此时,Compose 会删除旧容器并创建新容器。新容器会以不同的IP
加入网络,名称保持不变。任何指向旧容器的连接都会被关闭,容器会重新找到新容器并连接上去。
links
默认情况下,服务之间可使用服务名称相互访问,而links
允许我们定义一个别名去访问其他服务。
举个例子:1
2
3
4
5
6
7
8version: '2'
services:
web:
build: .
links:
- "db:database"
db:
image: postgres
这样web
服务就可使用db
或database
作为hostname
访问db
服务了。
指定自定义网络
一些场景下,默认的网络配置满足不了我们的需求,此时我们可使用networks
命令自定义网络。
networks
命令允许我们创建更加复杂的网络拓扑并指定自定义网络驱动和选项。不仅如此,我们还可使用networks
将服务连接到不是由 Compose 管理的、外部创建的网络。
如下,我们在其中定义了两个自定义网络。
1 | version: '2' |
其中,proxy 服务与 db 服务隔离,两者分别使用自己的网络;app服务可与两者通信。
由本例不难发现,使用 networks 命令,即可方便实现服务间的网络隔离与连接。
配置默认网络
除自定义网络外,我们也可为默认网络自定义配置。
1 | version: '2' |
这样,就可为该应用指定自定义的网络驱动。
使用已存在的网络
一些场景下,我们并不需要创建新的网络,而只需加入已存在的网络。
此时需要使用external
属性进行配置:1
2
3
4networks:
default:
external:
name: zookeeper_default
Docker Compose 链接外部容器的几种方式
在 Docker 中,容器之间的链接是一种很常见的操作:它提供了访问其中的某个容器的网络服务而不需要将所需的端口暴露给Docker Host主机的功能。
Docker Compose中对该特性的支持同样是很方便的。然而,如果需要链接的容器没有定义在同一个docker-compose.yml
中的时候,这个时候就稍微麻烦复杂了点。
在不使用Docker Compose的时候,将两个容器链接起来使用—link
参数,相对来说比较简单,以nginx
镜像为例子:
1 | # 开启一个实例test1 |
这样,nginx_2
与nginx_1
便建立了链接,就可以在nginx_2
中使用访问nginx_1
中的服务了。
如果使用 Docker Compose,那么这个事情就变得更简单了,还是以上面的nginx
镜像为例子,编辑docker-compose.yml
文件为:
1 | version: "3" |
最终效果与使用普通的Docker命令docker run xxxx
建立的链接并无区别。这只是一种最为理想的情况。
- 如果容器没有定义在同一个
docker-compose.yml
文件中,应该如何链接它们呢? - 又如果定义在
docker-compose.yml
文件中的容器需要与docker run xxx
启动的容器链接,需要如何处理?
针对这两种典型的情况,下面给出我个人测试可行的办法:
方式一:让需要链接的容器同属一个外部网络
我们还是使用nginx镜像来模拟这样的一个情景:假设我们需要将两个使用Docker Compose管理的nignx容器(test1
和test2
)链接起来,使得test2
能够访问test1
中提供的服务,这里我们以能ping通为准。
首先,我们定义容器test1
的docker-compose.yml
文件内容为:
1 | version: "3" |
容器test2
内容与test1
基本一样,只是多了一个external_links
,需要特别说明的是:最近发布的Docker版本已经不需要使用external_links来链接容器,容器的DNS服务可以正确的作出判断,因此如果你你需要兼容较老版本的Docker的话,那么容器test2
的docker-compose.yml
文件内容为:
1 | version: "3" |
否则的话,test2
的docker-compose.yml
和test1
的定义完全一致,不需要额外多指定一个external_links
。相关的问题请参见stackoverflow上的相关问题:docker-compose + external container
正如你看到的那样,这里两个容器的定义里都使用了同一个外部网络app_net
,因此,我们需要在启动这两个容器之前通过以下命令再创建外部网络:
1 | docker network create app_net复制代码 |
之后,通过docker-compose up -d
命令启动这两个容器,然后执行docker exec -it test2 ping test1
,你将会看到如下的输出:
1 | docker exec -it test2 ping test1 |
证明这两个容器是成功链接了,反过来在test1
中pingtest2
也是能够正常ping通的。
如果我们通过docker run --rm --name test3 -d nginx
这种方式来先启动了一个容器(test3
)并且没有指定它所属的外部网络,而需要将其与test1
或者test2
链接的话,这个时候手动链接外部网络即可:
1 | docker network connect app_net test3复制代码 |
这样,三个容器都可以相互访问了。
方式二:更改需要链接的容器的网络模式
通过更改你想要相互链接的容器的网络模式为bridge
,并指定需要链接的外部容器(external_links
)即可。与同属外部网络的容器可以相互访问的链接方式一不同,这种方式的访问是单向的。
还是以nginx容器镜像为例子,如果容器实例nginx1
需要访问容器实例nginx2
,那么nginx2
的doker-compose.yml
定义为:
1 | version: "3" |
与其对应的,nginx1
的docker-compose.yml
定义为:
1 | version: "3" |
需要特别说明的是,这里的
external_links
是不能省略的,而且nginx1
的启动必须要在nginx2
之后,否则可能会报找不到容器nginx2
的错误。
接着我们使用ping来测试下连通性:
1 | $ docker exec -it nginx1 ping nginx2 # nginx1 to nginx2 |
以上也能充分证明这种方式是属于单向联通的。
在实际应用中根据自己的需要灵活的选择这两种链接方式,如果想偷懒的话,大可选择第二种。不过我更推荐第一种,不难看出无论是联通性还是灵活性,较为更改网络模式的第二种都更为友好。
附docker-compose.yml
文件详解
1 | Compose和Docker兼容性: |
参考
- Docker 官网
- 史上最全(全平台)docker 安装方法
- 杨保华 戴王剑 曹亚仑. Docker 技术入门与实战 [M]. 机械工业出版社,2017
文章信息
时间 | 说明 |
---|---|
2019-05-30 | 初稿 |