🔥 本文由 程序喵正在路上 原创,CSDN首发!
💖 系列专栏:Springcloud微服务
🌠 首发时间:2024年6月22日
🦋 欢迎关注🖱点赞👍收藏🌟留言🐾
目录
- Docker基础之镜像
- 镜像结构
- Dockerfile
- 构建镜像
- Docker基础之网络
- 项目部署
- 部署Java项目
- 部署前端
- DockerCompose
- 基本语法
- 基础命令
Docker基础之镜像
镜像就是包含了应用程序、程序运行的系统函数库、运行配置等文件的文件包。构建镜像的过程其实就是把上述文件打包的过程。
镜像结构
要想自己构建镜像,必须先了解镜像的结构。
之前我们说过,镜像之所以能让我们快速跨操作系统部署应用而忽略其运行环境、配置,就是因为镜像中包含了程序运行需要的系统函数库、环境、配置、依赖。
因此,自定义镜像本质就是依次准备好程序运行的基础环境、依赖、应用本身、运行配置等文件,并且打包而成。
举个例子,如果我们要从 0 部署一个 Java 应用,大概流程是这样的:
- 准备一个 linux 服务(CentOS 或者 Ubuntu 均可)
- 安装并配置 JDK
- 上传 Jar 包
- 运行 jar 包
因此,我们打包镜像也是分成这么几步:
- 准备 Linux 运行环境(java 项目并不需要完整的操作系统,仅仅是基础运行环境即可)
- 安装并配置 JDK
- 拷贝 jar 包
- 配置启动脚本
上述步骤中的每一次操作其实都是在生产一些文件(系统运行环境、函数库、配置最终都是磁盘文件),所以镜像就是一堆文件的集合。
但需要注意的是,镜像文件不是随意堆放的,而是按照操作的步骤分层叠加而成,每一层形成的文件都会单独打包并标记一个唯一 id,称为Layer(层)。这样,如果我们构建时用到的某些层其他人已经制作过,就可以直接拷贝使用这些层,而不用重复制作。
例如,第一步中需要的 Linux 运行环境,通用性就很强,所以 Docker 官方就制作了这样的只包含 Linux 运行环境的镜像。我们在制作 java 镜像时,就无需重复制作,直接使用 Docker 官方提供的 CentOS 或 Ubuntu 镜像作为基础镜像。在此基础上再搭建其它层即可,这样逐层搭建,最终整个 Java 项目的镜像结构如图所示:
Dockerfile
由于制作镜像的过程中,需要逐层处理和打包,比较复杂,所以 Docker 就提供了自动打包镜像的功能。我们只需要将打包的过程,每一层要做的事情用固定的语法写下来,交给 Docker 去执行即可。
而这种记录镜像结构的文件就称为 Dockerfile,其对应的语法可以参考官方文档:https://docs.docker.com/reference/dockerfile/。
其中的语法比较多,比较常用的有:
指令 | 说明 | 示例 |
---|---|---|
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 |
例如,如果要基于 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"]
其实,我们仔细思考后会发现,将一个 Java 项目打包为镜像,都需要 Linux 系统环境、JDK 环境这两层,只有上面的 3 层不同(因为 jar 包不同)。如果每次制作 Java 镜像都重复制作前两层镜像,是有点麻烦的。
因此,就有人提供了基础的系统加 JDK 环境,我们在此基础上制作 Java 镜像,就可以省去一些配置了:
# 基础镜像
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 文件写好以后,我们就可以利用命令来构建镜像了。
在资料中,我们已经准备好了一个 demo 项目及对应的 Dockerfile用于演示如何构建镜像:
在虚拟机中回到根目录,然后我们可以将整个 demo 文件夹直接拖到窗口中,将其上传,里面的 logs 现在没什么用,可以删去:
我们查看一下这个 Dockerfile,可以发现它是在 JDK 的基础镜像上进行构架的,在资料中也准备了这个镜像,我们将其上传到宿主机中,等一下就不用再浪费时间去下载了:
在 images 目录下有一个 jdk.tar,我们将其拖到根目录下即可:
然后,加载一下:
一切准备好后,我们就可以来构建镜像了:
# 进入镜像目录
cd /root/demo
# 开始构建
docker build -t docker-demo:1.0 .
命令说明:
-
docker build
: 就是构建一个 docker 镜像 -
-t docker-demo:1.0
:-t
参数是指定镜像的名称(repository
和tag
) -
.
: 最后的点是指构建时Dockerfile
所在的路径,由于我们进入了 demo 目录,所以指定的是.
代表当前目录,也可以直接指定 Dockerfile 目录:# 直接指定Dockerfile目录 docker build -t docker-demo:1.0 /root/demo
可以看到,构建很快,1.5 秒就完成了。
# 1.创建并运行容器
docker run -d --name dd -p 8080:8080 docker-demo:1.0
# 2.查看容器
dps
# 3.查看容器运行日志
docker logs -f dd
可以看到一个很熟悉的标志:
我们可以来到浏览器访问一下虚拟机的 8080 端口:
192.168.150.128:8080/hello/count
记得换成你虚拟机的 IP,回车后我们可以看到这个页面,并且它会统计我们的访问次数:
我们也可以直接在虚拟机中通过指令来访问:
curl localhost:8080/hello/count
Docker基础之网络
前面我们创建了一个 Java 项目的容器,而 Java 项目往往需要访问其它各种中间件,例如 MySQL、Redis 等。现在,我们的容器之间能否互相访问呢?
默认情况下,所有容器都是以 bridge 方式连接到 Docker 的一个虚拟网桥上:
首先,我们查看下 MySQL 容器的详细信息,重点关注其中的网络 IP 地址,然后进入 dd 容器中 ping 一下:
# 1.用基本命令,寻找Networks.bridge.IPAddress属性
docker inspect mysql
# 也可以使用format过滤结果
docker inspect --format='{{range .NetworkSettings.Networks}}{{println .IPAddress}}{{end}}' mysql
# 得到IP地址如下:
172.17.0.2
# 2. mysql容器没启动的需要启动
docker start mysql
# 3.然后通过命令进入dd容器
docker exec -it dd bash
# 4.在容器内,通过ping命令测试网络
ping 172.17.0.2
发现可以互联,没有问题。
但是,容器的网络 IP 其实是一个虚拟的 IP,其值并不固定与某一个容器绑定,如果我们在开发时写死某个 IP,而在部署时很可能 MySQL 容器的 IP 会发生变化,从而导致连接失败。
所以,我们必须借助于 docker 的网络功能来解决这个问题,参考官方文档:https://docs.docker.com/reference/cli/docker/network/
命令 | 说明 |
---|---|
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
# 结果:
NETWORK ID NAME DRIVER SCOPE
8964d77f268d bridge bridge local
7c04bfc41671 hmall bridge local
99e9b07b2b5c host host local
a4deb7bd2a05 none null local
# 其中,除了hmall以外,其它都是默认的网络
# 3.让dd和mysql都加入该网络,注意,在加入网络时可以通过--alias给容器起别名
# 这样该网络内的其它容器可以用别名互相访问!
# 3.1.mysql容器,指定别名为db,另外每一个容器都有一个别名是容器名
docker network connect hmall mysql --alias db
# 3.2.db容器,也就是我们的java项目
docker network connect hmall dd
# 4.进入dd容器,尝试利用别名访问db
# 4.1.进入容器
docker exec -it dd 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
我们也可以在创建容器时直接加入网络,但是通过这种方式就不会再连接默认网桥:
docker run -d --name dd -p 8080:8080 --network hmall docker-demo
现在,我们无需记住 IP 地址也可以实现容器互联了,通过别名即可,它会自己去找。
项目部署
前面,我们已经熟悉了 Docker 的基本用法,接下来就可以尝试自己来部署一个项目了。
项目的相关代码已经存在资料中,如图:
项目说明:
- hmall:商城的后端代码
- hmall-portal:商城用户端的前端代码
- hmall-admin:商城管理端的前端代码
部署的容器及端口说明:
项目 | 容器名 | 端口 | 备注 |
---|---|---|---|
hmall | hmall | 8080 | 黑马商城后端API入口 |
hmall-portal | nginx | 18080 | 黑马商城用户端入口 |
hmall-admin | nginx | 18081 | 黑马商城管理端入口 |
mysql | mysql | 3306 | 数据库 |
在正式部署前,我们先删除之前的 nginx、dd 两个容器:
docker rm -f nginx dd
mysql 容器中已经准备好了商城的数据,所以就不再删除了。
部署Java项目
hmall 项目是一个 maven 聚合项目,我们使用 IDEA 打开 hmall 项目,查看项目结构如图:
我们要部署的就是其中的 hm-service,其中的配置文件采用了多环境的方式:
其中的 application-dev.yaml
是部署到开发环境的配置,application-local.yaml
是本地运行时的配置。
查看 application.yaml
,你会发现其中的 JDBC 地址并未写死,而是读取变量:
这两个变量在 application-dev.yaml
和 application-local.yaml
中并不相同。在 application-local.yaml
中,我们需要修改 host
为自己虚拟机的 ip 地址。
在 dev 开发环境(也就是 Docker 部署时)采用了 mysql
作为地址,刚好是我们的 mysql 容器名,只要两者在一个网络,就一定能互相访问。
打包完,我们就能在 target 目录下看到 jar 包了:
接下来,我们将 hm-service 目录下的 Dockerfile 和 hm-service/target 目录下的 hm-service.jar 一起上传到虚拟机的 root 目录,直接拖文件即可:
# 1. 启动mysql容器
docker start mysql
# 2. 在当前目录构建项目镜像,不指定tag,则默认为latest
docker build -t hmall .
# 3. 查看镜像
dis
# 4. 创建并运行容器,并通过--network将其加入hmall网络,这样才能通过容器名访问mysql
docker run -d --name hmall --network hmall -p 8080:8080 hmall
# 5. 查看容器运行日志
docker logs -f hmall
通过日志可以看到 Spring 和 Mabatisplus 的标志:
然后,我们可以通过浏览器访问 http://你的虚拟机地址:8080/search/list
这个地址,来测试 Java 项目是否部署成功。
部署前端
hmall-portal 和 hmall-admin 是前端代码,需要基于 nginx 部署。
其中:
- html 是静态资源目录
- nginx.conf 是 nginx 的配置文件,主要是完成对 html 下的两个静态资源目录做代理
我们现在要做的就是把整个 nginx 目录上传到虚拟机的 /root
目录下:
然后创建 nginx 容器并完成两个挂载:
- 把
/root/nginx/html
挂载到/usr/share/nginx/html
- 把
/root/nginx/nginx.conf
挂载到/etc/nginx/nginx.conf
由于需要让 nginx 同时代理 hmall-portal 和 hmall-admin 两套前端资源,因此我们需要暴露两个端口:
- 18080:对应 hmall-portal
- 18081:对应 hmall-admin
如果你在部署之后访问不了前端页面,就可以执行下列指令来开放端口。
# 开放端口18080
sudo firewall-cmd --zone=public --add-port=18080/tcp --permanent
# 开放端口18081
sudo firewall-cmd --zone=public --add-port=18081/tcp --permanent
# 重新加载防火墙规则
sudo firewall-cmd --reload
如果你的虚拟机中已经存在 nginx 容器,需要先将其删除:docker rm -f nginx
。
新建 nginx 容器:
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 hmall \
nginx
测试,通过浏览器访问:http://你的虚拟机ip:18080
。
DockerCompose
大家可以看到,我们部署一个简单的 Java 项目,其中就包含 3 个容器:
- MySQL
- Nginx
- Java项目
而稍微复杂的项目,其中还会有各种各样的其它中间件,需要部署的东西远不止 3 个。如果还像这样手动地逐一部署,就太麻烦了。
而 Docker Compose 就可以帮助我们实现多个相互关联的 Docker 容器的快速部署。它允许用户通过一个单独的 docker-compose.yml
模板文件(YAML 格式)来定义一组相关联的应用容器。
基本语法
docker-compose.yml
文件的基本语法可以参考官方文档:https://docs.docker.com/compose/compose-file/legacy-versions/。
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 hmall
mysql
如果用 docker-compose.yml
文件来定义,就是这样:
version: "3.8" # docker compose的版本
services:
mysql:
image: mysql # 镜像名称
container_name: mysql # 容器名称
ports:
- "3306:3306"
environment: # 环境
TZ: Asia/Shanghai
MYSQL_ROOT_PASSWORD: 123
volumes: # 数据卷
- "./mysql/conf:/etc/mysql/conf.d"
- "./mysql/data:/var/lib/mysql"
- "./mysql/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 | 网络 |
在资料中已经准备了黑马商城的部署文件 docker-compose.yml
:
version: "3.8"
services:
mysql:
image: mysql
container_name: mysql
ports:
- "3306:3306"
environment:
TZ: Asia/Shanghai
MYSQL_ROOT_PASSWORD: 123
volumes:
- "./mysql/conf:/etc/mysql/conf.d"
- "./mysql/data:/var/lib/mysql"
- "./mysql/init:/docker-entrypoint-initdb.d"
networks:
- hm-net
hmall:
build: # 构建镜像
context: . # 构建路径
dockerfile: Dockerfile # 根据dockerfile构建
container_name: hmall
ports:
- "8080:8080"
networks:
- hm-net
depends_on: # 依赖
- mysql
nginx:
image: nginx
container_name: nginx
ports:
- "18080:18080"
- "18081:18081"
volumes:
- "./nginx/nginx.conf:/etc/nginx/nginx.conf"
- "./nginx/html:/usr/share/nginx/html"
depends_on: # 依赖
- hmall
networks:
- hm-net
networks:
hm-net:
name: hmall
然后,我们将这个部署文件上传到和其他构建项目所需资料的同一目录下:
基础命令
编写好 docker-compose.yml
文件,就可以部署项目了。常见的命令可以参考官方文档:https://docs.docker.com/compose/reference/。
基本语法如下:
docker compose [OPTIONS] [COMMAND]
其中,OPTIONS 和 COMMAND 都是可选参数,比较常见的有:
类型 | 参数或指令 | 说明 |
---|---|---|
Options | -f | 指定 compose 文件的路径和名称,如果在当前目录下可以不用指定 |
Options | -p | 指定 project 名称。project 就是当前 compose 文件中设置的多个 service 的集合,是逻辑概念,默认为 root |
Options | -d | 后台运行 |
Commands | up | 创建并启动所有 service 容器 |
Commands | down | 停止并移除所有容器、网络 |
Commands | ps | 列出所有启动的容器 |
Commands | logs | 查看指定容器的日志 |
Commands | stop | 停止容器 |
Commands | start | 启动容器 |
Commands | restart | 重启容器 |
Commands | top | 查看运行的进程 |
Commands | exec | 在指定的运行中容器中执行命令 |
下面我们就用 docker compose 来部署项目。
首先,先删除前面创建的容器、镜像和网络:
然后用 docker compose 部署:
来到浏览器访问一下前端页面,点击登录,输入 Jack 和 123,登录成功:
如果我们执行 docker compose down
,就可以删除所有容器和网络: