第一期Docker:【Docker容器化技术】docker安装与部署、常用命令、容器数据卷、应用部署实战、Dockerfile、服务编排docker-compose、私有仓库
第一期中介绍的比较详细,可以结合着一起学习。第二期Docker中在第一期基础上增加了自动化部署项目实战、自定义镜像、docker网络等知识。
一、快速入门
要想让Docker帮我们安装和部署软件,避免部署对服务器环境的依赖,减少复杂的部署流程,肯定要保证机器上有Docker。由于操作系统各不相同,安装方式也不同。
安装方式参考文档:《最新安装Docker篇》
1、部署MySQL
首先,我们利用Docker来安装一个MySQL软件,大家可以对比一下之前传统的安装方式,看看哪个效率更高一些。
如果是利用传统方式部署MySQL,大概的步骤有:
- 搜索并下载MySQL安装包
- 上传至Linux环境
- 编译和配置环境
- 安装
而使用Docker安装,仅仅需要一步即可,在命令行输入下面的命令:
docker run -d \ --name mysql \ -p 3306:3306 \ -e TZ=Asia/Shanghai \ -e MYSQL_ROOT_PASSWORD=123 \ mysql
运行效果如图:
MySQL安装完毕!通过数据库的任意客户端工具即可连接到MySQL
如果Linux上之前已经在外部安装过MySQL,并且默认端口3306,启动容器时发生端口冲突。
- 方法一:需要先把宿主机中的MySQL服务先停止掉,使用systemctl stop mysqld,再执行刚刚的docker命令。
- 方法二:修改端口映射 -p 3307:3306,将宿主机外部端口3307映射到容器内端口3306
- 如果想要集群部署,只需修改
宿主机端口映射
和容器名称
保证不同即可,因为容器之间环境是相互隔离的,所以都是3306不用变
# 如果想要集群部署只需修改docker容器内部的端口号和容器名称即可 # 节点1 docker run -d \ --name mysql \ -p 3306:3306 \ -e TZ=Asia/Shanghai \ -e MYSQL_ROOT_PASSWORD=123 \ mysql # 节点2 docker run -d \ --name mysql2 \ -p 3307:3306 \ -e TZ=Asia/Shanghai \ -e MYSQL_ROOT_PASSWORD=123 \ mysql # 节点3 docker run -d \ --name mysql3 \ -p 3308:3306 \ -e TZ=Asia/Shanghai \ -e MYSQL_ROOT_PASSWORD=123 \ mysql
大家可以发现,当我们执行命令后,Docker做的第一件事情,是去自动搜索并下载了MySQL,然后会自动运行MySQL,我们完全不用插手,非常方便。而且,这种安装方式完全不用考虑运行的操作系统环境问题,它不仅仅在CentOS系统是这样,在Ubuntu系统、macOS系统、甚至是装了WSL的Windows下,都可以使用这条命令来安装MySQL。
要知道,不同操作系统下其安装包、运行环境是都不相同的!如果是手动安装,必须手动解决安装包不同、环境不同的、配置不同的问题!
而使用Docker会自动搜索并下载MySQL。注意:这里下载的不是安装包,而是镜像(image)。镜像中不仅包含了MySQL本身,还包含了其运行所需要的环境、配置、系统级函数库。因此它在运行时就有自己独立的环境,就可以跨系统运行,也不需要手动再次配置环境了。这套独立运行的隔离环境我们称为容器(container)。
镜像仓库(DockerRegistry):存储和管理镜像的平台,Docker官方维护了一个公共仓库Docker-Hub(目前国内已停止访问)。
这里提供一个DockerHub的镜像网站:https://hubgw.docker.com/
2、命令解读
docker run -d \ --name mysql \ -p 3307:3306 \ -e TZ=Asia/Shanghai \ -e MYSQL_ROOT_PASSWORD=123 \ mysql
解读:
docker run -d
:创建并运行一个容器,-d则是让容器以后台进程运行--name mysql
: 给容器起个名字叫mysql,你可以叫别的-p 3307:3306
: 设置端口映射。
- 容器是隔离环境,外界不可访问。但是可以将宿主机端口映射容器内到端口,当访问宿主机指定端口时,就是在访问容器内的端口了。
- 容器内端口往往是由容器内的进程决定,例如MySQL进程默认端口是3306,因此容器内端口一定是3306;而宿主机端口则可以任意指定,一般与容器内保持一致,但是这里为了防止与之前外部安装的MySQL的3306冲突,改为3307映射容器内的3306。
- 格式:
-p 宿主机端口:容器内端口
,示例中将宿主机的3307映射到容器内的3306端口
-e TZ=Asia/Shanghai
:配置容器内进程运行时的一些参数
- 格式:
-e KEY=VALUE
,KEY和VALUE都由容器内进程决定 - 案例中,
TZ=Asia/Shanghai
是设置时区;MYSQL_ROOT_PASSWORD=123
是设置MySQL默认密码
mysql
: 设置镜像名称,Docker会根据这个名字搜索并下载镜像
- 格式:
REPOSITORY:TAG
,例如mysql:8.0
,其中REPOSITORY
可以理解为镜像名,TAG
是版本号 - 在未指定TAG的情况下,默认拉取最新版本镜像,也就是
mysql:latest
镜像的名称不是随意的,而是要到DockerRegistry中寻找,镜像运行时的配置也不是随意的,要参考镜像的帮助文档,这些在DockerHub的国内镜像网站或者软件的官方网站中都能找到。
如果我们要安装其它软件,也可以到DockerRegistry中寻找对应的镜像名称和版本,阅读相关配置即可。
二、Docker基础
接下来,我们一起来学习Docker使用的一些基础知识,为将来部署项目打下基础。具体用法可以参考Docker官方文档:https://docs.docker.com/
1、常见命令
(1)命令介绍
命令 |
说明 |
文档地址 |
docker pull |
拉取镜像 |
|
docker push |
推送镜像到DockerRegistry |
|
docker images |
查看本地镜像 |
|
docker rmi |
删除本地镜像 |
|
docker run |
创建并运行容器(不能重复创建) |
|
docker stop |
停止指定容器 |
|
docker start |
启动指定容器 |
|
docker restart |
重新启动容器 |
|
docker rm |
删除指定容器 |
|
docker ps |
查看容器 |
|
docker logs |
查看容器运行日志 |
|
docker exec |
进入容器 |
|
docker save |
保存镜像到本地压缩文件 |
|
docker load |
加载本地压缩文件到镜像 |
|
docker inspect |
查看容器详细信息 |
用一副图来表示这些命令的关系:
补充:默认情况下,每次重启虚拟机我们都需要手动启动Docker和Docker中的容器。通过命令可以实现开机自启
# Docker开机自启 systemctl enable docker # Docker容器开机自启 docker update --restart=always [容器名/容器id]
(2)命令演示
- 以Nginx为例演示上述命令
# 第1步,查看nginx镜像仓库及相关信息 docker search nginx # 第2步,拉取Nginx镜像 docker pull nginx # 第3步,查看镜像 docker images # 结果如下: REPOSITORY TAG IMAGE ID CREATED SIZE nginx latest 605c77e624dd 16 months ago 141MB mysql latest 3218b38490ce 17 months ago 516MB # 第4步,创建并允许Nginx容器 docker run -d --name nginx -p 80:80 nginx # 第5步,查看运行中容器 docker ps # 也可以加格式化方式访问,格式会更加清爽 docker ps --format "table {{.ID}}\t{{.Image}}\t{{.Ports}}\t{{.Status}}\t{{.Names}}" # 查看容器日志:docker logs 容器名 docker logs nginx # 持续查看容器日志:docker logs -f 容器名 docker logs -f nginx # 第6步,访问网页,地址:http://虚拟机IP地址:80 # 第7步,停止容器 docker stop nginx # 第8步,查看所有容器 docker ps -a --format "table {{.ID}}\t{{.Image}}\t{{.Ports}}\t{{.Status}}\t{{.Names}}" # 第9步,再次启动nginx容器 docker start nginx # 第10步,再次查看容器 docker ps --format "table {{.ID}}\t{{.Image}}\t{{.Ports}}\t{{.Status}}\t{{.Names}}" # 第11步,查看容器详细信息 docker inspect nginx # 第12步,进入容器,查看容器内目录 docker exec -it nginx bash # 或者,可以进入容器的同时跟上执行的命令,之间一步进入容器内的MySQL docker exec -it mysql mysql -uroot -p # 将容器打包下载到本地,.tar.gz是打压缩包 docker save -o nginx.tar nginx:latest # 第13步,删除容器 docker rm nginx # 发现无法删除,因为容器运行中,强制删除容器 docker rm -f nginx # 将打包好的容器加载到本地 docker load -i nginx.tar
(3)命令别名
给常用Docker命令起别名,方便我们访问:
# 修改/root/.bashrc文件 vim /root/.bashrc 内容如下: # .bashrc # User specific aliases and functions alias rm='rm -i' alias cp='cp -i' alias mv='mv -i' alias dps='docker ps --format "table {{.ID}}\t{{.Image}}\t{{.Ports}}\t{{.Status}}\t{{.Names}}"' alias dis='docker images' # Source global definitions if [ -f /etc/bashrc ]; then . /etc/bashrc fi
然后,执行命令使别名生效
source /root/.bashrc
2、数据卷
容器是隔离环境,容器内程序的文件、配置、运行时产生的容器都在容器内部,我们要读写容器内的文件非常不方便。大家思考几个问题:
- 如果要升级MySQL版本,需要销毁旧容器,那么数据岂不是跟着被销毁了?
- MySQL、Nginx容器运行后,如果我要修改其中的某些配置该怎么办?
- 我想要让Nginx代理我的静态资源怎么办?
因此,容器提供程序的运行环境,但是程序运行产生的数据、程序运行依赖的配置都应该与容器解耦。
(1)什么是数据卷
数据卷(volume
)是一个虚拟目录,是容器内目录与宿主机目录之间映射的桥梁。
以Nginx为例,我们知道Nginx中有两个关键的目录:
html
:放置一些静态资源。如果我们要让Nginx代理我们的静态资源,最好是放到html
目录;conf
:放置配置文件。如果我们要修改Nginx的配置,最好是找到conf
下的nginx.conf
文件。
但遗憾的是,容器运行的Nginx所有的文件都在容器内部。所以我们必须利用数据卷将两个目录与宿主机目录关联,方便我们操作。如图:
在上图中:
- 我们创建了两个数据卷:
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/_data
目录。只要我们将静态资源放入宿主机对应目录,就可以被Nginx代理了。
- 小提示:
/var/lib/docker/volumes
这个目录就是宿主机默认的存放所有容器数据卷的目录,其下再根据数据卷名称创建新目录,格式为/数据卷名/_data
。
为什么不让容器目录直接指向宿主机目录呢?
- 因为直接指向宿主机目录就与宿主机强耦合了,如果切换了环境,宿主机目录就可能发生改变了。由于容器一旦创建,目录挂载就无法修改,这样容器就无法正常工作了。
- 但是容器指向数据卷,一个逻辑名称,而数据卷再指向宿主机目录,就不存在强耦合。如果宿主机目录发生改变,只要改变数据卷与宿主机目录之间的映射关系即可。
不过,由于数据卷目录比较深,不好寻找,通常我们也允许让容器直接与宿主机目录挂载而不使用数据卷,具体参考2(3)小节。
(2)数据卷命令
数据卷的相关命令有:
命令 |
说明 |
文档地址 |
docker volume create |
创建数据卷 |
|
docker volume ls |
查看所有数据卷 |
|
docker volume rm |
删除指定数据卷 |
|
docker volume inspect |
查看某个数据卷的详情 |
|
docker volume prune |
清除数据卷 |
注意:容器与数据卷的挂载要在创建容器时配置,对于创建好的容器,是不能设置数据卷的。而且创建容器的过程中,数据卷会自动创建。
- 案例:Nginx的html目录挂载
# 1.首先创建容器并指定数据卷,注意通过 -v 参数来指定数据卷 docker run -d --name nginx -p 80:80 -v html:/usr/share/nginx/html nginx # 2.然后查看数据卷 docker volume ls # 结果(一个是mysql容器的匿名挂载自动分配的数据卷名称,一个是nginx容器指定挂载的数据卷名称) DRIVER VOLUME NAME local 7a445e12415f9210fdc05aedad9891a1c8e27d6a4bd97f70936560d2db861ad1 local html # 3.查看数据卷详情 docker volume inspect html # 结果 [ { "CreatedAt": "2024-05-17T19:57:08+08:00", "Driver": "local", "Labels": null, "Mountpoint": "/var/lib/docker/volumes/html/_data", "Name": "html", "Options": null, "Scope": "local" } ] # 4.查看/var/lib/docker/volumes/html/_data目录 ll /var/lib/docker/volumes/html/_data # 可以看到与nginx的html目录内容一样,结果如下: 总用量 8 -rw-r--r--. 1 root root 497 12月 28 2021 50x.html -rw-r--r--. 1 root root 615 12月 28 2021 index.html # 5.进入该目录,并随意修改index.html内容 cd /var/lib/docker/volumes/html/_data vim index.html # 6.打开页面,查看效果 # 7.进入容器内部,查看/usr/share/nginx/html目录内的文件是否变化 docker exec -it nginx bash cat /usr/share/nginx/html/index.html
如果使用了MobaXterm甚至修改文件不用vi或vim命令,直接在sftp界面双击要修改的文件即可在编辑器内修改。
修改index.html内容后访问测试
使用docker inspect nginx
查看容器详情信息
- 案例:MySQL的匿名数据卷
# 1.查看MySQL容器详细信息 docker inspect mysql # 关注其中.Config.Volumes部分和.Mounts部分
我们关注两部分内容,第一是.Config.Volumes
部分
{ "Config": { // 略 ... "Volumes": { "/var/lib/mysql": {} } // 略 ... } }
可以发现这个容器声明了一个本地目录,需要挂载数据卷,但是数据卷未定义。这就是匿名卷。
然后,我们再看容器详情中的.Mounts
挂载结果部分:
"Mounts": [ { "Type": "volume", "Name": "7a445e12415f9210fdc05aedad9891a1c8e27d6a4bd97f70936560d2db861ad1", "Source": "/var/lib/docker/volumes/7a445e12415f9210fdc05aedad9891a1c8e27d6a4bd97f70936560d2db861ad1/_data", "Destination": "/var/lib/mysql", "Driver": "local", "Mode": "", "RW": true, "Propagation": "" } ],
可以发现,其中有几个关键属性:
Name
:数据卷名称。由于定义容器未设置容器名,这里的就是匿名卷自动生成的名字,一串hash值。Source
:宿主机目录Destination
: 容器内的目录
上述配置是将容器内的/var/lib/mysql
这个目录,与数据卷7a445e12415f9210fdc05aedad9891a1c8e27d6a4bd97f70936560d2db861ad1
挂载。于是在宿主机中就有了/var/lib/docker/volumes/7a445e12415f9210fdc05aedad9891a1c8e27d6a4bd97f70936560d2db861ad1/_data
这个目录。这就是匿名数据卷对应的目录,其使用方式与普通数据卷没有差别。
接下来,可以查看该目录下的MySQL的data文件:
ll /var/lib/docker/volumes/7a445e12415f9210fdc05aedad9891a1c8e27d6a4bd97f70936560d2db861ad1/_data
注意:每一个不同的镜像,将来创建容器后内部有哪些目录可以挂载,可参考DockerHub对应的说明。
(3)挂载宿主机本地目录或文件
可以发现,数据卷的目录结构较深,如果我们去操作数据卷目录会不太方便。在很多情况下,我们会直接将容器目录与宿主机指定目录挂载,而不挂载到数据卷。挂载语法与数据卷类似:
# 挂载本地目录 -v 本地目录:容器内目录 # 挂载本地文件 -v 本地文件:容器内文件
注意:本地目录或文件必须以 /
或 ./
开头,如果直接以名字开头,会被识别为数据卷名而非本地目录名。例如:
-v mysql:/var/lib/mysql # 会被识别为一个数据卷,数据卷名称叫mysql,运行时会自动创建这个数据卷 -v ./mysql:/var/lib/mysql # 会被识别为宿主机内当前目录下的mysql目录,运行时如果不存在会创建目录
- 案例:删除并重新创建mysql容器,并完成本地目录挂载:
- 挂载
/root/mysql/data
到容器内的/var/lib/mysql
目录 - 挂载
/root/mysql/init
到容器内的/docker-entrypoint-initdb.d
目录(初始化的SQL脚本目录,注意初始化脚本只有第一次创建时才生效) - 挂载
/root/mysql/conf
到容器内的/etc/mysql/conf.d
目录(这个是MySQL配置文件目录)
首先准备好mysql的init
目录和conf
目录:
以及对应的初始化SQL脚本hmall.sql
和配置文件hm.cnf
:
其中,hm.cnf
主要是配置了MySQL的默认编码改为了utf8mb4
;而hmall.sql
则是后面要用到的黑马商城项目的初始化SQL脚本。
直接将conf
和init
目录上传至虚拟机的/root/docker-mysql-hmall
目录下:
接下来进行mysql本地目录挂载
# 1.删除原来的MySQL容器 docker rm -f mysql # 2.进入/root目录 cd ~ # 在/root/docker-mysql-hmall下创建data、conf、init三个目录 mkdir -p ~/docker-mysql-hmall/data mkdir -p ~/docker-mysql-hmall/conf mkdir -p ~/docker-mysql-hmall/init # 3.创建并运行新mysql容器,挂载本地目录 docker run -d \ --name docker-mysql-hmall \ -p 3307:3306 \ -e TZ=Asia/Shanghai \ -e MYSQL_ROOT_PASSWORD=123 \ -v /root/docker-mysql-hmall/data:/var/lib/mysql \ -v /root/docker-mysql-hmall/conf:/etc/mysql/conf.d \ -v /root/docker-mysql-hmall/init:/docker-entrypoint-initdb.d \ mysql # 查看mysql容器创建情况 dps -a | grep mysql # 4.查看root目录,可以发现~/docker-mysql-hmall/data目录已经自动创建好了 ll docker-mysql-hmall # 结果: 总用量 4 drwxr-xr-x. 2 root root 20 11月 23 12:24 conf drwxr-xr-x. 7 polkitd root 4096 11月 23 12:27 data drwxr-xr-x. 2 root root 23 11月 23 12:24 init # 查看data目录,会发现里面有大量数据库数据,说明数据库完成了初始化 ll ~/docker-mysql-hmall/data # 5.查看MySQL容器内数据 # 5.1.进入MySQL docker exec -it docker-mysql-hmall mysql -uroot -p123 # 5.2.查看编码表 show variables like "%char%"; # 5.3.结果,发现编码是utf8mb4没有问题 +--------------------------+--------------------------------+ | Variable_name | Value | +--------------------------+--------------------------------+ | character_set_client | utf8mb4 | | character_set_connection | utf8mb4 | | character_set_database | utf8mb4 | | character_set_filesystem | binary | | character_set_results | utf8mb4 | | character_set_server | utf8mb4 | | character_set_system | utf8mb3 | | character_sets_dir | /usr/share/mysql-8.0/charsets/ | +--------------------------+--------------------------------+ # 6.查看数据 # 6.1.查看数据库 show databases; # 结果,hmall是黑马商城数据库 +--------------------+ | Database | +--------------------+ | hmall | | information_schema | | mysql | | performance_schema | | sys | +--------------------+ 5 rows in set (0.00 sec) # 6.2.切换到hmall数据库 use hmall; # 6.3.查看表 show tables; # 结果: +-----------------+ | Tables_in_hmall | +-----------------+ | address | | cart | | item | | order | | order_detail | | order_logistics | | pay_order | | user | +-----------------+ # 6.4.查看address表数据 +----+---------+----------+--------+----------+-------------+---------------+-----------+------------+-------+ | id | user_id | province | city | town | mobile | street | contact | is_default | notes | +----+---------+----------+--------+----------+-------------+---------------+-----------+------------+-------+ | 59 | 1 | 北京 | 北京 | 朝阳区 | 13900112222 | 金燕龙办公楼 | 李佳诚 | 0 | NULL | | 60 | 1 | 北京 | 北京 | 朝阳区 | 13700221122 | 修正大厦 | 李佳红 | 0 | NULL | | 61 | 1 | 上海 | 上海 | 浦东新区 | 13301212233 | 航头镇航头路 | 李佳星 | 1 | NULL | | 63 | 1 | 广东 | 佛山 | 永春 | 13301212233 | 永春武馆 | 李晓龙 | 0 | NULL | +----+---------+----------+--------+----------+-------------+---------------+-----------+------------+-------+ 4 rows in set (0.00 sec)
注意:如果不是首次创建,初始化sql脚本会失败,需要自己手动导入sql脚本即可。
3、自定义镜像
前面我们一直在使用别人准备好的镜像,那如果我要部署一个Java项目,把它打包为一个镜像该怎么做呢?
(1)镜像结构
要想自己构建镜像,必须先了解镜像的结构。镜像之所以能让我们快速跨操作系统部署应用而忽略其运行环境和配置,就是因为镜像中包含了程序运行需要的系统函数库、环境、配置、依赖。因此,自定义镜像本质就是依次准备好程序运行的基础环境、依赖、应用本身、运行配置等文件,并且打包构建成镜像。
举个例子,我们要从0部署一个Java应用,大概流程是这样:
- 准备一个linux服务器(CentOS或者Ubuntu均可)
- 安装并配置JDK
- 上传Jar包
- 运行jar包
那因此,我们打包镜像也是分成这么几步:
- 准备Linux运行环境(java项目并不需要完整的操作系统,仅仅是基础运行环境即可)
- 安装并配置JDK
- 拷贝jar包
- 配置启动脚本
上述步骤中的每一次操作其实都是在生产一些文件(系统运行环境、函数库、配置最终都是磁盘文件),所以镜像就是一堆文件的集合。
但需要注意的是,镜像文件不是随意堆放的,而是按照操作的步骤分层叠加而成,每一层形成的文件都会单独打包并标记一个唯一id,称为Layer(层)。这样,如果我们构建时用到的某些层其他人已经制作过,就可以直接拷贝使用这些层,而不用重复制作。再搭建其它层即可,这样逐层搭建,最终整个Java项目的镜像结构如图所示:
例如,第一步中需要的Linux运行环境,通用性就很强,所以Docker官方就制作了这样的只包含Linux运行环境的镜像。我们在制作java镜像时,就无需重复制作,直接使用Docker官方提供的CentOS或Ubuntu镜像作为基础镜像。
(2)Dockerfile
由于制作镜像的过程中,需要逐层处理和打包,比较复杂,所以Docker就提供了自动打包镜像的功能。我们只需要将打包的过程,每一层要做的事情用固定的语法写下来,交给Docker去执行即可。而这种记录镜像结构的文件就称为Dockerfile。
Dockerfile
就是一个文本文件,其中包含一个个的指令(Instruction),用指令来说明要执行什么操作来构建镜像。将来Docker可以根据Dockerfile帮我们构建镜像。
- Dockerfile支持以下指令:
Instruction |
Description |
添加本地或远程文件和目录。 |
|
使用构建时变量。 |
|
指定默认命令。 |
|
复制文件和目录。 |
|
指定默认可执行文件。 |
|
设置环境变量。 |
|
描述您的应用程序正在侦听哪些端口。 |
|
从基础镜像创建新的构建阶段。 |
|
启动时检查容器的运行状况。 |
|
向镜像添加元数据。 |
|
指定镜像的作者。 |
|
指定在构建中使用映像的说明。 |
|
执行构建命令。 |
|
设置镜像的默认shell。 |
|
指定退出容器的系统调用信号。 |
|
设置用户和组ID。 |
|
创建数据卷挂载。 |
|
更改工作目录。 |
更多详细的Dockerfile用法,请参考官网文档:https://docs.docker.com/reference/dockerfile/
常见指令如下:
指令 |
说明 |
示例 |
FROM |
指定基础镜像 |
|
ENV |
设置环境变量,可在后面指令使用 |
|
COPY |
拷贝本地文件到镜像的指定目录 |
|
RUN |
执行Linux的shell命令,一般是安装过程的命令 |
|
EXPOSE |
指定容器运行时监听的端口,是给镜像使用者看的 |
|
ENTRYPOINT |
镜像中应用的启动命令,容器运行时调用 |
|
例如,要基于Ubuntu镜像来构建一个Java应用,其Dockerfile内容如下:
# 指定基础镜像 FROM ubuntu:16.04 # 配置环境变量,JDK的安装目录、容器内时区 ENV JAVA_DIR=/usr/local ENV TZ=Asia/Shanghai # 拷贝jdk和java项目的包 COPY ./jdk8.tar.gz $JAVA_DIR/ COPY ./docker-demo.jar /tmp/app.jar # 设定时区 RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone # 安装JDK RUN cd $JAVA_DIR \ && tar -xf ./jdk8.tar.gz \ && mv ./jdk1.8.0_144 ./java8 # 配置环境变量 ENV JAVA_HOME=$JAVA_DIR/java8 ENV PATH=$PATH:$JAVA_HOME/bin # 指定项目监听的端口 EXPOSE 8080 # 入口,java项目的启动命令 ENTRYPOINT ["java", "-jar", "/app.jar"]
- 使用提供好的基础系统+JDK环境,我们在此基础上制作java镜像,就可以省去JDK的配置了
# 基础镜像 FROM openjdk:11.0-jre-buster # 设定时区 ENV TZ=Asia/Shanghai RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone # 拷贝jar包 COPY docker-demo.jar /app.jar # 入口 ENTRYPOINT ["java", "-jar", "/app.jar"]
当Dockerfile文件写好以后,就可以利用命令来构建镜像了。
(3)构建镜像
准备好要部署的Java项目docker-demo.jar
和Dockerfile
文件,将dockerfile-demo
文件夹拷贝到/root
下
执行命令,构建镜像
# 进入镜像目录 cd /root/dockerfile-demo # 开始构建,默认寻找当前目录下名为Dockerfile的文件 docker build -t docker-demo:1.0 .
命令说明:
docker build
: 就是构建一个docker镜像-t docker-demo:1.0
:-t
参数是指定镜像的名称(repository:tag
).
:最后的点是指构建时Dockerfile所在路径,由于我们进入了/root/dockerfile-demo目录,所以指定的是.
代表当前目录,也可以直接指定Dockerfile目录:
# 如果需要指定Dockerfile的路径,可以后面跟上指定PATH目录,和上面同理 docker build -t docker-demo:1.0 /root/dockerfile-demo/
镜像构建过程:
镜像构建结果:
尝试运行该镜像:
访问项目:http://192.168.8.100:8080/hello/count
4、网络
- Java项目往往需要访问其它各种中间件,例如MySQL、Redis等。现在我们的容器之间能否互相访问呢?
首先,我们使用docker inspect docker-mysql-hmall
查看下MySQL容器的详细信息,重点关注其中的网络IP地址(Networks.bridge.IPAddress属性):
也可以使用format过滤结果:docker inspect --format='{{range .NetworkSettings.Networks}}{{println .IPAddress}}{{end}}' 容器名称
通过命令进入c_docker-demo容器,在容器内,通过ping命令测试与其他容器的网络连通性
发现可以互联,没有问题。默认情况下,安装docker时会在宿主机上创建一个名为docker0
的虚拟网桥(子网网关 172.17.0.1
),所有容器都是以桥接(bridge)方式连接到Docker的一个虚拟网桥上:
Docker默认使用的子网172.17.0.0/16
落在一个特殊的B类私有地址段(172.16.0.0/12 [172.16.0.0 ~ 172.31.255.255]
),表示从 172.17.0.0
到 172.17.255.255
的所有 IP 地址。前16位网络位不动,后16位主机位随机分配给每个被创建出来的容器,一共65536
个IP地址。相当于是各个容器虽然是独立空间,但是他们通过网桥建立了连接,因此各容器都在一个网段上,可以相互通信访问。
但是,容器的网络IP其实是一个虚拟的IP,其值并不固定,当容器重启后如果之前的IP被其他容器占用,IP就会发生变化。因此,如果我们在开发时写死某个IP,而在部署时很可能MySQL容器的IP会发生变化,连接会失败。
所以,我们必须借助于docker的网络功能来解决这个问题,官方文档:https://docs.docker.com/reference/cli/docker/network/
- docker网络常见命令:
命令 |
说明 |
文档地址 |
docker network create |
创建一个网络 |
|
docker network ls |
查看所有网络 |
|
docker network rm |
删除指定网络 |
|
docker network prune |
清除未使用的网络 |
|
docker network connect |
使指定容器连接加入某网络 |
|
docker network disconnect |
使指定容器连接离开某网络 |
|
docker network inspect |
查看网络详细信息 |
- 案例:自定义网络
# 1.首先通过命令创建一个网络 docker network create hmall # 2.然后查看网络 docker network ls # 结果(其中,除了hmall以外,其它都是默认的网络): NETWORK ID NAME DRIVER SCOPE 639bc44d0a87 bridge bridge local 403f16ec62a2 hmall bridge local 0dc0f72a0fbb host host local cd8d3e8df47b none null local # 3.让c_docker-demo和docker-mysql-hmall都加入该网络,注意,在加入网络时可以通过--alias给容器起别名,这样该网络内的其它容器可以用别名互相访问! # 3.1 mysql容器,指定别名为db,另外每一个容器都有一个别名是容器名 docker network connect hmall docker-mysql-hmall --alias db # 3.2 db容器,也就是我们的java项目 docker network connect hmall c_docker-demo # 查看两个容器的网络IP docker inspect docker-mysql-hmall c_docker-demo --format='{{range .NetworkSettings.Networks}}{{println .IPAddress}}{{end}}' # 结果:每个容器分别拥有了两个Network,一个是默认网桥的,另一个是自定义网桥的 172.17.0.2 172.18.0.2 172.17.0.4 172.18.0.3 # 也可以在创建容器时使用 --network networkName 加入到指定网络,这样就不会加入到默认网络了。 docker rm -f c_docker-demo docker run -d --name c_docker-demo -p 8080:8080 --network hmall docker-demo:1.0 docker inspect c_docker-demo 172.20.0.3 # 加入指定网络,不加入默认网段,所以只有一个ip # 4.进入c_docker-demo容器,尝试利用别名访问db # 4.1 进入容器 docker exec -it c_docker-demo bash # 4.2 方式一:用db别名访问 ping db # 结果 PING db (172.18.0.2) 56(84) bytes of data. 64 bytes from mysql.hmall (172.18.0.2): icmp_seq=1 ttl=64 time=0.070 ms 64 bytes from mysql.hmall (172.18.0.2): icmp_seq=2 ttl=64 time=0.056 ms # 4.3 方式二:用容器名访问 ping mysql # 结果: PING mysql (172.18.0.2) 56(84) bytes of data. 64 bytes from mysql.hmall (172.18.0.2): icmp_seq=1 ttl=64 time=0.044 ms 64 bytes from mysql.hmall (172.18.0.2): icmp_seq=2 ttl=64 time=0.054 ms # 而ping默认网桥中的nginx就无法ping通,因为没加入自定义网络hmall ping 172.17.0.3
注意:
- 在自定义网络中,可以给容器起多个别名,默认的别名是容器名本身。
- 只有在同一个自定义网络中的容器,才可以通过别名互相访问,而默认网络不可以。
- 在创建容器时指定
--network networkName
参数,容器只会分配指定的网段ip,而不会加入默认网段。
总结:
- 在自定义网络中,可以给容器起多个别名,默认的别名是容器名本身
- 在同一个自定义网络中的容器,可以通过别名互相访问
三、项目部署
项目说明:
hmall
:商城的后端代码hmall-portal
:商城用户端的前端代码hmall-admin
:商城管理端的前端代码
部署的容器及端口说明:
项目 |
容器名 |
端口 |
备注 |
hmall |
hm |
8080 |
黑马商城后端API入口 |
hmall-portal |
nginx-hmall |
18080 |
黑马商城用户端入口 |
hmall-admin |
18081 |
黑马商城管理端入口 |
|
mysql |
docker-mysql-hmall |
3307:3306 |
数据库 |
在正式部署前,我们先删除之前的nginx、c_docker-demo两个容器
docker rm -f nginx c_docker-demo
docker-mysql-hmall容器中已经准备好了商城的数据,所以就不再删除了。
1、部署Java项目
hmall
项目是一个maven聚合项目,使用IDEA打开hmall
项目,查看项目结构如图:
我们要部署的就是其中的hm-service
,其中的配置文件采用了多环境配置的方式。
application-dev.yaml
是部署到开发环境的配置
hm: db: host: docker-mysql-hmall # 项目部署在docker后,用容器名和mysql互联,并且同一网络内的容器之间可以通过容器名互相访问 pw: 123 # docker中的MySQL密码 port: 3306 # 容器内的端口
application-local.yaml
是本地运行时的配置
hm: db: host: 192.168.8.100 # 本地服务器地址 pw: 123 # docker中的MySQL密码 port: 3307 # 本地MySQL 3307端口 --> 映射到容器内的3306端口
查看application.yaml,其中的JDBC地址并未写死,而是根据不同的开发环境读取不同的变量
激活的环境是dev开发环境(也就是Docker部署时),采用了MySQL的容器名作为地址,即docker-mysql-hmall,只要两者在一个网络,就一定能互相访问。
- 将父工程mvn clean、mvn install,接着对
hm-service
模块mvn package打成jar包
- 编写Dockerfile
# 基础镜像 FROM openjdk:17 # 设定时区 ENV TZ=Asia/Shanghai RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone # 拷贝jar包 COPY hm-service.jar /app.jar # 入口 ENTRYPOINT ["java", "-jar", "/app.jar"]
- 将
Dockerfile
和target目录下的hm-service.jar
上传到/root/hmall
目录下
- 进入到/root/hmall下使用
docker build -t 镜像名:tag .
命令构建镜像
- 创建hmall的容器hm,并启动,分配容器网络到hmall
- 部署成功,测试访问
2、前端部署
hmall-portal
和hmall-admin
是前端代码,需要基于nginx部署。
其中:
html
是静态资源目录,我们需要把hmall-portal
以及hmall-admin
都复制进去conf
是配置文件目录,nginx.conf
是nginx的配置文件,主要是完成对html
下的两个静态资源目录做代理
worker_processes 1; events { worker_connections 1024; } http { include mime.types; default_type application/json; sendfile on; keepalive_timeout 65; server { listen 18080; # 指定前端项目所在的位置 location / { root /usr/share/nginx/html/hmall-portal; } error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } location /api { rewrite /api/(.*) /$1 break; proxy_pass http://hm:8080; } } server { listen 18081; # 指定前端项目所在的位置 location / { root /usr/share/nginx/html/hmall-admin; } error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } location /api { rewrite /api/(.*) /$1 break; proxy_pass http://hm:8080; } } }
我们现在要做的就是把整个nginx-hmall
目录上传到虚拟机的/root
目录下:
然后创建nginx容器并完成两个挂载:
- 把
/root/nginx-hmall/conf/nginx.conf
挂载到/etc/nginx/nginx.conf
- 把
/root/nginx-hmall/html
挂载到/usr/share/nginx/html
由于需要让nginx同时代理hmall-portal
和hmall-admin
两套前端资源,因此我们需要暴露两个端口:
18080
:对应hmall-portal
18081
:对应hmall-admin
命令如下:
docker run -d \ --name nginx-hmall \ -p 18080:18080 \ -p 18081:18081 \ -v /root/nginx-hmall/html:/usr/share/nginx/html \ -v /root/nginx-hmall/conf/nginx.conf:/etc/nginx/nginx.conf \ --network hmall \ nginx
测试,通过浏览器访问:http://虚拟机ip:18080
3、DockerCompose
我们部署的是一个简单的java项目,其中包含3个容器:
- MySQL
- Nginx
- Java项目
如果是稍微复杂的项目,还会有各种各样的其它中间件,需要部署的东西远不止3个。如果还像之前那样手动的逐一部署,就太麻烦了,而且项目各组件之间部署管理没有统一性。
而Docker Compose
就可以帮助我们实现多个相互关联的Docker容器的快速部署。它允许用户通过一个单独的 docker-compose.yml
模板文件(YAML 格式)来定义一组相关联的应用容器,实现容器编排。
关于docker compose安装:https://docs.docker.com/compose/install/,因为在《最新安装Docker篇》已经安装过了docker compose plugins,因此这里不需要再次安装。
(1)基本语法
docker-compose.yml
文件的基本语法可以参考官方文档:https://docs.docker.com/reference/compose-file/
旧版本Compose文件格式:https://docs.docker.com/reference/compose-file/legacy-versions/
docker-compose文件中可以定义多个相互关联的应用容器,每一个应用容器被称为一个服务(service)。由于service就是在定义某个应用的运行时参数,因此与docker run
参数非常相似。
- 举例来说,用docker run部署MySQL的命令如下:
docker run -d \ --name docker-mysql-hmall \ -p 3307:3306 \ -e TZ=Asia/Shanghai \ -e MYSQL_ROOT_PASSWORD=123 \ -v /root/docker-mysql-hmall/data:/var/lib/mysql \ -v /root/docker-mysql-hmall/conf:/etc/mysql/conf.d \ -v /root/docker-mysql-hmall/init:/docker-entrypoint-initdb.d \ --network hmall mysql
- 如果用
docker-compose.yml
文件来定义,就是这样:
version: "3.8" services: mysql: image: mysql container_name: docker-mysql-hmall ports: - "3307:3306" environment: TZ: Asia/Shanghai MYSQL_ROOT_PASSWORD: 123 volumes: - "/root/docker-mysql-hmall/conf:/etc/mysql/conf.d" - "/root/docker-mysql-hmall/data:/var/lib/mysql" - "/root/docker-mysql-hmall/init:/docker-entrypoint-initdb.d" networks: - hm-net networks: hm-net: name: hmall
- 对比如下:
docker run 参数 |
docker compose 指令 |
说明 |
--name |
container_name |
容器名称 |
-p |
ports |
端口映射 |
-e |
environment |
环境变量 |
-v |
volumes |
数据卷配置 |
--network |
networks |
网络 |
- 黑马商城部署文件:
services: mysql: # 容器标识 image: mysql container_name: docker-mysql-hmall ports: - "3307:3306" environment: TZ: Asia/Shanghai MYSQL_ROOT_PASSWORD: 123 volumes: - "/root/docker-mysql-hmall/conf:/etc/mysql/conf.d" - "/root/docker-mysql-hmall/data:/var/lib/mysql" - "/root/docker-mysql-hmall/init:/docker-entrypoint-initdb.d" networks: - hm-net hmall: build: # 在context参数目录下根据Dockerfile构建镜像 context: . # 相当于/root/hmall,Dockerfile在Java项目hmall目录下 dockerfile: Dockerfile container_name: hm ports: - "8080:8080" networks: - hm-net # 这里会先创建MySQL容器,再创建hmall容器 depends_on: - mysql nginx: image: nginx container_name: nginx-hmall ports: - "18080:18080" - "18081:18081" volumes: - "/root/nginx-hmall/conf/nginx.conf:/etc/nginx/nginx.conf" - "/root/nginx-hmall/html:/usr/share/nginx/html" depends_on: - hmall networks: - hm-net networks: hm-net: # 网络标识 name: hmall
(2)基础命令
编写好docker-compose.yml文件,就可以部署项目了。常见的命令:https://docs.docker.com/reference/cli/docker/compose/
- 基本语法如下:
docker compose [OPTIONS] [COMMAND]
- 其中,[OPTIONS] 和 [COMMAND] 都是可选参数,比较常用的有:
类型 |
参数或指令 |
说明 |
Options |
-f |
指定compose文件的路径和名称,不加默认compose文件在当前目录 |
-p |
指定project名称。project就是当前compose文件中设置的多个service的集合,是逻辑概念 |
|
Commands |
up |
创建并启动所有service容器,后面常搭配-d参数,使service容器后台运行 |
down |
停止并移除所有容器、网络 |
|
ps |
列出所有启动的容器 |
|
logs |
查看指定容器的日志 |
|
stop |
停止容器 |
|
start |
启动容器 |
|
restart |
重启容器 |
|
top |
查看运行的进程 |
|
exec |
在指定的运行中容器中执行命令 |
注意:这些命令参数和指令跟在docker compose后面,作用域是当前project。
- 使用示例
# 1.进入/root/hmall目录 cd /root/hmall # 2.删除所有旧容器,$(docker ps -qa)是所有容器id docker rm -f $(docker ps -qa) # 或者 docker rm -f nginx-hmall hm docker-mysql-hmall # 3.删除hmall、docker-demo:1.0镜像 docker rmi hmall docker-demo:1.0 # 4.清空MySQL数据 rm -rf docker-mysql-hmall/data # 删除hmall网络 docker network rm hmall # 5.启动所有, -d 参数是后台启动 docker compose up -d # 或者 docker compose -f /root/hmall/docker-compose.yml up -d # 结果: [+] Building 15.5s (8/8) FINISHED => [internal] load build definition from Dockerfile 0.0s => => transferring dockerfile: 358B 0.0s => [internal] load .dockerignore 0.0s => => transferring context: 2B 0.0s => [internal] load metadata for docker.io/library/openjdk:11.0-jre-buster 15.4s => [1/3] FROM docker.io/library/openjdk:11.0-jre-buster@sha256:3546a17e6fb4ff4fa681c3 0.0s => [internal] load build context 0.0s => => transferring context: 98B 0.0s => CACHED [2/3] RUN ln -snf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo 0.0s => CACHED [3/3] COPY hm-service.jar /app.jar 0.0s => exporting to image 0.0s => => exporting layers 0.0s => => writing image sha256:32eebee16acde22550232f2eb80c69d2ce813ed099640e4cfed2193f71 0.0s => => naming to docker.io/library/root-hmall 0.0s [+] Running 4/4 ✔ Network hmall Created 0.2s ✔ Container mysql Started 0.5s ✔ Container hmall Started 0.9s ✔ Container nginx Started 1.5s # 6.查看镜像 docker compose images # 结果 CONTAINER REPOSITORY TAG IMAGE ID SIZE hmall root-hmall latest 32eebee16acd 362MB mysql mysql latest 3218b38490ce 516MB nginx nginx latest 605c77e624dd 141MB # 7.查看容器 docker compose ps # 结果 NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS hmall root-hmall "java -jar /app.jar" hmall 54 seconds ago Up 52 seconds 0.0.0.0:8080->8080/tcp, :::8080->8080/tcp mysql mysql "docker-entrypoint.s…" mysql 54 seconds ago Up 53 seconds 0.0.0.0:3306->3306/tcp, :::3306->3306/tcp, 33060/tcp nginx nginx "/docker-entrypoint.…" nginx 54 seconds ago Up 52 seconds 80/tcp, 0.0.0.0:18080-18081->18080-18081/tcp, :::18080-18081->18080-18081/tcp
- 一键部署所有镜像和容器服务:
docker compose -f docker-compose.yml文件路径 up -d
打开浏览器,访问前台首页:http://IP:18080
分页查询商品页面:http://IP:18080/search/list
- 如果想要关闭服务使用:
docker compose stop [service]
- 如果想要开启服务使用:
docker compose start [service]
- 如果想要一键移除当前project所有容器和镜像,使用:
docker compose down
至此,我们使用docker compose进行容器服务的统一管理,并且将项目一键部署到Linux服务器,实现了容器编排和自动化部署。