简介
Docker,一种容器技术。旨在解决运行环境和配置问题,方便做持续集成,有助于整体发布。
- 官网:https://www.docker.com
- 官方文档:https://docs.docker.com
- DockerHub:https://hub.docker.com
安装
配置Docker的yum库
- 安装yum工具
1
yum install -y yum-utils device-mapper-persistent-data lvm2
- 配置Docker的yum源
1
2yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
sed -i 's+download.docker.com+mirrors.aliyun.com/docker-ce+' /etc/yum.repos.d/docker-ce.repo - 更新yum,建立缓存
1
yum makecache fast
安装Docker
1 | yum install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin |
启动和校验
- 启动:
systemctl start docker
- 停止:
systemctl stop docker
- 重启:
systemctl restart docker
- 查看状态:
systemctl status docker
- 开机自启:
systemctl enable docker
配置镜像加速
在/etc/docker
新建文件daemon.json
,内容如下:
1 | { |
然后:
- 重新加载配置:
systemctl daemon-reload
- 重启Docker:
systemctl restart docker
关于docker.m.daocloud.io
,可以参考如下链接
https://github.com/DaoCloud/public-image-mirror
开始
我们基于Docker安装部署MySQL。
示例代码:
1 | docker run -d \ |
运行结果:
1 | Unable to find image 'mysql:latest' locally |
解释说明:
- Docker首先查找本地的"mysql",在本地没有后,再从"library/mysql"下载(Pulling)。
- 上述过程,就是自动搜索下载镜像,然后创建并运行容器。
- 上述命令,已经把MySQL安装部署完毕。
命令解释:
docker run -d
创建并运行一个容器,-d
是让容器以后台进程运行。--name mysql
给容器起个名字,在本文是"mysql"。-p 3306:3306
设置端口映射,-p 宿主机端口:容器内端口
-e TZ=Asia/Shanghai
、-e MYSQL_ROOT_PASSWORD=123
配置容器内进程运行时的一些参数。
格式:-e KEY=VALUE
,KEY
和VALUE
都由容器内进程决定mysql
,镜像名称,Docker会根据这个名字搜索并下载镜像。
REPOSITORY:TAG
,例如"mysql:8.0",其中REPOSITORY
可以理解为镜像名,TAG
是版本号。
在未指定TAG的情况下,默认是最新版本,也就是mysql:latest
。
这里下载的不是安装包,而是镜像(image)。这个镜像中不仅包含了MySQL本身,还包含了其运行所需要的环境、配置、系统级函数库。因此MySQL在运行时就有独立的环境,就可以跨系统运行,也不需要手动再次配置环境了。这套独立运行的隔离环境我们称为容器(container)。
我们甚至可以为一个镜像运行多个容器,例如在一台机器上,运行两个MySQL镜像的容器。
Docker官方提供了一个专门管理、存储镜像的网站,并对外开放了镜像上传、下载的权利。各大软件公司也在这个网站发布镜像。
https://hub.docker.com
我们在安装完Docker后,会启动一个Docker的服务,是我们通过systemctl start docker
启动的那个服务;然后我们输入命令docker run
,会通过Docker服务,去镜像仓库中寻找MySQL镜像,拉取到本地;最后,基于镜像运行容器。
常见命令
命令
命令 | 说明 | 文档地址 |
---|---|---|
docker pull |
拉取镜像 | docker pull |
docker push |
推送镜像到DockerRegistry | docker push |
docker images |
查看本地镜像 | docker images |
docker rmi |
删除本地镜像 | docker rmi |
docker run |
创建并运行容器(不能重复创建) | docker run |
docker stop |
停止指定容器 | docker stop |
docker start |
启动指定容器 | docker start |
docker restart |
重新启动容器 | docker restart |
docker rm |
删除指定容器 | docker rm |
docker ps |
查看容器 | docker ps |
docker logs |
查看容器运行日志 | docker logs |
docker exec |
进入容器 | docker exec |
docker save |
保存镜像到本地压缩文件 | docker save |
docker load |
加载本地压缩文件到镜像 | docker load |
docker inspect |
查看容器详细信息 | docker inspect |
命令之间的关系如下:
案例
在本文,我们创建一个nginx容器。
步骤如下:
- 在DockerHub查看nginx镜像仓库及相关信息
https://hub.docker.com/_/nginx - 拉取nginx镜像
1
docker pull nginx
- 查看镜像运行结果:
1
docker images
1
2
3REPOSITORY TAG IMAGE ID CREATED SIZE
nginx latest f876bfc1cc63 4 weeks ago 192MB
mysql latest 56a8c14e1404 2 months ago 603MB - 创建并运行nginx容器
1
docker run -d --name nginx -p 80:80 nginx
- 查看运行中容器
示例代码:运行结果:1
docker ps
1
2
3CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
ae50fcc4e066 nginx "/docker-entrypoint.…" 20 seconds ago Up 19 seconds 0.0.0.0:80->80/tcp, :::80->80/tcp nginx
1c397faed580 mysql "docker-entrypoint.s…" 4 days ago Up 4 days 0.0.0.0:3306->3306/tcp, :::3306->3306/tcp, 33060/tcp mysql - 访问网页
- 停止容器
1
docker stop nginx
- 查看所有容器
示例代码:运行结果:1
docker ps -a
1
2
3CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
ae50fcc4e066 nginx "/docker-entrypoint.…" 5 minutes ago Exited (0) 5 seconds ago nginx
1c397faed580 mysql "docker-entrypoint.s…" 4 days ago Up 4 days 0.0.0.0:3306->3306/tcp, :::3306->3306/tcp, 33060/tcp mysql - 再次启动nginx容器
示例代码:1
docker start nginx
- 查看容器详细信息
示例代码:1
docker inspect nginx
- 查看容器日志与通过tail查看日志类似,可以加上
1
2# 查看MySQL这个容器的日志
docker logs mysql-f
参数,docker logs -f mysql
。 - 进入nginx容器,再执行
exit
退出容器。1
docker exec -it nginx bash
exec
:执行-it
:添加一个可输入的终端nginx
:容器名bash
:命令行交互
- 进入MySQL容器,执行
mysql -uroot -p
1
docker exec -it mysql mysql -uroot -p
- 删除容器
1
docker rm nginx
-f
强制删除容器(删除运行中的容器)1
docker rm -f nginx
数据卷
引例
容器是隔离环境,容器内程序的文件、配置、运行时产生的容器都在容器内部。
那么,如果我们要读写容器内的文件呢?例如:
- 如果要升级MySQL版本,需要销毁旧容器,那么数据岂不是跟着被销毁了?
- MySQL、nginx容器运行后,如果需要修改其中的某些配置该怎么办?
- 想要让nginx代理我们的静态资源怎么办?
虽然我们进入容器内部,进行各种操作。但总归是不方便。
什么是数据卷
数据卷,volume,一个虚拟目录,是容器内目录与宿主机目录之间映射的桥梁。
以nginx为例,我们知道nginx中有两个关键的目录:html(存放静态资源)、(conf)存放配置文件。
我们利用数据卷将两个目录与宿主机目录关联,方便我们操作。
在上图中:
- 我们创建了两个数据卷:
conf
、html
。 - nginx容器内部的
conf目录
和html目录
分别与两个数据卷关联。 - 而数据卷
conf
和html
分别指向了宿主机的/var/lib/docker/volumes/conf/_data
目录和/var/lib/docker/volumes/html/_data
目录。
这样一来,容器内的conf
和html
目录就与宿主机的conf
和html
目录关联起来,我们称之为挂载。
此时,我们操作宿主机的/var/lib/docker/volumes/html/_data
就是在操作容器内的/usr/share/nginx/html
目录。
只要我们将静态资源放入宿主机对应目录,就可以被nginx代理了。
/var/lib/docker/volumes
是默认的存放所有容器数据卷的目录,其下再根据数据卷名称创建新目录,格式为/数据卷名/_data
。
数据卷命令
命令 | 说明 | 文档地址 |
---|---|---|
docker volume create |
创建数据卷 | docker volume create |
docker volume ls |
查看所有数据卷 | docker volume ls |
docker volume rm |
删除指定数据卷 | docker volume rm |
docker volume inspect |
查看某个数据卷的详情 | docker volume inspect |
docker volume prune |
删除所有未使用的数据卷 | docker volume prune |
注意:
- 容器与数据卷的挂载要在创建容器时配置。
- 对于创建好的容器,是不能设置数据卷的。
- 在创建容器的过程中,数据卷会自动创建。
数据卷案例
数据卷挂载(nginx)
首先创建容器并指定数据卷,注意通过-v
参数来指定数据卷
1 | docker run -d --name nginx -p 80:80 -v html:/usr/share/nginx/html nginx |
-v html:/usr/share/nginx/html
,冒号左侧的是数据卷名称,冒号右侧是容器内目录。
查看数据卷,示例代码:
1 | docker volume ls |
运行结果:
1 | DRIVER VOLUME NAME |
查看数据卷详情,示例代码:
1 | docker volume inspect html |
运行结果:
1 | [ |
然后,我们修改宿主机的/var/lib/docker/volumes/html/_data/index.html
,会发现nginx部署的静态网页的内容也被修改了。
匿名数据卷(MySQL)
我们查看MySQL容器详细信息,docker inspect mysql
,关注其中.Config.Volumes
部分和.Mounts
部分。
示例代码:
1 | docker inspect mysql |
.Config.Volumes
部分:
1 | "Config": { |
.Mounts
部分:
1 | "Mounts": |
解释说明:
Name
:数据卷名称。由于定义容器未设置容器名,这里的就是匿名卷自动生成的名字,一串hash值。Source
:宿主机目录。Destination
:容器内的目录。
上述配置是将容器内的/var/lib/mysql
这个目录,与数据卷73fb45-XXXXXX
挂载。于是在宿主机中就有了/var/lib/docker/volumes/73fb45-XXXXXX/_data
这个目录。这就是匿名数据卷对应的目录,其使用方式与普通数据卷没有差别。
挂载本地目录或文件
我们除了挂载数据卷,还可以挂载本地目录。
执行docker run
命令时,使用-v 本地目录:容器内目录
,即可完成本地目录挂载。
需要注意的是,本地目录或文件必须以/
或./
开头,如果直接以名字开头,会被识别为数据卷名而非本地目录名。
例如:
-v mysql:/var/lib/mysql
,会被识别为一个数据卷叫mysql,运行时会自动创建这个数据卷。
-v ./mysql:/var/lib/mysql
,会被识别为当前目录下的mysql目录,运行时如果不存在会创建目录。
创建并运行新的mysql容器,挂载本地目录
1 | docker run -d \ |
镜像
如果我们要部署一个Java项目,并将其打包为一个镜像该怎么做呢?
镜像结构
镜像之所以能让我们快速跨操作系统部署应用而忽略其运行环境、配置等,就是因为镜像中包含了程序运行需要的系统函数库、环境、配置、依赖。
自定义镜像本质就是依次准备好程序运行的基础环境、依赖、应用本身、运行配置等文件,并且打包。
例如,我们从零部署一个Java应用,大概步骤是:准备一个Linux服务、安装并配置JDK、上传Jar包、运行jar包。
同样的,我们打包镜像的步骤如下:
- 准备Linux运行环境
- 安装并配置JDK
- 拷贝jar包
- 配置启动脚本
需要注意的是,镜像文件不是随意堆放的,而是按照操作的步骤分层叠加而成,每一层形成的文件都会单独打包并标记一个唯一id,称为Layer(层)。这样,如果我们构建时用到的某些层其他人已经制作过,就可以直接拷贝使用这些层,而不用重复制作。
例如,我们拉取一个redis的镜像,示例代码:
1 | docker pull redis |
运行结果:
1 | Using default tag: latest |
注意:Already exists
Dockerfile
Dockerfile,记录镜像结构的文件。
官方文档:https://docs.docker.com/engine/reference/builder/
比较常用的有:
指令 | 说明 | 示例 |
---|---|---|
FROM |
指定基础镜像 | FROM centos:6 |
ENV |
设置环境变量,可在后面指令使用 | ENV key value |
COPY |
拷贝本地文件到镜像的指定目录 | COPY ./xx.jar /tmp/app.jar |
RUN |
执行Linux的shell命令,一般是安装过程的命令 | RUN yum install gcc |
ENTRYPOINT |
镜像中应用的启动命令,容器运行时调用 | ENTRYPOINT java -jar xx.jar |
例如,我们基于Ubuntu镜像来构建一个Java应用,其Dockerfile如下:
1 | # 指定基础镜像 |
特别的,我们可以直接基于Java镜像,就可以省去JDK的配置了:
1 | # 基础镜像 |
构建镜像
执行命令docker build -t docker-demo:1.0 ./
,构建镜像
docker build
,构建一个docker镜像。-t docker-demo:1.0
,-t
参数是指定镜像的repository和tag。./
,最后的点是指构建时Dockerfile所在路径,由于我们进入了demo目录,所以指定的是./
代表当前目录,也可以直接指定Dockerfile目录。
示例代码:
1 | docker build -t docker-demo:1.0 ./ |
运行结果:
1 | [+] Building 115.9s (8/8) FINISHED docker:default |
查看镜像列表。示例代码:
1 | docker images |
运行结果:
1 | REPOSITORY TAG IMAGE ID CREATED SIZE |
基于该镜像,创建并运行容器。示例代码:
1 | docker run -d --name demo -p 8080:8080 docker-demo:1.0 |
查看容器,示例代码:
1 | docker ps |
运行结果:
1 | CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES |
网络
bridge模式
默认情况下,所有容器都是以bridge方式连接到Docker的一个虚拟网桥。
而且,同一个bridge下,容器是互通的。
如下:
- 我们查看下MySQL容器的详细信息,重点关注其中的网络IP地址
Networks.bridge.IPAddress
属性。
示例代码:运行结果:1
docker inspect --format='{{range .NetworkSettings.Networks}}{{println .IPAddress}}{{end}}' mysql
1
172.17.0.2
- 通过命令进入上文Java项目的容器,并通过命令检查。发现可以ping通。
1
2docker exec -it demo bash
ping 172.17.0.2
引例
但是,容器的网络IP其实是一个虚拟的IP,其值并不固定与某一个容器绑定,如果我们在开发时写死某个IP,而在部署时很可能MySQL容器的IP会发生变化,连接会失败。
所以?自建DNS服务?修改HOST文件?
不用这么复杂,我么可以基于Docker的网络功能实现。
命令
官方文档:https://docs.docker.com/engine/reference/commandline/network
常用的有:
命令 | 说明 | 文档地址 |
---|---|---|
docker network create | 创建一个网络 | docker network create |
docker network ls | 查看所有网络 | docker network ls |
docker network rm | 删除指定网络 | docker network rm |
docker network prune | 清除未使用的网络 | docker network prune |
docker network connect | 使指定容器连接加入某网络 | docker network connect |
docker network disconnect | 使指定容器连接离开某网络 | docker network disconnect |
docker network inspect | 查看网络详细信息 | docker network inspect |
案例:
- 通过命令创建一个网络,示例代码:
1
docker network create kaka
- 然后我们执行
ip addr
,会发现多了一张虚拟网卡。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
【部分运行结果略】
5: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:91:f6:63:22 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
valid_lft forever preferred_lft forever
inet6 fe80::42:91ff:fef6:6322/64 scope link
valid_lft forever preferred_lft forever
【部分运行结果略】
22: br-0e8626d168c6: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
link/ether 02:42:95:dc:8d:b4 brd ff:ff:ff:ff:ff:ff
inet 172.18.0.1/16 brd 172.18.255.255 scope global br-0e8626d168c6
valid_lft forever preferred_lft forever - 执行
docker network ls
,查看网络。1
2
3
4
5NETWORK ID NAME DRIVER SCOPE
3f1486bd98a7 bridge bridge local
8c5822a2f9ef host host local
0e8626d168c6 kaka bridge local
91131376287b none null local- 其中,除了kaka以外,其它都是默认的网络。
- 将
demo
容器和mysql
容器加入该网络。
在加入网络时可以通过--alias
给容器起别名。
例如,mysql
容器,指定别名为db
,示例代码:1
2docker network connect kaka mysql --alias db
docker network connect kaka demo - 进入
demo
容器,通过别名 db
和容器名 mysql
,都能访问。
host模式
假如有这么一个现象,公司正在进行容器化改造。
比如,A服务和B服务有通信,A服务已经上了容器化,B服务还没有上容器化,此时A服务在Docker的bridge网段内,无法访问B服务。
这时候,可以考虑host模式。
默认情况下,docker使用的是bridge模式启动服务,如果使用host模式启动,Docker容器与宿主机共享同一个网络命名空间,即Docker容器将直接使用宿主机的网络。
在docker run
命令中,添加--network host
,即可使用host模式。
DockerCompose
作用
通过上文的讨论,已经非常简化了我们的项目部署。
现在,我们部署一个简单且常规的现代JavaWeb项目,一般需要三个容器,MySQL、Java、nginx。
那么,可以更简单一些吗?
Docker Compose
通过一个单独的docker-compose.yml
模板文件(YAML格式)来定义一组相关联的应用容器,帮助我们实现多个相互关联的Docker容器的快速部署。
基本语法
官方文档:https://docs.docker.com/compose/compose-file/compose-file-v3/
例如,我们通过docker run
部署一个MySQL容器,示例代码:
1 | docker run -d \ |
如果用docker-compose.yml
文件来定义,如下:
1 | version: "3.8" |
version: "3.8"
,DockerCompose的语法的版本。
对比如下:
docker run 参数 | docker compose 指令 | 说明 |
---|---|---|
--name |
container_name |
容器名称 |
-p |
ports |
端口映射 |
-e |
environment |
环境变量 |
-v |
volumes |
数据卷配置 |
--network |
networks |
网络 |
基础命令
官方文档:https://docs.docker.com/compose/reference/
docker compose
,整体语法:
1 | docker compose [OPTIONS] [COMMAND] |
- OPTIONS,可选参数
-f
,指定compose文件的路径和名称。-p
,指定project名称。project就是当前compose文件中设置的多个service的集合,是逻辑概念。
- COMMAND,可选参数
up
,创建并启动所有service容器。down
,停止并移除所有容器、网络。ps
,列出所有启动的容器。logs
,查看指定容器的日志。stop
,停止容器。start
,启动容器。restart
,重启容器。top
,查看运行的进程。exec
,在指定的运行中容器中执行命令。
案例
本文,我们以部署一个常规且简单的JavaWeb项目为例。
这个项目的前端通过nginx部署;后端是SpringBoot框架的,通过Java部署;数据库是MySQL。
Dockerfile:
1 | # JDK8 |
docker-compose.yml
:
1 | version: "3.8" |
启动:
1 | docker compose up -d |