Docker基础
命令介绍
其中,比较常见的命令有:
命令 | 说明 | 文档地址 |
---|---|---|
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 | 删除指定容器 | docs.docker.com |
docker ps | 查看容器 | docker ps |
docker logs | 查看容器运行日志 | docker logs |
docker exec | 进入容器ps | docker exec |
docker save | 保存镜像到本地压缩文件 | docker save |
docker load | 加载本地压缩文件到镜像 | docker load |
docker inspect | 查看容器详细信息 | docker inspect |
用一副图来表示这些命令的关系:
默认情况下,每次重启虚拟机我们都需要手动启动Docker和Docker中的容器。通过命令可以实现开机自启:
# Docker开机自启
systemctl enable docker# Docker容器开机自启
docker update --restart=always [容器名/容器id]
命令演示
以Nginx为例演示上述命令。
第1步,去DockerHub查看nginx镜像仓库及相关信息
第2步,拉取Nginx镜像
docker pull nginx
第3步,查看镜像
docker images
第4步,保存镜像到本地
docker save -o nginx.tar nginx:latest
ll
第5步,删除镜像
docker rmi nginx:latest
查看镜像
docker images
第6步,读取本地镜像
docker load -i nginx.tar
再次查看
docker images
第7步,创建并运行容器
docker run -d --name nginx -p 80:80 nginx
第8步,查看容器状态
docker ps
对输出的内容格式化
docker ps --format "table {{.ID}}\t{{.Image}}\t{{.Ports}}\t{{.Status}}\t{{.Names}}"
第9步,停止容器
docker stop nginx
再次查看容器
docker ps --format "table {{.ID}}\t{{.Image}}\t{{.Ports}}\t{{.Status}}\t{{.Names}}"
发现没有查看到nginx容器,是因为ps
查看的是运行状态的容器。如果需要查看所有的容器,需要在最后面加上参数`
第10步,启动容器
docker start nginx
再次使用ps
查看状态
第11步,查看日志
后面加上容器名,加上-f
持续输出,Ctrl+C
停止日志。
docker logs -f nginx
第12步,进入容器内部
exec
执行,-it
是可交互终端,容器是一个隔离环境模拟了一个计算机。进去之后采用命令行交互,命令行交互需要有个终端,-it
是添加一个可输入的终端。使用bash
命令进行交互。
docker exec -it nginx bash
exit
退出容器
第13步,通过容器内部命令,直接访问mysql。
先进入mysql容器
docker exec -it mysql bash
使用mysql客户端命令,访问mysql
mysql -u root -p
show databases;
exit
退出
第14步,删除容器
docker rm mysql
命令别名
给常用Docker命令起别名,方便我们访问:
找到root
目录下的bashrc
文件
vi ~/.bashrc
添加dps和dis,右边是其对应的原始命令。
# User specific aliases and functionsalias 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 ~/.bashrc
数据卷
连接nginx容器,通过bash命令进入控制台:
docker exec -it nginx bash
进入到静态资源目录:
cd /usr/share/nginx/html
对查看到的html文件进行修改:
docker容器确实提供了运行需要的环境,但是里面只包含运行必备的函数,nginx运行不限于要ll
或者vi
命令。因此在容器内修改和复制文件比较麻烦。这就需要使用数据卷解决。
容器是隔离环境,容器内程序的文件、配置、运行时产生的容器都在容器内部,我们要读写容器内的文件非常不方便。可以思考几个问题:
- 如果要升级MySQL版本,需要销毁旧容器,那么数据岂不是跟着被销毁了?
- MySQL、Nginx容器运行后,如果我要修改其中的某些配置该怎么办?
- 我想要让Nginx代理我的静态资源怎么办?
因此,容器提供程序的运行环境,但是程序运行产生的数据、程序运行依赖的配置都应该与容器解耦。
什么是数据卷
数据卷(volume)是一个虚拟目录,逻辑上存在,是容器内目录与宿主机目录之间映射的桥梁。
以Nginx为例,我们知道Nginx中有两个关键的目录:
html
:放置一些静态资源conf
:放置配置文件
如果想对nginx的配置和静态资源都做处理,就需要创建两个数据卷conf
和html
。这两个数据卷是用docker命令创建的,docker会帮助对这两个数据卷创建对应的真实的目录,在宿主机中创建,也就是容器所在的机器。在虚拟机的文件系统里的准备对应的目录,只要是Linux系统就会固定在/var/lib/docker/volumes/
目录下创建数据卷对应的目录。将来html卷
就映射到html
下的/_data
,conf卷
就映射到conf
下的/_data
。也就是每一个数据卷都与宿主机上的一个目录一一对应,所以数据卷是逻辑的,但是对应宿主机上的文件是真实的。既然它是容器目录与宿主机目录之间映射的桥梁,所以需要让容器的目录与数据卷做挂载。
现在容器的/conf目录
指向了conf卷
,conf卷
又指向了宿主机/conf下的/data
。
数据卷是两者之间关联的桥梁,一旦关联之后docker就会实现宿主机目录与容器内目录之间的双向绑定。一旦这个绑定产生了,只需要在宿主机目录的/_data
下新增一个文件,就会自动跑到容器内的对应的目录下,同样在容器内目录下做修改,在对应的宿主机的目录里这个文件也会进行修改。现在要去修改nginx的配置文件或者部署静态资源就不需要进入到容器内,只需要绑定,然后更改宿主机即可。
数据卷命令
docker提供一些命令来操作数据卷:
命令 | 说明 | 文档地址 |
---|---|---|
docker volume create | 创建数据卷 | docker volume create |
docker volume ls | 查看所有数据卷 | docs.docker.com |
docker volume rm | 删除指定数据卷 | docs.docker.com |
docker volume inspect | 查看某个数据卷的详情 | docs.docker.com |
docker volume prune | 删除未使用的卷 | docker volume prune |
这些命令不需要记,只需要使用docker volume --help
即可查看所有命令的帮助信息。
案例一:数据卷挂载
为了方便修改,需要对nginx下的html目录
做挂载。
注意:容器与数据卷的挂载要在创建容器时配置,对于创建好的容器,是不能设置数据卷的。而且创建容器的过程中,数据卷会自动创建。
演示一下nginx的html目录挂载:
第0步,删除原有的nginx容器
docker rm -f nginx
第1步,首先创建容器并指定数据卷,通过 -v 数据卷:容器内目录完成数据卷挂载
数据卷的名字可以随便起,但是不要冲突。在创建容器时,如果挂载了数据卷且数据卷不存在,会自动创建数据卷,不需要自己执行docker volume create
。
docker run -d --name nginx -p 80:80 -v html:/usr/share/nginx/html nginx
通过docker ps
查看容器创建成功。
第2步,查看数据卷
docker volume ls
第3步,查看数据卷详情
html卷已经有了,可以通过inspect
命令查看挂载到宿主机的具体位置。
docker volume inspect html
第4步,查看/var/lib/docker/volumes/html/_data目录
ll /var/lib/docker/volumes/html/_data
可以看到nginx的html内容
index.html对应的就是nginx的欢迎文档
第5步,进入该目录,并随意修改index.html内容
cd /var/lib/docker/volumes/html/_data
vi index.html
第6步,打开页面,查看效果
第7步,将本地图片上传到宿主机的静态资源目录下
第7步,进入容器内部,查看/usr/share/nginx/html目录内的文件是否变化
docker exec -it nginx bash
cd /usr/share/nginx/html
这说明在宿主机内做的操作,容器内也就出现了。
案例二:本地挂载
之前学习了数据卷的知识,并且实现了容器目录与数据卷的挂载。在实际的应用中容器的挂载方式不止一种,通过案例学习其他的与容器挂载的方式。
在之前创建MySQL容器的时候,没有进行数据卷的挂载,因为当时没学-v
参数。
之前已经在nginx中挂载了数据卷,可以通过命令查看。
docker inspect nginx
演示一下MySQL的匿名数据卷:
首先查看容器是否有挂载数据卷,可以通过inspect
命令查看容器详情。
# 1.查看MySQL容器详细信息
docker inspect mysql
# 关注其中.Config.Volumes部分和.Mounts部分,Mounts就是挂载。
我们在创建Mysql容器的时候并没有指定数据卷,但是它本身就有一个数据卷,像这种不是我们创建的卷,是由容器运行自动创建的卷就是匿名卷
。匿名卷的名字是自动生成的,/var/lib/mysql
是数据存储的目录,因为mysql是一个数据库将来增删改查产生的数据就保存在这个目录下。
直接进入这个数据卷,这个数据卷中有非常多的文件,binlog
文件是mysql做主从同步的。其中mysql文件中存放的是数据库中的表,sys中存放的是系统中的表。这里就是mysql的data目录,也就是数据存储的目录。
为什么mysql要将数据存储目录挂载到宿主机?因为mysql在运行的过程中不断产生数据,如果不做挂载的话这些数据都会保存在容器内的文件系统里。将来这个容器的体积将会越来越大,对容器做迁移也就很不方便,出于数据解耦的考虑就将这个存储目录挂载到宿主机中。只不过这种做法用的是匿名卷,匿名卷的名字是随机生成的,超级长。如果相对mysql的版本做升级,需要对旧的mysql容器删掉,创建新的容器。
删除旧的mysql容器:
docker rm -f mysql
删除之后数据卷还是存在,数据也就还存在,但是重新创建mysql新版本的容器的时候,生成卷的名字也就改变了。原来的数据在旧的数据卷里面,也就相当于丢失了。
所以不太建议使用匿名卷,最好自己实现挂载,也不需要挂载到volume中,因为会自动存放在/var/lib/docker/volumes
目录中。可以挂载到任意目录下,建议在root
目录下,将来操作的时候目录在哪会一目了然。
实现宿主机目录与mysql目录的挂载:
之前学的是数据卷的挂载,这种目录直接挂载也是通过-v参数来实现,不过之前是-v 数据卷名:容器内的目录
,现在只需要使用-v 本地目录:容器内目录
即可。注意直接以目录名开头会认为这是一个数据卷,会在/var/lib/docker/volumes
中查找。但是以./
或者/
开头,都以为是一个目录,就可以去本地磁盘找。
这个需求不仅要求挂载mysql的数据目录,还需要挂载mysql的配置文件和初始化脚本。数据目录在/var/lib/mysql
中,但是配置文件和初始化脚本就需要查找官方镜像文档,找到其位置。mysql的配置文件是放在/etc/mysql/conf.d
中,只需要将自己的目录与这个目录挂载即可,将来把mysql的配置文件放到自己的目录下,就会自动出现在容器内。初始化脚本在第一次启动时生效,它会去执行一些sql的脚本,在容器内的/docker-entrypoint-initdb.d
目录下。
在root目录下创建本地目录:
mkdir mysql
cd mysql/
mkdir data
mkdir conf
mkdir init
将配置文件和脚本放到创建好的本地目录里面。
通过网盘分享的文件:docker
链接: https://pan.baidu.com/s/15MYeRXnsGyH6zVjj68wzcg?pwd=be7i 提取码: be7i
–来自百度网盘超级会员v5的分享
检查mysql容器是否删除掉:
dps -a
将宿主机的本地目录挂载到容器内:
docker run -d \
--name mysql \
-p 3306:3306 \
-e TZ=Asia/Shanghai \
-e MYSQL_ROOT_PASSWORD=123 \
-v /root/mysql/data:/var/lib/mysql \
-v /root/mysql/init:/docker-entrypoint-initdb.d \
-v /root/mysql/conf:/etc/mysql/conf.d \
mysql
查看data目录下内容:
说明数据目录成功挂载到宿主机中,同时还存在一个新的数据库hmall。里面很多表,这证明初始化脚本也成功执行了。说明挂载完全成功。
总结:
主要是容器的挂载还有第二种方式,就是基于本地目录直接挂载到容器内的目录。语法跟数据卷挂载类似,区别在于前面的不是数据卷而是目录。同时目录的名字要以./或者/开头,否则就会识别成数据卷。同时也不建议使用mysql的匿名卷,这种匿名卷挂载的位置太深了,而且不方便迁移,推荐挂载自己设定的目录,这样哪怕删掉mysql容器,将来再次运行mysql容器的时候所有的mysql数据还在,这就实现了数据的持久保存。
自定义镜像
前面使用的nginx和mysql的镜像,这两个镜像都是由官方制作上传的,我们下载下来就可以使用。将来自己开发完成Java应用去部署,也需要使用docker部署,也需要制作成镜像才可以,这就需要自定义镜像。
镜像结构
镜像就是一个文件包,里面包含了这个镜像应用程序本身
还有这个应用程序运行所需要的环境
和系统函数配置
等。构建镜像就是把这些文件找到,然后打包即可。
部署Java应用:
在企业中服务器一般都是Linux操作系统,首先得有一个Linux操作环境,接下来就需要在这个环境中安装好JRE
(JVM
运行环境)。紧接着就是将自己的jar包传上去,然后启动就部署成功了。
如果想要别人拿到自己的Java镜像直接可以运行,不需要上面的部署过程,只准备JRE
和Jar
包是不行的。Java应用之所以可以跨系统运行确实因为JRE
或者JVM
,JVM
最终是会跟操作系统交互的,不同的操作系统使用的JVM
也不一样。自己做镜像的时候也不知道将来部署的服务器用的是什么样的系统。所以不仅仅要准备JRE
,还需要将JRE
所依赖的系统也就是系统中的函数库也加进去,这样镜像就不依赖所部署的操作系统了。
比如准备的JRE
是基于Ubuntu系统
的,这就需要将JRE
所依赖的Ubuntu系统的函数库
拿过来。如果不知道Ubuntu版的JRE
需要什么样的函数库,可以在制作Java镜像的时候,将整个Ubuntu的运行环境全拿过来。再将对应的JRE
整合进去,配置好环境变量。再将Jar包整进去,编写一个运行的脚本。最后打成一个包,拿过去后直接执行脚本即可。
每一步制作镜像的过程都会产生一些文件,docker再去制作镜像的时候,不是将每一步产生的文件合并在一起打成一个包作为镜像。而是会把每一步操作产生的文件分别打成压缩包作为镜像的一部分,最终合在一起才是一个完整的镜像。
上述步骤中的每一次操作其实都是在生产一些文件(系统运行环境、函数库、配置最终都是磁盘文件),所以镜像就是一堆文件的集合。但需要注意的是,镜像文件不是随意堆放的,而是按照操作的步骤分层叠加而成,每一层形成的文件都会单独打包并标记一个唯一id,称为Layer(层)。之前在下载mysql和nginx的时候,下载产生的日志不是下载一个文件,它有好几行日志,其实也就是在分成下载。下载完成之后,需要将每层文件都解压,最后合并在一起才是真正完整的镜像。
制作镜像的时候都有一个类似的结构,首先有一个基础镜像,还有一个统一的入口,紧接着就是制作镜像过程中,操作产生的文件形成的层。只需要将每一层分别压缩打包,镜像就制作完成了。
docker为什么将这些镜像分层打包?首先在制作镜像的时候,我们可以将Ubuntu
中JRE
依赖的函数库单独拿出来,打成一个包,作为镜像直接上传到公共镜像仓库。接下来再去制作镜像的时候这样,就不需要再从0开始了,直接从原来制作好的基础Ubuntu镜像开始,这样就不需要重复挑函数库的操作了。这就是分层的好处,可以共享某些基础的层。第二个就是有这些基础镜像,以后很多镜像都可以共享前面的几层,这样下载的时候也就方便了。如果a镜像和b镜像的前两层是一样的,当去下载b镜像的时候,docker会先检查下前两层在本地有没有,如果一就可以不用下载,直接下载后面的即可。这样下载速度也会提升,本地存储文件的体积也会减少。
目前已经有nginx和mysql镜像,现在拉一个redis镜像:
docker pull redis
在下载redis的时候会有Already exists
,这说明redis镜像分n层,其中的第一层跟前面下载的某一层一样,所以就省略了一次下载。
上面的操作有些复杂,首先需要找镜象,还需要将每一层压缩打包,这些操作如果个人去做比较困难。
Dockerfile
由于制作镜像的过程中,需要逐层处理和打包,比较复杂,所以Docker就提供了自动打包镜像的功能。我们根本不需要亲自动手做镜像,只需要描述清楚镜像的结构,入口是什么?基础是什么?中间有哪些层?docker就会自动帮助完成整个镜像的构建,如何告诉docker镜像结构?就需要通过Dockerfile。Dockerfile就是一个普通的文本文件,这个文件里面可以用一个个指令描述整个镜像的结构。docker就可以按照这些指令构建镜像。我们只需要将打包的过程,每一层要做的事情用固定的语法写下来,交给Docker去执行即可。
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 |
EXPOSE | 指定容器运行时监听的端口,是给镜像使用者看的。 | EXPOSE 8080 |
ENTRYPOINT | 镜像中应用的启动命令,容器运行时调用 | ENTRYPOINT java -jar xx.jar |
EXPOSE 8080并不是将来使用镜像的人可以直接访问8080,还是需要通过-p做端口映射。这里只是描述,并不影响使用者需要做端口映射。
例如,要基于Ubuntu镜像来构建一个Java应用,其Dockerfile内容如下:
# 指定基础镜像
FROM ubuntu:16.04
# 配置环境变量,JDK的安装目录、容器内时区
ENV JAVA_DIR=/usr/local
# 拷贝jdk和java项目的包,拷过来的是压缩包,还需要解压安装。
COPY ./jdk8.tar.gz $JAVA_DIR/
COPY ./docker-demo.jar /tmp/app.jar
# 安装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"]
同学们思考一下:以后我们会有很多很多java项目需要打包为镜像,他们都需要Linux系统环境、JDK环境这两层,只有上面的3层不同(因为jar包不同)。如果每次制作java镜像都重复制作前两层镜像,是不是很麻烦。
所以,就有人提供了基础的系统加JDK环境,我们在此基础上制作java镜像,就可以省去JDK的配置了
# 基础镜像
FROM openjdk:11.0-jre-buster
# 拷贝jar包
COPY docker-demo.jar /app.jar
# 入口
ENTRYPOINT ["java", "-jar", "/app.jar"]
只需要将jar包备份一下,每次构建新镜像的时候就只需要拷贝过去,jar包名字更改下即可,是不是简单多了。
构建镜像
利用Dockerfile构建java镜像非常简单,只需简单的几行命令就能搞定,只不过Dockerfile写完之后,怎么让docker帮我们去构建?这里就需要用一些命令,当Dockerfile写好了之后,可以用下面的命令来构建镜像。
docker build -t myImage:1.0 .
-t
:是给镜像起名,格式依然是repository:tag
的格式,不指定tag
时,默认为latest
。
.
: 是指定Dockerfile所在的目录,如果就是在当前目录,则指定为".
",也就是docker build在指定运行的时候,是跟Dockerfile在同一个目录运行的。Dockerfile所在的位置要跟将来的文件是有关联的,因为前面有拷贝的动作,拷贝写的就是相对路径,就意味着jar包得跟Dockerfile在一块。
在下面的资料中有Dockerfile还有一个docker-demo.jar包。
通过网盘分享的文件:demo
链接: https://pan.baidu.com/s/11Ega-hiyJoB0cf3bevZeGQ?pwd=v4ws 提取码: v4ws
–来自百度网盘超级会员v5的分享
打开Dockerfile文件:
# 基础镜像
FROM openjdk:11.0-jre-buster
# 设定时区,如果不做就默认是中时区,而不是东八区,这样跟国内的时间有差距。
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
# 拷贝jar包,将同目录下的docker-demo.jar拷贝到根目录的app.jar
COPY docker-demo.jar /app.jar
# 入口就是app.jar,就找到它去执行了。
ENTRYPOINT ["java", "-jar", "/app.jar"]
首先,我们将课前资料提供的docker-demo.jar
包以及Dockerfile
拷贝到虚拟机的/root/demo
目录:
在下面资料中有jdk.tar的镜像,直接将其传到根目录下,省的下载花费很多时间。
通过网盘分享的文件:images
链接: https://pan.baidu.com/s/1vqr2R8AcsgzXfugINWKJEw?pwd=5nii 提取码: 5nii
–来自百度网盘超级会员v5的分享
对于准备好的tar包,如何将其变成自己的镜像,可以通过下面的命令来加载镜像。
docker load -i jdk.tar
查看镜像,发下openjdk已经加载成功了。
接下来需要进入到demo目录里面运行指令。
# 必须进入到Dockerfile所在的目录,这样写路径的时候省事。.就是指定Dockerfile所在的目录,如果后面没有指定文件名,那么Dockerfile名就得是Dockerfile才可以。
cd ./demo
docker build -t docker-demo .
第一步FROM基础镜像,第二步RUN配置时区,第三步COPY将jar包拷进去。入口就是一个脚本,将来直接运行就可以按照脚本运行。
通过dis查看,docker-demo已经变成一个镜像了,最新版本。
运行容器
java应用的端口号为8080:
docker run -d --name dd -p 8080:8080 docker-demo
通过docker ps
查看运行状态:
发现docker-demo属于运行状态
通过docker logs查看日志后面跟上容器名dd。
docker logs -f dd
访问8080端口查看详情,提前定义好的一个路径/hello/count
这就说明自己可以给java应用构建镜像,并且部署了。
总结
镜像的结构是怎样的?
镜像的结构就是就是文件包,镜像中包含了应用程序所需的运行环境、函数库、配置以及应用本身等文件。但是这些文件不是在一个包里,是在构建镜像中逐层打包的,一般情况下会有一个入口和基础镜像。
Dockerfile是做什么的?
Dockerfile是用来帮助构建镜像的,镜像结构要分成打包比较麻烦,所以不用自己去构建。而是去利用Dockerfile中的指令去描述镜像的结构和构建过程,这样docker才能基于描述完成镜像的构建。
镜像构建的命令是什么?
镜像构建最后是基于Dockerfile,但是需要docker bulid -t 镜像名 Dockerfile目录
。镜像名一般是名字:版本号,不指定版本号就是默认最新。一般情况下执行命令的目录就是Dockerfile当前所在目录,所以写个.
即可。
网络
之前我们创建了一个Java项目的容器,而Java项目往往需要访问其它各种中间件,例如MySQL、Redis等。现在,我们的容器是相互隔离的空间,它们之间能否互相访问呢?我们来测试一下
首先,我们查看下MySQL容器的详细信息,重点关注其中的网络IP地址:
# 1.用基本命令,寻找Networks.bridge.IPAddress属性
docker inspect mysql
查看服务器dd容器的详细信息,查看其网络IP地址:
docker inspect dd
发现这两个容器的IP地址是相似的,都是172.17.0
开始的,只有最后一位不同。说明这两个容器是在一个网段当中的,在一个网段当中意味着它们之间是可以相互访问的。容器都是相互隔离的空间,为什么还有相同的网段可以相互访问,这是因为它们都有相同的网关。
在安装Docker的时候,docker就会在虚拟机里面创建一张虚拟的网卡。这个网卡的名字默认是docker0
,并且还会给这个网卡创建一个虚拟的网桥。所有与这个网桥连上的容器都会分配一个ip,ip的范围就是网桥的范围。
相当于各个容器虽然是独立空间,但是它们通过这个网桥建立了连接,因此它们之间可以相互访问。
连接服务器这个dd容器,进入bash控制台,通过ping命令访问mysql容器。
docker exec -it dd bashping 172.17.0.3
发现可以互联,没有问题。
但是,这个ip地址是docker网桥分配的,假如这个服务重新启动,或者在重启的过程中其他容器也启动了,那么ip地址就可能会被其他人占用。容器的网络IP其实是一个虚拟的IP,其值并不固定与某一个容器绑定,如果我们在开发时写死某个IP,而在部署时很可能MySQL容器的IP会发生变化,连接会失败。
所以,我们必须借助于docker的自定义网络功能来解决这个问题,
查看当前虚拟机的网卡:
ip addr
默认情况下有两张网卡,lo和ens33网卡,虚拟机的真实ip地址是192.168.202.128/24
。docker0网桥是docker一安装就有的,如果创建自定义网络就会形成一个新的网桥,而新的网桥是自定义的,其网段与docker0的是不一样的。如果自己的容器加入到自定义的网桥,那么加入到这个网桥的容器就能互联了。
加入自定义网络的容器可以通过新的网桥互联,也可以通过容器名互相访问,这样就无需知道对方的ip地址,只需要知道容器名即可。
官方文档:
https://docs.docker.com/engine/reference/commandline/network/
常见命令有:
命令 | 说明 | 文档地址 |
---|---|---|
docker network create | 创建一个网络 | docker network create |
docker network ls | 查看所有网络 | docs.docker.com |
docker network rm | 删除指定网络 | docs.docker.com |
docker network prune | 清除未使用的网络 | docs.docker.com |
docker network connect | 使指定容器连接加入某网络 | docs.docker.com |
docker network disconnect | 使指定容器连接离开某网络 | docker network disconnect |
docker network inspect | 查看网络详细信息 | docker network inspect |
第一步,首先查看所有的网络
docker network ls
第二步,创建自己的网桥,网桥名叫Charlie
docker network create Charlie
第三步,查看网桥信息
ip addr
发现除了docker0以外,出现了新的网卡172.18.0.1/16
第四步,让mysql容器加入到创建的网桥
docker network connect Charlie mysql
第五步,查看mysql容器
docker inspect mysql
发现mysql容器的network有两个,一个是默认网桥17网段,一个是新的网桥18网段。因为1是网关,所以ip地址从2开始递增。
第六步,创建容器并连接
上面是容器已经存在然后去连接创建的网桥,还可以让容器在创建的时候就连接。
# 删除容器
docker rm -f dd
# 创建启动容器,并连接网桥
docker run -d --name dd -p 8080:8080 --network Charlie docker-demo
会在创建容器的那一刻,直接加入网络,不需要后面添加。
第七步,查看dd容器
docker inspect dd
发现网络里只有Charlie,没有默认网桥docker0,就是因为在创建时指定就不会再加入默认,这样dd容器就跟默认网桥里的容器连不上了。
第八步,进入dd容器,连接nginx容器。
docker exec -it dd bash
ping mysql
ping nginx
通过自定义网络,将来就能非常方便的做容器间的相互访问,可以使用容器名。
项目部署
好了,我们已经熟悉了Docker的基本用法,接下来可以尝试部署项目了。
在网盘资料中已经提供了一个黑马商城项目:
通过网盘分享的文件:黑马商城
链接: https://pan.baidu.com/s/1GbTe43W79KboRn_l8syOeA?pwd=3yhq 提取码: 3yhq
–来自百度网盘超级会员v5的分享
项目说明:
- hmall:商城的后端代码
- hmall-portal:商城用户端的前端代码
- hmall-admin:商城管理端的前端代码
部署的容器及端口说明:
项目 | 容器名 | 端口 | 备注 |
---|---|---|---|
hmall | hmall | 8080 | 黑马商城后端API入口 |
hmall-portal | nginx | 18080 | 黑马商城用户端入口 |
hmall-admin | 18081 | 黑马商城管理端入口 | |
mysql | mysql | 3306 | 数据库 |
在正式部署前,我们先删除之前的nginx、dd两个容器:
docker rm -f nginx dd
mysql容器中已经准备好了商城的数据,所以就不再删除了。
部署Java项目
hmall
项目是一个maven聚合项目,使用IDEA打开hmall
项目,查看项目结构如图:
这个项目需要访问之前部署好的mysql数据库,数据库里面有很多的表,实现了商城的一些基础功能,商品表中有数万条的数据。因此,项目代码需要访问数据库,但是这个数据库是部署在虚拟机里docker内部。在真实项目部署的时候,数据库这样的容器不会对外做端口映射的,无法在外面访问。将来只能通过容器名去访问,将来JDBC的连接必须得按照容器名去配置。
在项目配置里,查看JDBC的连接参数:
可以发现连接的地址没有写死,$用于读取配置中的其他配置变量,这个地方的ip地址不是写死的,而是一个变量。因为要将其部署到虚拟机内,相当于一个公司内的整体开发环境,但是平常开发是在本地开发。本地开发和部署到开发环境之后的地址是不一样的,所以不能写死是动态的。变量地址取决于两个配置文件dev
和local
,dev放到开发环境之后的地址,local是本地开发时的配置。
查看application-dev.yaml
hm:db:host: mysqlpw: 123
配置的不是ip地址,而是容器名字。将来放在虚拟机里,用docker部署以后,用容器名与mysql容器互连,就没有任何问题。
查看application-local.yaml
hm:db:host: 192.168.150.101 # 修改为你自己的虚拟机IP地址pw: 123 # 修改为docker中的MySQL密码
这是本地开发时,需要写成一个真实的ip地址。
在项目中Dockerfile也已准备,直接将jar包准备好就能构建镜像。
# 基础镜像
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 hm-service.jar /app.jar
# 入口
ENTRYPOINT ["java", "-jar", "/app.jar"]
我们将项目打包:
结果:
将hm-service
目录下的Dockerfile
和hm-service/target
目录下的hm-service.jar
一起上传到虚拟机的root
目录:
部署项目:
构建项目:
# 1.构建项目镜像,不指定tag,则默认为latest
docker build -t hmall .
查看镜像:
# 2.查看镜像
docker images
查看正在运行的容器详情:
docker ps
发现已经有一个dd容器为8080端口,而hamll配置也是8080,这就会造成冲突。
删除dd容器:
docker rm -f dd
运行创建的hmall镜像:
要与mysql连接在一个网络里面,所以要在network中添加网络名Charlie。
docker run -d --name hm -p 8080:8080 --network Charlie hmall
查看刚创建的容器
docker ps
查看容器的日志:
docker logs -f hm
访问ip加controller中的接口:
http://你的虚拟机地址:8080/接口名
http://192.168.202.128:8080/hi
http://192.168.202.128:8080/search/list?pageNo=1&pageSize=5
这样证明服务已经部署成功,回到控制台也能看到输出的SQL语句。
总结:
部署Java项目需要做几件事?
-
将项目打包,打包完成之后得到jar包
-
将jar包与
Dockerfile
一起放到虚拟机里,利用命令docker build
构建镜像。 -
镜像构建完成之后,使用
docekr run
命令部署应用,可以添加network参数,与数据库放到同一网络段里面。
部署前端
hmall-portal
和hmall-admin
是前端代码,需要基于nginx部署。在百度网盘资料中已经给了nginx
通过网盘分享的文件:nginx
链接: https://pan.baidu.com/s/1oUZl00IL4QLOBKe5e5J2vQ?pwd=7ehw 提取码: 7ehw
–来自百度网盘超级会员v5的分享
其中:
html
是静态资源目录,我们需要把hmall-portal
以及hmall-admin
都复制进去nginx.conf
是nginx的配置文件,主要是完成对html
下的两个静态资源目录做代理
需要创建一个新的nginx容器,将nginx.conf
、html目录
与容器挂载。之前的nginx容器只挂载了html目录,没有挂载conf文件,所以没有办法对nginx的conf配置,因此需要创建一个新的容器,完成对于配置文件的挂载。
在html里包含了商城的前端页面hmall-portal
以及后台管理页面hmall-admin
,因此nginx需要对这两个目录做代理。
在nginx.conf中配置了对这两个目录的代理
server {# 服务监听了18080端口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;}# 后端接口配置,前端向后端发送请求都发送给到了/api路径,这个路径最终要代理到后端的8080。location /api {rewrite /api/(.*) /$1 break;proxy_pass http://hm:8080;}}
将来访问用户端就访问18080,管理端就访问18081。指向的前端地址/usr/share/nginx/html/
是容器内的html,本地也有html文件,应该将本地的html文件与容器内html做挂载。一旦做了挂载,html下的两个文件就放到容器内了,将来反向代理就能找到这两个静态资源目录。
删除原有的nginx容器:
挂载nginx目录和conf文件:
之前学习了挂载html目录,现在需要挂载conf文件。
首先上传整个nginx目录到root目录下
查看官网可知-v 宿主机文件地址:/etc/nginx/nginx.conf
,挂载命令如下:
docker run -d \
--name nginx \
-p 18080:18080 \
-p 18081:18081 \
-v /root/nginx/html:/usr/share/nginx/html \
-v /root/nginx/nginx.conf:/etc/nginx/nginx.conf \
-- network Charlie \
nginx
访问18081端口号:
http://192.168.202.128:18081
输出日志:
docker logs -f hm
前端点击搜索,后端输出日志信息。
证明前后端和数据库这三个容器之间互联没有任何问题。
DockerCompose
大家可以看到,我们部署一个简单的java项目,其中包含3个容器:
- MySQL
- Nginx
- Java项目
而稍微复杂的项目,其中还会有各种各样的其它中间件,需要部署的东西远不止3个。如果还像之前那样手动的逐一部署,就太麻烦了。
而Docker Compose就可以帮助我们实现多个相互关联的Docker容器的快速部署。它允许用户通过一个单独的 docker-compose.yml
模板文件(YAML 格式)来定义一组相关联的应用容器。这样就可以统一管理相关的容器,可以直接将整个项目启用或停用。
一个docker-compose.yaml文件对应一个完整的项目,一个项目内部有很多部分组成,每一部分旧称之为服务。所以,docker-compose.yaml文件就是描述一个项目内多个服务的信息。
基本语法
docker-compose.yml文件的基本语法可以参考官方文档:
https://docs.docker.com/compose/compose-file/compose-file-v3/
docker-compose文件中可以定义多个相互关联的应用容器,每一个应用容器被称为一个服务(service)。由于service就是在定义某个应用的运行时参数,因此与docker run
参数非常相似。
举例来说,用docker run部署MySQL的命令如下:
docker run -d \--name mysql \-p 3306:3306 \-e TZ=Asia/Shanghai \-e MYSQL_ROOT_PASSWORD=123 \-v ./mysql/data:/var/lib/mysql \-v ./mysql/conf:/etc/mysql/conf.d \-v ./mysql/init:/docker-entrypoint-initdb.d \--network hmallmysql
如果用docker-compose.yml
文件来定义,就是这样:
version: "3.8"services:mysql:image: mysqlcontainer_name: mysqlports:- "3306:3306"environment:TZ: Asia/ShanghaiMYSQL_ROOT_PASSWORD: 123volumes:- "./mysql/conf:/etc/mysql/conf.d"- "./mysql/data:/var/lib/mysql"networks:- new
networks:new:name: hmall
对比如下:
docker run 参数 | docker compose 指令 | 说明 |
---|---|---|
–name | container_name | 容器名称 |
-p | ports | 端口映射 |
-e | environment | 环境变量 |
-v | volumes | 数据卷配置 |
–network | networks | 网络 |
明白了其中的对应关系,相信编写docker-compose
文件应该难不倒大家。
黑马商城部署文件:
version: "3.8"services:mysql:image: mysqlcontainer_name: mysqlports:- "3306:3306"environment:TZ: Asia/ShanghaiMYSQL_ROOT_PASSWORD: 123volumes:- "./mysql/conf:/etc/mysql/conf.d"- "./mysql/data:/var/lib/mysql"- "./mysql/init:/docker-entrypoint-initdb.d"# 声明网络标识networks:- hm-net# 手动部署Java应用的时候,先要将jar包通过docker build构建成镜像,再去docker run。# docker-compose文件里,直接build,会在当前目录自动去找Dockerfile文件,接下来就可以基于Dockerfile描述的信息完成镜像构建# 这里将手动转jar包的动作替换了,镜像就已经存在了,不需要再添加image了。hmall:build: # 当前目录context: .dockerfile: Dockerfilecontainer_name: hmallports:- "8080:8080"networks:- hm-net# 这个项目依赖mysql,会先创建mysql再创建这个项目的容器。depends_on:- mysqlnginx:image: nginxcontainer_name: nginxports:- "18080:18080"- "18081:18081"volumes:- "./nginx/nginx.conf:/etc/nginx/nginx.conf"- "./nginx/html:/usr/share/nginx/html"depends_on:- hmallnetworks:- hm-net
# 之前都是手动创建网络,再将容器添加到对应的网络。在这里只需要告诉这些网络在一个标识hm-net里,然后声明这个网络。
# docker-compose会自动先去帮助创建网络,再去完成网络的连接。
networks:# 标识名hm-net:# 网络名name: Charlie
整个命令都是以相对路径写的,所以docker-compose文件应该与mysql目录、nginx目录、jar包和Dockerfile文件在一起。
注意在反向代理nginx中,后端的地址容器名为hm,在上面文件中项目容器的名字叫hmall,所以需要将hm改为hmall
基础命令
编写好docker-compose.yml文件,就可以部署项目了。常见的命令:
https://docs.docker.com/compose/reference/
基本语法如下:
docker compose [OPTIONS] [COMMAND]
其中,OPTIONS和COMMAND都是可选参数,比较常见的有:
类型 | 参数或指令 | 说明 |
---|---|---|
Options | -f | 指定compose文件的路径和名称,如果在当前目录下就可以不需要指定。 |
-p | 指定project名称,不需要一般默认就是root | |
Commands | up | 创建并启动所有service容器,就是一键启动 |
down | 停止并移除所有容器、网络 | |
ps | 列出所有启动的容器,只查看与这个projrct有关的。 | |
logs | 查看指定容器的日志 | |
stop | 停止某一个容器 | |
start | 启动某一个容器 | |
restart | 重启某一个容器 | |
top | 查看运行的进程 | |
exec | 在指定的运行中容器中执行命令,进入某个容器。 |
删除之前部署的容器:
docker ps
docker rm -f nginx hm mysql
docker ps -a
删除之前部署的网络:
docker network rm -f Charlie
移除相关镜像:
dis
docker rmi hamll docker-demo
dis -a
一键部署:
加上-d,后台运行,否则一直在前台打印各种信息。
docker compose up -d
查看项目下的所有运行的容器:
docker compose ps
打开浏览器,访问:http://192.168.202.128:18080/
登录:账号名:jack 密码:123
一键删除:
docker compose down
查看容器和网络:
docker ps -a
docker network ls
DockerCompose不止可以做项目部署,还可以做集群部署。