
Docker 初探:制作最简单的镜像
Docker 初探
创建镜像 dockerfile:制作自己的镜像
目录挂载 Volumes:持久化数据,保存数据(如数据库数据存储);
多容器通信 network:如后端程序与数据库通信
Docker-Compose:把所有内容汇集到一个Compose文件中,该文件允许您使用一个命令来设置和运行本地开发环境。
Dockerfile 初探
相关官方文档:链接
相关教程:easydoc
今天学到了如何制作自己的镜像,但是Dockerfile却不知道如何编写,遂整理出该文档入坑Dockerfile
最简单最基础的 Dockerfile
询问ChatGPT ”可以做一个最基础最简单的dockerfile吗?“,给出的代码如下:
# 使用官方的 Nginx 镜像作为基础镜像
FROM nginx
# 将本地的 index.html 文件复制到 Nginx 默认的 HTML 目录下
COPY index.html /usr/share/nginx/html
# 暴露容器的端口
EXPOSE 80
ChatGPT:如果不使用基础镜像,你需要自己手动搭建整个操作系统环境和应用程序所需的运行环境,这将会非常复杂和耗时。通常情况下,使用基础镜像是一种更加简单和高效的方法,因为基础镜像已经包含了操作系统环境和预装的软件,你只需要在其基础上进行定制即可。
但如果你想自己构建一个不基于任何基础镜像的 Docker 镜像,你需要先创建一个最小的 Linux 环境,并手动安装和配置所有需要的软件和依赖项。这样做的难度非常大,而且容易出错。
在实际情况下,为了方便和效率,几乎所有的 Docker 镜像都是基于某个基础镜像构建的。
于是我写了一个index.html,写了一个上述的Dockerfile:
<!DOCTYPE html>
<body>
<h1>Hello World!</h1>
<p>Hello dockerfile!</p>
</body>
按照ChatGPT给出的方法,将以上两个文件放入同一个目录下,在这个目录打开cmd,运行:
docker build -t my-nginx-server .
这会在当前目录下构建一个名为 my-nginx-server 的 Docker 镜像。
这时打开 Docker Desktop,就可以在镜像列表找到这个镜像了:
之后无论是使用命令行还是直接用图形界面都是顺理成章的事情了。
docker run -p 8080:80 my-nginx-server
而按照Docker官方的说法,要用docker init之后,自己生成Dockerfile和compose.yaml文件,之后再用docker-compose,compose.yaml配置如下:
version: '3'
services:
nginx:
build: .
ports:
- "8080:80"
volumes:
- ./index.html:/usr/share/nginx/html/index.html
然而在 docker compose up 命令中,却发生了如下错误,有个软件包没有下载下来:
不禁让人怀疑是不是镜像源的问题,这个留到以后再深究。
配置一个 Java 项目镜像
官方文档:Java language-specific guide
本人是Java程序员,那肯定先从熟悉的 Springboot 项目入手最好。
编写Dockerfile
首先在合适路径下克隆官方示例项目:
git clone https://github.com/spring-projects/spring-petclinic.git
这个PetClinic项目据说也是Springboot中一个很经典的项目,github链接
运行后,访问localhost:8080 即可访问项目:
然后在项目的根目录创建一个文件命名Dockerfile,并开始编辑:
FROM 使用基础镜像:可以认为是Dockerfile程序的启动入口。
docker官方推荐使用Eclipse Temurin,(渣翻:声称是最受欢迎的官方镜像,并且有一个值得构建的JDK)
FROM eclipse-temurin:17-jdk-jammy
WORKDIR 工作目录:这指示 Docker 使用此路径作为所有后续命令的默认位置。
为了在运行其他命令时更容易,可以设置图像的工作目录。这指示 Docker 使用此路径作为所有后续命令的默认位置。通过这样做,你不必输入完整的文件路径,但可以使用基于工作目录的相对路径。
先直观了解一下 WORKDIR ,假如我们这样填写:
WORKDIR /appWorkdir
那么我们在最终项目运行后,这个目录会在这里:
COPY 复制文件到镜像 :将宿主机目录中文件复制到镜像中。
COPY命令采用两个参数。第一个参数告诉Docker您想要复制到镜像中的文件。第二个参数告诉Docker您希望将文件复制到哪个位置。您将所有这些文件和目录复制到您的工作目录 /appWorkdir
中。
为什么要使用COPY命令呢? 通常,下载了一个使用 Maven 进行项目管理的Java项目后,您要做的第一件事就是安装依赖项(使用 Maven Wrapper)。
在运行 mvnw 依赖项之前,您需要将 Maven 包装器和 pom.xml 文件放入映像中。您将使用COPY命令来执行此操作。
# 复制.mvn文件夹
COPY .mvn/ .mvn
# 复制 mvnw pom.xml 两个文件到根目录下
COPY mvnw pom.xml ./
RUN 运行命令:与shell命令完全相同,只不过是运行在镜像中的文件;
RUN ./mvnw dependency:resolve
然后,把项目源代码复制过去:
COPY src ./src
CMD运行命令:
CMD ["./mvnw", "spring-boot:run"]
完整代码如下:
# syntax=docker/dockerfile:1
FROM eclipse-temurin:17-jdk-jammy
WORKDIR /appWorkdir
COPY .mvn/ .mvn
COPY mvnw pom.xml ./
RUN ./mvnw dependency:resolve
COPY src ./src
CMD ["./mvnw", "spring-boot:run"]
另外,为了提高构建的性能,并且作为一般的最佳实践,Docker建议您在与Dockerfile相同的目录中创建一个.dokerignore文件。对于本教程,.dockerignore文件应该只包含一行:
target
此行将目标目录从Docker构建上下文中排除,该目录包含Maven的输出。
运行Dockerfile
docker build --tag java-docker .
然后从构建docker项目一直到Springboot运行成功,等待约20分钟。
Intellij IDEA 专业版也继承了Docker模块,可以在图形化界面运行:
这里可以给项目版本号打tag:
# 打tag
docker tag java-docker:latest java-docker:v1.0.0
# 去除tag
docker rmi java-docker:v1.0.0
打tag后,命令行和客户端分别显示如下:
REPOSITORY TAG IMAGE ID CREATED SIZE
java-docker latest 63ce4252ffff 11 minutes ago 605MB
java-docker v1.0.0 63ce4252ffff 11 minutes ago 605MB
目录挂载 Volumes
将容器内目录挂载到宿主机 Bind Mount
到目前为止,我们只能在docker desktop里能够进入容器内命令行、查看文件等,离开docker desktop就很不方便了。
假如我们在docker里安装了一个MySQL容器,我们想要在宿主机内 `D:\Users\Donnie\Desktop\dockerTest\mysql1` 目录查看容器内的 `/var/lib/mysql` 目录
本例中:
宿主机内 D:\Users\Donnie\Desktop\dockerTest\mysql1 是空目录;
容器内 /var/lib/mysql目录存在运行容器时已存在的文件,即MySQL数据;
实际操作中,如果容器内是空目录,宿主机内已存在文件也可以实现同步挂载,即把宿主机内文件挂载到容器内。
我们可以在docker run的时候多加一个参数:
-v D:\Users\Donnie\Desktop\dockerTest\mysql1:/var/lib/mysql
-v 代表挂载
冒号”:“前 代表 宿主机目录 是宿主机的绝对路径
冒号”:“后 代表 容器内目录 是容器内的绝对路径
比如我们在运行Mysql镜像时:
docker run -it --rm -d
-v D:\Users\Donnie\Desktop\dockerTest\mysql1:/var/lib/mysql
--network mysqlnet
--name mysqlserver3308
-e MYSQL_USER=petclinic
-e MYSQL_PASSWORD=petclinic
-e MYSQL_ROOT_PASSWORD=root
-e MYSQL_DATABASE=petclinic
-p 3308:3306 mysql:8.0
运行后,打开宿主机的目录:
再看看容器里的目录:
可以说一毛一样,在docker desktop还显示备注 MOUNT
在宿主机创建一个a.txt,在容器内ls也能看到;而在容器内执行 echo "Hello World!!! >> a.txt
后,用记事本打开a.txt也能看到改动。
注意:mysql容器内不含vim,无法使用apt或者yum等包管理器
这样除了直接在宿主机打开之外,还有几个好处
多容器共用:我们可以配置多个容器共享一个宿主机的目录,达到共享数据/配置文件的作用
持久备份:一旦容器被删除,容器里的东西就没了,但是挂载在宿主机里的东西还在。
将容器内目录挂载到 Docker Volume
然鹅,有时候我们不想把这些敏感信息挂载到宿主机内,但是我们还是想实现多容器公用和持久备份功能。
这种情况,Docker提供了 Docker Volume 来替代宿主机目录。
在这里,我们创建一个docker volume叫 mysql_data
docker volume create mysql_data
然后,在docker run的时候也类似的添加一个命令:
# 宿主机挂载
-v D:\Users\Donnie\Desktop\dockerTest\mysql1:/var/lib/mysql
# Docker Volume 挂载
-v mysql_data:/var/lib/mysql
-v 代表挂载
冒号”:“前 代表 volume名称
冒号”:“后 代表 容器内目录 是容器内的绝对路径
如果我们运行以下命令
docker run -it --rm -d
-v mysql_data:/var/lib/mysql
--network mysqlnet
--name mysqlserver3308
-e MYSQL_USER=petclinic
-e MYSQL_PASSWORD=petclinic
-e MYSQL_ROOT_PASSWORD=root
-e MYSQL_DATABASE=petclinic
-p 3307:3306 mysql:8.0
可以在Volumes中的mysql_data看到这些目录:
多容器通信 Network
很简单,这个意思就是把两个容器放在同一个网络下。
比如Springboot程序是一个容器,MySQL数据库是另一个容器,后端程序想要调用数据库,就必须把他们放在同一个网络下。
# 创建 network
docker network create mysqlnet
# docker run 时 配置network
--network mysqlnet \
# 数据库 docker run
docker run -it --rm -d -v mysql_data:/var/lib/mysql \
-v mysql_config:/etc/mysql/conf.d \
--network mysqlnet \
--name mysqlserver \
-e MYSQL_USER=petclinic -e MYSQL_PASSWORD=petclinic \
-e MYSQL_ROOT_PASSWORD=root -e MYSQL_DATABASE=petclinic \
-p 3380:3306 mysql:8.0
# springboot docker run
docker run --rm -d \
--name springboot-server \
--network mysqlnet \
-e MYSQL_URL=jdbc:mysql://mysqlserver/petclinic \
-p 8080:8080 java-docker
注意:Mysql对宿主机开放转发的端口不是3306,这不会影响容器内springboot请求mysql。
MYSQL_URL 这里 `mysqlserver` 代替了 localhost:3306 集成在了Springboot程序中。
用于开发的多阶段Dockerfile
现在,您可以更新Dockerfile,以制作准备生产的最终镜像(production) 以及 制作开发镜像(development)的专用步骤。
您还将设置DockerFile以在开发容器中以调试模式启动应用程序,以便您可以将调试器连接到运行的Java进程。
以下是一个多阶段的Dockerfile,您将用于构建生产镜像和开发镜像。用以下内容替换Dockerfile的内容。
# syntax=docker/dockerfile:1
FROM eclipse-temurin:17-jdk-jammy as base
WORKDIR /app
COPY .mvn/ .mvn
COPY mvnw pom.xml ./
RUN ./mvnw dependency:resolve
COPY src ./src
FROM base as development
CMD ["./mvnw", "spring-boot:run", "-Dspring-boot.run.profiles=mysql", "-Dspring-boot.run.jvmArguments='-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:8000'"]
FROM base as build
RUN ./mvnw package
FROM eclipse-temurin:17-jre-jammy as production
EXPOSE 8080
COPY --from=build /app/target/spring-petclinic-*.jar /spring-petclinic.jar
CMD ["java", "-Djava.security.egd=file:/dev/./urandom", "-jar", "/spring-petclinic.jar"]
Docker-Compose
看看上面两个 docker run 的命令:
# 数据库 docker run
docker run -it --rm -d -v mysql_data:/var/lib/mysql \
-v mysql_config:/etc/mysql/conf.d \
--network mysqlnet \
--name mysqlserver \
-e MYSQL_USER=petclinic -e MYSQL_PASSWORD=petclinic \
-e MYSQL_ROOT_PASSWORD=root -e MYSQL_DATABASE=petclinic \
-p 3380:3306 mysql:8.0
# springboot docker run
docker run --rm -d \
--name springboot-server \
--network mysqlnet \
-e MYSQL_URL=jdbc:mysql://mysqlserver/petclinic \
-p 8080:8080 java-docker
我们每次运行的时候都要敲一遍这个代码…… 要是不写脚本的话,多多少少有点厌烦,而docker-compose就是一种运行”脚本“;
version: '3.8' # Docker Compose 文件的版本号
services: # 定义服务部分的开始
petclinic: # 定义一个名为 petclinic 的服务
build: # 构建镜像的配置部分
context: . # 构建上下文路径,指定了 Dockerfile 的位置为当前目录
target: development # 指定构建的目标阶段为 development
ports: # 端口映射配置部分
- "8000:8000" # 将宿主机的 8000 端口映射到容器的 8000 端口
- "8080:8080" # 将宿主机的 8080 端口映射到容器的 8080 端口
environment: # 环境变量配置部分
- SERVER_PORT=8080
- MYSQL_URL=jdbc:mysql://mysqlserver/petclinic
volumes: # 数据卷配置部分
- ./:/app # 将当前目录挂载到容器的 /app 目录
depends_on: # 依赖关系配置部分
- mysqlserver # 表示 petclinic 服务依赖于 mysqlserver 服务
mysqlserver: # 定义一个名为 mysqlserver 的服务
image: mysql:8.0 # 使用 MySQL 8.0 的官方镜像
ports: # 端口映射配置部分
- "3307:3306" # 将宿主机的 3307 端口映射到容器的 3306 端口
environment: # 环境变量配置部分
- MYSQL_ROOT_PASSWORD= # 设置 root 用户的密码为空
- MYSQL_ALLOW_EMPTY_PASSWORD=true # 允许 root 用户的密码为空
- MYSQL_USER=petclinic
- MYSQL_PASSWORD=petclinic
- MYSQL_DATABASE=petclinic
volumes: # 数据卷挂载配置部分
- mysql_data:/var/lib/mysql
- mysql_config:/etc/mysql/conf.d
volumes: # 定义数据卷部分的开始
mysql_data: # 定义一个名为 mysql_data 的数据卷
mysql_config: # 定义一个名为 mysql_config 的数据卷
把docker-compose.yml 和 Dockerfile放在同一目录下,运行:
docker compose -f docker-compose.yml up --build
若等待一段时间后运行起来,下面验证接口:
curl --request GET \
--url http://localhost:8080/vets \
--header 'content-type: application/json'
成功!