在 Java 大型项目的开发中,微服务虽然具备各种各样的优势,但服务的拆分通用给部署带来了很大的麻烦。
可以说项目越大,运行环境也越复杂,部署时就会遇到:
如上图:一个项目中,部署时需要依赖于 node.js、Redis、RabbitMQ、MySQL 等,这些服务部署时所需要的函数库、依赖项各不相同,甚至会有冲突,给部署带来了极大的困难。
Docker 为了解决依赖的兼容问题的,采用了两个手段:
这样打包好的应用包中,既包含应用本身,也包含应用所需要的 Libs、Deps,无需再操作系统上安装这些,自然就不存在不同应用之间的兼容问题了。
虽然解决了不同应用的兼容问题,但是开发、测试等环境会存在差异,操作系统版本也会有差异,怎么解决这些问题呢?
要解决不同操作系统环境差异问题,必须先了解操作系统结构。以一个 Ubuntu 操作系统为例,结构如下:
结构包括:
而应用于计算机交互的流程如下:
举例:Ubuntu 和 CentOS 都是基于 Linux 内核,无非是系统应用不同,提供的函数库有差异,那如果将一个Ubuntu 版本的 MySQL 安装到 CentOS 上,MySQL 在调用 Ubuntu 函数库时,会发现找不到或者不匹配,如图:
那 Docker 如何解决不同系统环境的问题?
Docker 是一个开源的应用容器引擎,诞生于 2013 年初,基于 Go 语言实现,dotCloud 公司出品
Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中,然后发布到任何流行的 Linux 机器上。而容器是完全使用沙箱机制,相互隔离,容器性能开销极低。
Docker 从 17.03 版本之后分为 CE(Community Edition:社区版)免费,支持周期 7 个月和 EE(Enterprise Edition:企业版)强调安全,付费使用,支持周期 24 个月。
官网地址:https://www.docker.com/
Docker 通俗的讲,是服务器中高性能的虚拟机,可以将一台物理机虚拟 N 多台虚拟机的机器,互相之间隔离,互不影响。
特点:
虚拟化(Virtualization)是一种资源管理技术,是将计算机的各种实体资源,如服务器、网络、内存及存储等,予以抽象、转换后呈现出来,打破实体结构间的不可切割的障碍,使用户可以比原本的组态更好的方式来应用这些资源。
这些资源的核心虚拟部分是不受现有资源的架设方式,低于或者物理组态所限制,一般所指的虚拟化资源包括计算能力和资料存储。
虚拟化技术主要作用:
Docker 可以让一个应用在任何操作系统中非常方便的运行。而以前我们接触的虚拟机,也能在一个操作系统中,运行另外一个操作系统,保护系统中的任何应用。两者有什么差异呢?
虚拟机(virtual machine):是在操作系统中模拟硬件设备,然后运行另一个操作系统,比如在 Windows 系统里面运行 Ubuntu 系统,这样就可以运行任意的 Ubuntu 应用了。
Docker:仅仅是封装函数库,并没有模拟完整的操作系统
下面的图片比较了 Docker 和传统虚拟化方式的不同之处,总的来说:Docker 是操作系统层面上实现虚拟化,直接复用本地主机的操作系统,而虚拟机则是在硬件层面实现虚拟化。
特性 | Docker | 虚拟机 |
---|---|---|
隔离级别 | 进程级 | 操作系统级 |
隔离策略 | 直接运行在宿主机内核中 | 运行于 Hypervisor 上 |
启动速度 | 秒级 | 分钟级 |
占用磁盘空间 | 一般为MB | 一般为GB |
性能 | 接近原生硬件 | 弱鸡 |
系统支持量 | 单机可跑几十个容器 | 单机几个虚拟OS |
运行环境 | 主要在 Linux | 主要在 Windows |
相同:Docker 和虚拟机都是虚拟化技术,具备资源隔离和分配优势
不同:
宿主机:安装 Docker 守护进程的 Linux 服务器,称之为宿主机;
镜像(Image):Docker 将应用程序及其所需的依赖、函数库、环境、配置等文件打包在一起,称为镜像。
容器(Container):镜像运行之后的实体,镜像和容器的关系,就像是面向对象程序设计中的类和对象一样,镜像是静态的定义,容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等,也可以有多个。
例如:你下载了一个 QQ,如果我们将 QQ 在磁盘上的运行文件及其运行的操作系统依赖打包,形成 QQ 镜像。然后你可以启动多次,双开、甚至三开 QQ,跟多个妹子聊天。
仓库(Repository):仓库可看成一个代码控制中心,用来保存镜像。类似 Maven 的中央仓库
Docker 官方镜像的托管平台叫 Docker Hub,我们可以将自己的镜像共享到 Docker Hub,也可以从 Docker Hub 上拉取镜像。同时国内也有类似于 Docker Hub 的公开服务,比如网易云镜像服务、阿里云镜像库等。
Docker是一个 C/S 结构的程序,由两部分组成:
服务端(Server):Docker 的守护进程,负责处理 Docker 指令,管理镜像和容器等等
客户端(Client):通过命令或 RestAPI 向 Docker 服务端发送指令。可以在本地或远程向服务端发送指令。
安装之前进行虚拟机网卡配置:IP 地址 192.168.88.101、网关为 192.168.88.2
Docker 官方建议在 Ubuntu 中安装,因为 Docker 是基于 Ubuntu 发布的,而且一般 Docker 出现的问题 Ubuntu 是最先更新或者打补丁的。在很多版本的 CentOS 中是不支持更新最新的一些补丁包的。
由于我们学习的环境都使用的是 CentOS,因此这里我们将 Docker 安装到 CentOS 上。
注意:这里建议安装在 CentOS 7.x 以上的版本!其他更多安装参考官网:https://docs.docker.com/get-docker/
0)如果之前安装过旧版本的Docker,可以使用下面命令卸载
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 \
docker-ce
1)yum 包更新到最新
sudo yum -y update
2)安装需要的软件包, yum-util 提供 yum-config-manager 功能,另外两个是 devicemapper 驱动依赖的
sudo yum install -y yum-utils
3)设置 yum 源为阿里云
sudo yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
sudo yum makecache fast
4)安装 docker
sudo yum -y install docker-ce
5)安装后查看 docker 版本
docker -v
6)通过运行 hello-world 来验证是否正确安装了 Docker
docker run hello-world
Docker应用需要用到各种端口,逐一去修改防火墙设置。非常麻烦,因此建议大家直接关闭防火墙!
# 关闭
systemctl stop firewalld
# 禁止开机启动防火墙
systemctl disable firewalld
操作 | 指令 |
---|---|
启动 Docker | systemctl start docker |
停止 Docker | systemctl stop docker |
重启 Docker | systemctl restart docker |
查看 Docker 状态 | systemctl status docker |
设置开机启动 | systemctl enable docker |
查看 Docker 概要信息 | docker info |
查看 Docker 帮助文档 | docker --help |
默认情况,将从Docker Hub(https://hub.docker.com)下载镜像,因为太慢,一般都会配置镜像加速器;
建议配置阿里云镜像加速,可以自己从阿里云上申请!必须要注册,每个人分配一个免费的 Docker 镜像加速地址,速度杠杠的~,配置完成记得刷新配置
sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<-'EOF'
{
"registry-mirrors": ["https://6wgh41zg.mirror.aliyuncs.com"]
}
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker
首先来看下镜像的名称组成:
这里的 MySQL 就是 repository,5.7就是tag,合一起就是镜像名称,代表5.7版本的MySQL镜像。
而常见的镜像操作命令如图:
如果你需要从网络中查找需要的镜像,可以通过以下命令搜索,注意:必须确保当前系统能联网,当然最好的方式还是在 Docker Hub 上进行搜索查找
docker search 镜像名称
拉取镜像:从Docker仓库下载镜像到本地,镜像名称格式为名称:版本号,如果版本号不指定则是最新的版本。如果不知道镜像版本,可以去 Docker Hub 搜索对应镜像查看。
docker pull 镜像名称
例如,我要下载 nginx 最新的镜像:docker pull nginx
查看本地所有镜像
docker images
这些镜像都是存储在 Docker 宿主机的 /var/lib/docker 目录下
如果该镜像正在使用(创建了容器),则先删除容器再删除镜像
docker rmi 镜像ID # 按镜像ID删除镜像
docker rmi `docker images -qa` # 删除所有镜像
其中:
查看正在运行的容器:docker ps
查看所有容器:docker ps –a
查看最后一次运行的容器:docker ps –l
查看停止的容器:docker ps -f status=exited
查看容器日志:docker logs 容器ID
docker run
创建容器常用的参数说明:
# OPTIONS说明(常用):有些是一个减号,有些是两个减号
--name:为创建的容器命名。
-d:在run后面加上-d参数,会创建一个守护式容器在后台运行(这样创建容器后不会自动登录容器)。
-i:以交互模式运行容器,通常与 -t 同时使用
-t:表示容器启动后会进入其命令行。为容器重新分配一个伪输入终端,通常与 -i 同时使用
-p: 表示端口映射,前者表示宿主机端口,后者是容器内的映射端口,可以使用多个-p做多个端口映射
--restart=always:可以让容器开机自启
docker run -it --name=容器名称 镜像名称:标签 /bin/bash
# 举例:/bin/bash的作用是因为docker后台必须运行一个进程,否则容器就会退出,在这里表示启动容器后启动bash。
docker run -it --name=mycentos centos:7 /bin/bash
这时我们通过ps命令查看,发现可以看到启动的容器,状态为启动状态
退出当前容器
exit
什么是守护式容器:能够长期运行、 没有交互式会话、 适合运行应用程序和服务
docker run -d --name=容器名称 镜像名称:标签
# 举例:
docker run -d --name=nginx1 nginx:latest
docker exec -it 容器名称 (或者容器ID) /bin/bash
# 举例:
docker run -it nginx1 /bin/bash
注意:这里的登陆容器之后执行的脚本 /bin/bash 必须写
默认情况下,容器是隔离环境,我们直接访问宿主机的 80 端口,肯定访问不到容器中的 nginx。
所以我们需要将容器的 80 端口与宿主机的 80 端口关联起来,当我们访问宿主机的 80 端口时,就会被映射到容器的 80,这样就能访问到 nginx 了:
操作 | 指令 |
---|---|
启动容器 | docker start 容器名称(或者容器ID) |
停止容器 | docker stop 容器名称(或者容器ID) |
重启容器 | docker restart 容器名称(或者容器id) |
强制停止容器 | docker kill 容器名称(或者容器id) |
如果我们需要将文件拷贝到容器内可以使用cp命令
docker cp 宿主机需要拷贝的文件或目录 容器名称:容器目录
# 举例:
docker cp /etc/hosts nginx1:/tmp
也可以将文件从容器内拷贝出来
docker cp 容器名称:容器目录 宿主机存放的文件或目录
# 举例:
docker cp nginx1:/usr/share/nginx/html/index.html /tmp
我们可以通过以下命令查看容器运行的各种数据
docker inspect 容器名称(容器ID)
# 举例:
docker inspect nginx1
docker inspect --format='{{.NetworkSettings.IPAddress}}' 容器名称(容器ID)
# 举例:
docker inspect --format='{{.NetworkSettings.IPAddress}}' nginx1
删除指定的容器
docker rm 容器名称(容器ID) # 无法删除运行的容器
docker rm `docker ps -qa` # 删除所有容器
docker rm -f 容器名称(容器ID) # 慎用:可以删除正在运行的容器
我们可以通过以下命令将镜像保存为 tar 文件
docker save -o {镜像的备份文件} {镜像名称}
# 举例:-o表示输出到的文件
docker save -o nginx.tar nginx:latest
我们先删除掉 nginx:latest 镜像:docker rmi nginx:latest
,然后再进行恢复
docker load -i {备份的镜像文件}
# 举例:-i表示指定导入的文件
docker load -i nginx.tar
我们可以通过以下命令将容器保存为镜像
docker commit [OPTIONS] 容器名称 [镜像名称[:TAG]]
# 举例,不指定为latest
docker commit nginx1 test_nginx
docker images # 查看
由于每个容器是互相隔离的,那么就会产生以下的问题:
Docker 容器删除之后,在容器中产生的数据还在吗?
Docker 容器和外部机器可以直接交换文件吗?
容器之间怎么进行数据交互?
要解决以上问题,就需要将数据与容器解耦,这就要用到数据卷了。
数据卷(volume):是一个虚拟目录,指向宿主机文件系统中的某个目录。当容器目录和数据卷目录绑定后,对方的修改自然会立即同步;一个数据卷可以被多个容器同时挂载,一个容器也可以挂载多个数据卷。
数据卷操作的基本语法如下:
docker volume [COMMAND]
其中 docker volume 命令是数据卷操作,根据命令后跟随的 command 来确定下一步的操作:
create # 创建一个volume
ls # 列出所有的volume
inspect # 显示一个或多个volume的信息
prune # 删除未使用的volume
rm # 删除一个或多个指定的volume
使用上面命令演示:创建一个数据卷,并查看数据卷在宿主机的目录位置,然后删除它
# 1、创建数据卷
docker volume create html
# 2、查看所有数据卷
docker volume ls
# 3、查看数据卷的详细信息
docker volume inspect html
# 可以看到,我们创建的html这个数据卷关联的宿主机目录为/var/lib/docker/volumes/html/_data目录。
[
{
"CreatedAt": "2021-11-11T16:52:34+08:00",
"Driver": "local",
"Labels": {},
"Mountpoint": "/var/lib/docker/volumes/html/_data",
"Name": "html",
"Options": {},
"Scope": "local"
}
]
# 4、删除html的数据卷
docker volume rm html
# 5、删除所有未使用的数据卷
docker volume prune
我们在创建容器时,可以通过 -v 参数来挂载一个数据卷到某个容器内目录
创建启动容器时,使用 –v 参数设置数据卷,冒号前面是宿主机目录,冒号后是容器目录
注意事项:如果宿主机目录不存在,会自动创建,可以挂载多个数据卷
docker run ... –v 宿主机目录(文件):容器内目录(文件) ...
案例:创建一个 nginx 容器,挂载容器内的 html 目录到默认目录
# 1、创建容器并挂载数据卷到容器内的HTML目录
docker run -d --name=nginx2 -v html:/usr/share/nginx/html -p 80:80 nginx
# 2、查看html数据卷的位置
docker volume inspect html
# 3、进入html数据卷所在位置,并修改HTML内容
cd /var/lib/docker/volumes/html/_data
# 修改文件,保存,然后刷新网页就可以看到效果了
vi index.html
我们可以通过以下命令,挂载多个数据卷,其实就是多个 -v 参数
docker run -d --name=nginx3 -v /tmp/data1:/data1 -v /tmp/data2:/data2 nginx
方式一:多个容器挂载1个数据卷,实现数据共享,2个容器都挂载到/tmp/data_common
下
docker run -d --name=nginx4 -v /tmp/data_common:/data4 nginx
docker run -d --name=nginx5 -v /tmp/data_common:/data5 nginx
方式二:多个容器挂载1个容器(这个容器挂载1个数据卷)
# 创建启动 c3 数据卷容器,使用 –v 参数设置数据卷,激素创建一个容器,挂载一个目录,让其他容器继承自该容器
docker run -d --name=c3 -v /tmp/data_container:/c3 nginx
# 创建启动 c1 c2 容器,使用 –-volumes-from 参数设置数据卷
docker run -d --name=c1 --volumes-from c3 nginx
docker run -d --name=c2 --volumes-from c3 nginx
熟悉基本应用部署的使用
开启 IP 转发,参考:https://www.cnblogs.com/sfnz/p/6555723.html
echo net.ipv4.ip_forward=1 >> /etc/sysctl.conf
# 重启network和docker服务
systemctl restart network && systemctl restart docker
# 查看是否修改成功 (备注:返回1,就是成功)
sysctl net.ipv4.ip_forward
docker pull mysql:8.0.27
docker run -d --name=mysql1 -p 3307:3306 \
-v /opt/mysql/logs:/logs \
-v /opt/mysql/data:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=123456 \
mysql:8.0.27
# 参数说明:
-p 3307:3306:将容器的 3306 端口映射到宿主机的 3307 端口。
-v /opt/mysql/logs:/logs:将主机目录(/opt/mysql/logs)挂载到容器中的 /logs 日志目录
-v /opt/mysql/data:/var/lib/mysql:将主机目录(/opt/mysql/data)挂载到容器的 /var/lib/mysql 数据目录
-e MYSQL_ROOT_PASSWORD=123456:初始化 root 用户的密码。
docker exec -it mysql1 mysql -uroot -p123456
# 修改远程访问
GRANT ALL ON *.* TO 'root'@'%';
docker pull tomcat
docker run -d --name=tomcat1 -p 8080:8080 -v /opt/tomcat/webapps:/usr/local/tomcat/webapps tomcat
# 参数说明:
-p 8080:8080:将容器的8080端口映射到主机的8080端口
-v /opt/tomcat/webapps:/usr/local/tomcat/webapps:将主机目录(/opt/tomcat/webapps)挂载到容器webapps
向 Tomcat 中部署服务
使用外部机器访问 Tomcat,测试部署服务
docker pull redis:latest
docker run -d --name=redis1 -p 6379:6379 redis:latest
docker exec -it redis1 redis-cli
常见的镜像在 Docker Hub 就能找到,但是我们自己写的项目就必须自己构建镜像了。而要自定义镜像,就必须先了解镜像的结构才行。
镜像就是将应用程序及其需要的系统函数库、环境、配置、依赖打包而成。以MySQL为例看看镜像的组成结构:
简单来说,镜像就是在系统函数库、运行环境基础上,添加应用程序文件、配置文件、依赖文件等组合,然后编写好启动脚本打包在一起形成的文件。我们要构建镜像,其实就是实现上述打包的过程。
Docker镜像都是只读的, 当容器启动时,一个新的可写层被加载到镜像的顶部,这一层通常被称作“容器层”, “容器层”之下的都叫“镜像层”。
UnionFS(联合文件系统): Union文件系统是一种分层、轻量级并且高性能的文件系统,它支持对文件系统的修改作为一次提交来一层层的叠加,同时可以将不同目录挂载到同一个虚拟文件系统下(unite several directories into a single virtual filesystem)。
Union 文件系统是 Docker 镜像的基础。 镜像可以通过分层来进行继承,基于基础镜像(没有父镜像),可以制作各种具体的应用镜像。
UnionFS 的特性:可以一次同时加载多个文件系统,但从外面看起来,只能看到一个文件系统,联合加载会把各层文件系统叠加起来,这样最终的文件系统会包含所有底层的文件和目录。
Docker 镜像本质是什么?
Docker 中一个 CentOS 镜像为什么只有200MB,而一个 CentOS 操作系统的 ISO 文件要几个个G?
Docker 中一个 Tomcat 镜像为什么有500MB,而一个 Tomcat 安装包只有70多MB?
在下载镜像的过程中我可以看到 Docker 的镜像好像是在一层一层的在下载,为什么采用分层结构?
Docker的镜像实际上由一层一层的文件系统组成, 这种层级的文件系统就是UnionFS。 包含两部分:
1)Docker 镜像本质是什么?
2)Docker 中一个 CentOS 镜像为什么只有 200MB,而一个 CentOS 操作系统的 ISO 文件要几个 G?
3)Docker 中一个 Tomcat 镜像为什么有 500MB,而一个 Tomcat 安装包只有 10 多 MB?
4、为什么 Docker 镜像采用分层结构?
构建自定义的镜像时,并不需要一个个文件去拷贝,打包。
我们只需要告诉 Docker,我们的镜像的组成,需要哪些 BaseImage、需要拷贝什么文件、需要安装什么依赖、启动脚本是什么,将来 Docker 会帮助我们构建镜像。而描述上述信息的文件就是 Dockerfile 文件。
可以说 Dockerfile 就是一个文本文件,包含了构建镜像文件的指令,用指令来说明要执行什么操作来构建镜像。每一个指令都会形成一层 Layer,最终构建出一个新的镜像;
1)Dockerfile 内容基础知识
2)Docker 执行 Dockerfile 大致流程
3)从应用软件的角度来看,Dockerfile、Docker 镜像、Docker 容器分别代表软件的三个不同阶段
可以理解为 Dockerfile 面向开发,Docker 镜像成为交付标准,Docker 容器则涉及部署与运维,三者缺一不可,合力充当 Docker 体系的基石。
更多语法说明,参考官网文档:https://docs.docker.com/engine/reference/builder
指令示例 | 作用 |
---|---|
FROM image_name:tag | 定义了使用哪个基础镜像启动构建流程 |
LABEL key value | 声明我们需要各种元数据,比如版本、作者、描述等等(可以写多条) |
ENV key value | 设置环境变量(可以写多条) |
RUN command | 执行一段命令默认是/bin/sh,也是Dockerfile的核心部分(可以写多条) |
COPY source_dir/file dest_dir/file | 将宿主机文件复制到容器内,如果有压缩文件并不能解压 |
ADD source_dir/file dest_dir/file | 与COPY类似,但如果是压缩文件会在复制后自动解压 |
WORKDIR path_dir | 设置工作目录,如果没有会自动创建 |
EXPOSE 8080/udp | 运行时监听的端口, 启动容器的时候使用 -p 绑定 |
ENTRYPOINT command | 镜像中应用的启动命令,在容器运行时调用,只有最后一个会生效 |
发布 SpringBoot 项目到 Docker 容器中
实现过程:
1)编辑 Dockerfile 文件
# 定义基础镜像
FROM java:8-alpine
# 添加jar包文件到镜像中
COPY ./springboot.jar /opt/app.jar
# 暴露端口
EXPOSE 8080
# 定义当前镜像启动容器时,执行命令
ENTRYPOINT java -jar /opt/app.jar
2)在宿主机中,构建镜像
docker bulid –t {镜像名称:版本} {构建文件所在路径}
# 举例:注意后面的空格和点,不要省略,点表示当前目录
docker build -t boot:1.0 ./
# -t:镜像的名字及标签,通常 name:tag 或者 name 格式,不设置为latest
3)基于镜像,启动容器
docker run -d --name=boot1 -p 9001:8080 boot:1.0
4)测试:http://192.168.88.101:9001/sayHello