深入理解Docker,从入门到精通-Part1(基础使用)
一、Docker基本概念
Docker架构
基本组件的介绍
Docker Client 是用户界面,它支持用户与Docker Daemon之间通信
Docker Daemon Docker最核心的后台进程,运行于主机上,处理服务请求
Docker registry是中央registry,支持拥有公有与私有访问权限的Docker容器镜像的备份
Docker Containers负责应用程序的运行,包括操作系统、用户添加的文件以及元数据
Docker Images是一个只读模板,用来运行Docker容器
DockerFile是文件指令集,用来说明如何自动创建Docker镜像
三个基本概念
镜像
Docker 镜像是一个特殊的文件系统,除了提供容器运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的一些配置参数(如匿名卷、环境变量、用户等),镜像不包含任何动态数据,其内容在构建之后也不会被改变。
简单理解,镜像就是一个文件,特殊之处在于能被Docker加载运行
但实际上并不是由单个文件组成那么简单,背后是一个文件系统,采用分层存储。
分层存储指的是镜像是由一组文件系统组成,或者说,由多层文件系统联合组成。
关键技术就是Union FS,联合文件系统是(Union FS)是linux的存储技术,也是Docker镜像的存储方式, 它是分层的文件系统,将不同目录拉到同一个虚拟目录下,下图展示了Docker用Union FS 搭建的分层镜像:(比如最下层是操作系统的引导,上一层是Linux操作系统,再上一层是Tomcat,jdk,再上一层是应用代码)
镜像构建时,会一层层构建,前一层是后一层的基础,每一层构建完就不会再发生改变,后一层上的任何改变只发生在自己这一层。(比如,删除前一层文件的操作,实际不是真的删除前一层的文件,而是仅在当前层标记为该文件已删除,在最终容器运行的时候,虽然不会看到这个文件,但是实际上该文件会一直跟随镜像,因此,在构建镜像的时候,需要额外小心,每一层尽量只包含该层需要添加的东西,任何额外的东西应该在该层构建结束前清理掉)
容器
镜像和容器的关系,就像是面向对象程序设计中的 类 和 实例 一样
镜像是静态的定义,容器是镜像运行时的实体,容器可以被创建、启动、停止、删除、暂停等。
容器的实质是进程,但与直接在宿主执行的进程不同,容器进程运行于属于自己的独立的命名空间,因此容器可以拥有自己的root文件系统、自己的网络配置、自己的进程空间,甚至自己的用户 ID 空间。
容器内的进程是运行在一个隔离的环境里,使用起来,就好像是在一个独立于宿主的系统下操作一样,这种特性使得容器封装的应用比直接在宿主运行更加安全。
前面讲过镜像使用的是分层存储,容器也是如此,每一个容器运行时,是以镜像为基础层,在其上创建一个当前容器的存储层,我们可以称这个为容器运行时读写而准备的存储层为容器存储层。
容器存储层的生存周期和容器一样,容器消亡时,容器存储层也随之消亡,因此,任何保存于容器存储层的信息都会随容器删除而丢失。
仓库
镜像构建完成后,可以很容易的在当前宿主机上运行,但是,如果需要在其它服务器上使用这个镜
像,我们就需要一个集中的存储、分发镜像的服务,Docker Registry 就是这样的服务。
二、Docker安装部署MySQL
1、下载镜像,这里以mysql5.7.41版本为例
docker pull mysql:5.7.41
2、 mysql配置
创建MySQL挂载目录,等会会解释什么是挂载路径
# 创建MySQL配置的文件夹
mkdir -p /tmp/etc/mysql
# 编辑my.cnf配置文件
vi /tmp/etc/mysql/my.cnf
配置MySQL忽略大小写,在我们创建的MySQL配置文件挂载点的目录的my.cnf文件加入如下内容
[mysqld]
lower_case_table_names=1
创建MySQL数据目录
因为默认MySQL启动后他的文件是在容器中的,如果我们删除容器,数据也将会消失,我们需要将数据挂载出来。
#创建mysql存储的目录
mkdir -p /tmp/data/mysql
启动MySQL
docker run -d -p 3306:3306 -v /tmp/etc/mysql:/etc/mysql/mysql.conf.d/ -v /tmp/data/mysql:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=root --name mysql mysql:5.7.41
用docker ps查询,出现如下结果表明mysql启动成功
参数解释
- -d:是指容器后台运行,如果不加
-d
,当用户断开客户端时容器会结束运行 - -p:将容器的3306端口映射到主机的3306端口,用来暴漏端口的
- -v:这个命令是用来挂载目录的,将本地目录挂载到容器中,这样容器操作的就是本地目录
- -e:这个命令是配置环境参数的,这里
MYSQL_ROOT_PASSWORD=root
指的是用root用户运行mysql,可以登录Docker容器通过ENV
命令查看 - --name:这个命令是配置Mysql的容器名称的,如果不配置,默认是随机生成的名字
查看容器挂载的配置文件
可以通过docker exec -ti mysql /bin/bash
命令登录容器,检查挂载的配置文件情况
我们可以看到我们修改的配置文件已经被挂载到了docker内部,这里面用到了exec命令,主要是在docker容器中运行命令
docker exec就是进入容器的命令,其中
-t 表示命令行交互模式 -i 表示展示容器输入信息STDIN
至此,MySQL安装部署成功
三、Docker安装部署Nacos
1、下载镜像,这里以nacos2.0.1版本为例
docker pull nacos/nacos-server:2.0.1
2、运行镜像
docker run -d -p 8848:8848 --name nacos --env MODE=standalone nacos/nacos-server:2.0.1
参数解释:
-d:后台进程运行
--name:容器名称
--env MODE=standlone:设置环境变量,这里表示单机模式启动
登录控制台,成功显示
至此,Nacos单机版安装部署成功
四、SpringBoot项目部署
将项目通过Maven进行打包,再去Target目录复制Jar包文件上传至服务器
编写DockerFile文件
vi Dockerfile
具体内容如下:
FROM openjdk:8-jdk-alpine
VOLUME /tmp
ADD learn-docker-storage-1.0-SNAPSHOT.jar app.jar
EXPOSE 8003
ENTRYPOINT ["java", "-Djava.security.egd=file:/dev/./urandom", "-jar", "/app.jar"]
命令解释:
- FORM:定制的镜像都是基于 FROM 的镜像,这里的 openjdk 就是定制需要的基础镜像,后续操作都是基于openjdk
- VOLUME:挂载一个数据卷,这里因为没有名称,所以是一个默认的数据卷(后面详细解释)
- ADD:添加一层镜像到当前镜像,这里就是添加SpringBootTest镜像到当前层,并改名app.jar
- EXPOSE:暴漏端口,因为我们的自己的端口是8003,所以我们暴漏8003
- ENTRYPOINT:设定容器启动时第一个运行的命令及其参数,这里就是容器以启动就执行 java -jar /app.jar
写好DockerFile后就需要用docker build
命令来构建我们的镜像了,这样就可以将我们的SpringBoot项目打包成一个镜像了
构建一个镜像需要使用以下命令
docker bulid -t 仓库名/镜像名:tag .
-
-t 镜像的名字及标签,一般命名规则是 仓库名/镜像名:tag,
- 仓库名:一般是私服或者dockerhub等地址,如果忽略默认就是dockerhub的地址
docker.io.library/
- 镜像名称:就是我们的自己的服务名称,可以随意命名
- tag:就是我们的版本号
- 仓库名:一般是私服或者dockerhub等地址,如果忽略默认就是dockerhub的地址
-
. 这个
.
表示当前目录,这实际上是在指定上下文的目录是当前目录,docker build
命令会将该目录下的内容打包交给 Docker 引擎以帮助构建镜像。
docker build -t learn-docker-storage:0.0.1 .
构建完成如果出现Successfully
说明已经构建成功了
可以查看我们构建的镜像
启动镜像:
# 运行容器
docker run -d -p 8003:8003 learn-docker-storage:0.0.1
查看启动日志
docker logs -f container_id
至此,我们的打包镜像部署服务就已经大功告成了
五、日志挂载优化
存储卷优化
什么是存储卷
“卷”是容器上的一个或多个“目录”,此类目录可绕过联合文件系统,与宿主机上的某个目录“绑定
(关联)”;
存储卷的使用
查看存储卷
docker volume ls
docker inspect 2041965c3e87|grep Mounts -A20
# 进入文件挂载目录
cd /var/lib/docker/volumes/d35de1b7e4631908b05635db4c1f114ab3aafbdf25a9843c068696e6 6a779c75/_data
# 输出日志
tail -f learn-docker-storage.log
当删除了容器,发现存储卷所挂载的目录数据依然还存在
bind挂载共享存储
什么是bind
Bind mounts模式和Volumes非常相似,不同点在于Bind mounts模式是将宿主机上的任意文件或文件夹挂载到容器,而Volumes本质上是将Docker服务管理的一块区域(默认是/var/lib/docker/volumes下的文件夹)挂载到容器。
共享存储
经过上面的测试,我们发现每一个容器都是单独用一个存储卷,用于临时文件没有问题的,但是如果要让容器都用同一个存储路径怎么办呢,这个时候就用到了 bind挂载了,可以使用
-v
进行挂载挂载刚才的存储卷。
# 级联创建文件夹
mkdir -p /tmp/data/logs
# 运行容器,指定挂载路径
docker run -d -v /tmp/data/logs:/logs \
-p 8003:8003 --name learn-docker-storage \
learn-docker-storage:0.0.2
用-v来指定需要挂载的目录,这也是我们用的最多的一种方式
使用docker inspect
命令来检查容器详情
docker inspect learn-docker-storage|grep Mounts -A20
volume和bind的区别
对于多个容器需要共享访问同一数据目录,或者需要持久化容器内数据(如数据库)时,我们都是采用挂载目录形式(bind mounts),将宿主机的某一目录挂载到容器内的指定目录,这种方式能解决问题,但这种方式也一直有一些缺点
- 容器在不同的服务器部署需要根据实际磁盘挂载目录修改路径
- 不同操作系统的文件和目录权限会搞得你昏头转向,火冒三丈 ?
bind mount和volume其实都是利用宿主机的文件系统,不同之处在于volume是docker自身管理的目录中的子目录,所以不存在权限引发的挂载的问题,并且目录路径是docker自身管理的,所以也不需要在不同的服务器上指定不同的路径,你不需要关心路径。
六、网络优化
我们先用一张图来说明为什么要进行网络优化
所以Docker的容器是否可以直接通过Docker的网卡进行直接通信呢?
答案是当然可以,这就叫做网络优化。
Docker网络原理
Docker使用Linux桥接,在宿主机虚拟一个Docker容器网桥(docker0),Docker启动一个容器时会根据Docker网桥的网段分配给容器一个IP地址,称为Container-IP,同时Docker网桥是每个容器的默认网关,因为在同一宿主机内的容器都接入同一个网桥,这样容器之间就能够通过容器的Container-IP直接通信。
Docker网桥是宿主机虚拟出来的,并不是真实存在的网络设备,外部网络是无法寻址到的,这也意味着外部网络无法通过直接Container-IP访问到容器,如果容器希望外部访问能够访问到,可以通过映射容器端口到宿主主机(端口映射),即docker run创建容器时候通过 -p 或 -P 参数来启用,访问容器的时候就通过[宿主机IP]:[容器端口]访问容器。
Docker网络模式
Docker网络模式 | 配置 | 说明 |
---|---|---|
host模式 | –net=host | 容器和宿主机共享Network namespace。 |
container模式 | –net=container:NAME_or_ID | 容器和另外一个容器共享Network namespace。 kubernetes中的pod就是多个容器共享一个Network namespace。 |
none模式 | –net=none | 容器有独立的Network namespace,但并没有对其进行任何网络设置,如分配veth pair 和网桥连接,配置IP等。 |
overlay模式 | -- driver overlay | Docker跨主机通信模式,使用分布式计算机架构后需要使用overlay网络模式 |
bridge模式 | –net=bridge | (默认为该模式) |
Docker 的 bridge
模式是 Docker 容器网络的默认模式,它提供了一种简单而强大的方式来为容器分配独立的 IP 地址,并使它们能够在同一主机上相互通信。下面是对 bridge
模式的简要介绍:
桥接网络模式实战
查看网络列表
可以通过
docker network ls
命令查看网络列表
# 查看网络列表
docker network ls
创建一个桥接网络
默认容器启动会自动默认接入
bridge
的桥接网络,为了区分我们的服务也防止各种网络问题,我们创建一个专用网络,可以通过docker network create 网络名称
来创建一个默认的桥接网络
# 创建一个桥接网络
docker network create learn-docker-network
# 查看网络列表
docker network ls
停止并删除原有容器
停止和删除我们的微服务以及mysql服务
# 删除当前运行中的容器
docker rm -f learn-docker-storage nacos mysql
创建MySQL
因为我们的微服务依赖MySQL先启动MySQL并接入网络,因为MySQL不需要通过宿主机访问,所有也不需要映射端口了,--network 是配置接入哪一个网络
docker run -d \
-v /tmp/etc/mysql:/etc/mysql/mysql.conf.d/ \
-v /tmp/data/mysql:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=root \
--name mysql --network=learn-docker-network \
mysql:5.7.38
这里需要注意:为什么重新建了个新的MySQL容器,能拿到以前的数据?因为我们做了存储卷映射,所以数据可以持久化
创建Nacos
我们的nacos是需要暴露端口的,因为我们需要外部能够看到nacos页面,但是我们也需要我们的nacos连接到当前网络
docker run -d \
--network=learn-docker-network \
-p 8848:8848 \
--name nacos --env MODE=standalone \
nacos/nacos-server
查看网络详情
可以通过
docker network inspect 网络名称
可以查看当前的网络的详细信息
docker network inspect learn-docker-network
修改微服务配置
因为需要使用自定义网络访问mysql容器以及nacos容器,需要修改微服务数据库连接地址,
docker 网络访问 可以通过IP或者通过服务名称都是可以的,这里我们通过服务名称访问,因为我们使用了maven打包的方式,我们只需要将pom文件修改就可以
<properties>
<mysql.addr>mysql:3306</mysql.addr>
<nacos.addr>nacos:8848</nacos.addr>
</properties>
重新打包服务
将打包的文件上传服务器后按照上面步骤同上面打包,打包版本为 0.0.3
docker build -t learn-docker-storage:0.0.3 .
创建微服务
下面就按部就班的创建微服务就可以,只是注意需要加入网络,这里这个端口需要映射外网访问
docker run -d \
-v /tmp/data/logs:/logs \
-p 8003:8003 \
--name learn-docker-storage \
--network=learn-docker-network \
learn-docker-storage:0.0.3
测试微服务
到现在微服务已经启动,我们尝试访问以下
curl http://192.168.64.153:8003/storage/employe/findByID/10001 | python -m json.tool
访问测试数据没有问题,到现在我们服务已经搭建完成,并且使用网络进行了优化