docker官方文档解读
官网:https://docs.docker.com/get-started/overview/
1 Docker底层原理
1.1 docker使用的底层技术
首先,需要强调的一点是,目前的容器化技术基于Linux内核的两个重要特性:cgroups 和 Namespace
容器技术发展历史:https://blog.csdn.net/Tencent_TEG/article/details/109505143
容器的实质是进程,和宿主机上的其他进程是平级的。与宿主机上的其他进程是共用一个内核,但与直接在宿主机执行的进程不同,容器进程运行在属于自己的独立的命名空间。命名空间隔离了进程间的资源,使得 a,b 进程可以看到 S 资源,而 c 进程看不到。有关Namespace和cgroups的介绍可参考:https://www.cnblogs.com/aozhejin/p/16183046.html
1.2
2 开始
2.1 创建镜像并启动
这里我们以一个nodejs用于为例,说明容器化应用程序过程
首先克隆这个应用程序源码
git clone https://github.com/docker/getting-started.git
然后,创建一个Dockerfile文件
[root@localhost app]# pwd /data/srcs/getting-started/app [root@localhost app]# vi Dockerfile
Dockerfile内容为
# syntax=docker/dockerfile:1 FROM node:18-alpine WORKDIR /app COPY . . RUN yarn install --production --registry=https://registry.npm.taobao.org
# 如果yarn install命令执行失败,可以改为npm install试一下 CMD ["node", "src/index.js"] EXPOSE 3000
构建镜像
docker build -t getting-started .
其中-t参数为镜像打一个tag,.参数表示在当前路径下查找Dockerfile文件
启动镜像
docker run -dp 3000:3000 getting-started
然后我们访问宿主机的3000端口,可以看到应用程序部署成功
有时候我们重新生成了镜像,需要重新部署镜像
[root@localhost app]# docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES c77251691610 05a7b6b9d2f4 "docker-entrypoint.s…" 37 minutes ago Up 37 minutes 0.0.0.0:3000->3000/tcp, :::3000->3000/tcp ecstatic_cartwright [root@localhost app]# docker stop c77251691610 c77251691610 [root@localhost app]# docker rm c77251691610 c77251691610 [root@localhost app]# docker run -dp 3000:3000 getting-started 89ed2658b10f65f80d616fe571de53e2fae9113ff05da125eec9371b19f82eef [root@localhost app]#
2.2 共享镜像
共享镜像的意思是,我们将生成的镜像push到远程仓库。
我们以推送到docker hub为例
首先,需要在docker hub注册账号。
然后,登陆docker hub
docker login -u zhenjingcool
然后,执行docker tag命令
docker tag getting-started zhenjingcool/getting-started
然后执行docker push命令
docker push zhenjingcool/getting-started
然后,我们会发现仓库中多了一个镜像
接下来,我们在play with docker中运行刚才上传的镜像
首先登陆play with docker,然后点击"ADD NEW INSTANCE",然后执行 docker run -dp 3000:3000 zhenjingcool/getting-started ,如下图所示
然后,我们就可以看到我们运行的镜像应用了
2.3 使用volumes
上面的例子中,当我们重新启动getting started镜像后,我们之前在镜像实例中配置的todo list会丢失。这里我们使用volumes来进行数据的持久化。
我们的数据库在/etc/todos/todo-db文件中,我们创建一个volume然后挂载到容器的/etc/todos/路径下。
1 创建一个volume
[root@localhost ~]# docker volume create todo-db todo-db [root@localhost ~]# docker volume ls DRIVER VOLUME NAME local todo-db
2 启动容器
docker run -dp 3000:3000 --mount type=volume,src=todo-db,target=/etc/todos getting-started
然后我们可以在页面上增加几个todo项,然后重启容器,我们会发现,todo项做到了持久化。
3 查看volume详情
[root@localhost ~]# docker volume inspect todo-db [ { "CreatedAt": "2023-04-19T05:44:13-07:00", "Driver": "local", "Labels": null, "Mountpoint": "/var/lib/docker/volumes/todo-db/_data", "Name": "todo-db", "Options": null, "Scope": "local" } ] [root@localhost ~]#
这里,Mountpoint指的是磁盘上数据的实际位置。
2.4 使用bind mounts
上面我们使用volume方式挂载到容器中的特定路径来实现数据的持久化。这一小节我们使用另外一种方式,即bind mounts实现宿主机路径挂载到容器特定路径下,来实现数据持久化。
bind mounts是另一种挂载方式,这种方式允许我们共享宿主机的一个目录。
下面是volumes和bind mounts的简单对比
Named volumes | Bind mounts | |
---|---|---|
Host location | Docker chooses | You decide |
Mount example (using --mount ) |
type=volume,src=my-volume,target=/usr/local/data |
type=bind,src=/path/to/data,target=/usr/local/data |
Populates new volume with container contents | Yes | No |
Supports Volume Drivers | Yes | No |
实例
docker run -it --mount type=bind,src="$(pwd)",target=/src ubuntu bash
我们查看一下容器的/src路径,发现挂载了宿主机的/data/目录
root@08807d48a248:/src# ll total 28 drwxr-xr-x. 3 root root 68 Apr 17 14:46 ./ drwxr-xr-x. 1 root root 17 Apr 19 13:53 ../ -rw-r--r--. 1 root root 26024 Apr 24 2019 mysql80-community-release-el7-3.noarch.rpm drwxr-xr-x. 3 root root 29 Apr 17 14:46 srcs/ root@08807d48a248:/src#
我们在容器中创建/src/test.txt,然后退出容器,发现,在宿主机上也能看到创建的这个文件。
root@08807d48a248:/src# touch test.txt root@08807d48a248:/src# ll total 28 drwxr-xr-x. 3 root root 84 Apr 19 13:54 ./ drwxr-xr-x. 1 root root 17 Apr 19 13:53 ../ -rw-r--r--. 1 root root 26024 Apr 24 2019 mysql80-community-release-el7-3.noarch.rpm drwxr-xr-x. 3 root root 29 Apr 17 14:46 srcs/ -rw-r--r--. 1 root root 0 Apr 19 13:54 test.txt root@08807d48a248:/src# exit exit [root@localhost data]# ll total 28 -rw-r--r--. 1 root root 26024 Apr 24 2019 mysql80-community-release-el7-3.noarch.rpm drwxr-xr-x. 3 root root 29 Apr 17 07:46 srcs -rw-r--r--. 1 root root 0 Apr 19 06:54 test.txt [root@localhost data]#
2.5 多容器应用
一般情况下我们会把一个应用分立为几个容器。原因是:
- 方便扩展
- 分立容器可以独立更新,互不影响
这里我们的应用有两个容器部署,一个运行mysql,另一个运行应用本身
各个容器之前采用网络连接的方式进行通信。
2.5.1 部署mysql容器
1 创建network
docker network create todo-app
2 启动容器
docker run -d \ --network todo-app --network-alias mysql \ -v todo-mysql-data:/var/lib/mysql \ -e MYSQL_ROOT_PASSWORD=secret \ -e MYSQL_DATABASE=todos \ mysql:8.0
其中-v用于指定volumes挂载,其中volume名称为todo-mysql-data,不存在将会自动创建,挂载到容器中的/var/lib/mysql。
-e指定环境变量
我们登陆mysql看看是否成功创建数据库实例
[root@localhost app]# docker exec -it f21178ba297f mysql -u root -p
我们可以看到,数据库实例成功创建了
mysql> show databases; +--------------------+ | Database | +--------------------+ | information_schema | | mysql | | performance_schema | | sys | | todos | +--------------------+ 5 rows in set (0.00 sec) mysql>
2.5.2 使用netshoot进行网络诊断
一般,我们部署容器时,都是尽量减小容器体积,所以,容器中很多命令是无法使用的。比如netstat,ll等命令。
在这里,我们需要知道上面部署的mysql容器的ip,以便我们可以访问数据库。
我们使用nicolaka/netshoot进行网络诊断,在netshoot中,安装了很多和网络诊断相关的命令。
1 启动一个nicolaka/netshoot容器
docker run -it --network todo-app nicolaka/netshoot
启动成功后,我们看到如下界面
上面,我们启动mysql容器的时候,设置了容器的网络别名为mysql,这里,我们将使用 dig mysql 命令来解析这个网络,获取它的ip地址,如上图所示。
因此,我们得到的mysql容器的ip地址为172.18.0.2
2.5.3 启动应用容器
1 启动容器
首先,我们要进入到源码路径下 /data/srcs/getting-started/app/
在源码路径下的package.json中,有如下脚本
"scripts": { "prettify": "prettier -l --write \"**/*.js\"", "test": "jest", "dev": "nodemon src/index.js" },
因此,我们执行yarn run dev命令后执行的是 nodemon src/index.js
nodemon是一个nodejs代码调试工具,我们在源码路径下改动了代码之后,容器将会自动重新部署。
在当前路径下执行如下命令启动容器
docker run -dp 3000:3000 \ -w /app -v "$(pwd):/app" \ --network todo-app \ -e MYSQL_HOST=mysql \ -e MYSQL_USER=root \ -e MYSQL_PASSWORD=secret \ -e MYSQL_DB=todos \ node:18-alpine \ sh -c "yarn install && yarn run dev"
其中,这里我们启动一个node运行环境,且-w指定工作目录,-v指定使用volume,--network指定网络,-e指定环境变量,sh -c指定容器启动后要执行的sh命令。
2 查看启动日志
我们看到,我们成功连接上了mysql数据库
[root@localhost app]# docker logs --details 4f274e0723b8 yarn install v1.22.19 [1/4] Resolving packages... [2/4] Fetching packages... info There appears to be trouble with your network connection. Retrying... [3/4] Linking dependencies... [4/4] Building fresh packages... Done in 219.40s. yarn run v1.22.19 $ nodemon src/index.js [nodemon] 2.0.20 [nodemon] to restart at any time, enter `rs` [nodemon] watching path(s): *.* [nodemon] watching extensions: js,mjs,json [nodemon] starting `node src/index.js` Waiting for mysql:3306. Connected! Connected to mysql db at host mysql Listening on port 3000 [root@localhost app]#
3 页面添加todos,查看效果
当上面docker启动日志打印出Listening on port 3000后,我们在浏览器中访问应用,并添加两个待办项,然后去mysql中查看是否插入数据库中了
mysql> select * from todo_items; +--------------------------------------+----------------+-----------+ | id | name | completed | +--------------------------------------+----------------+-----------+ | f41a2fe4-b9ef-44f5-97c3-1a405419a8b2 | 待办项添加到m-01 | 0 | | 26aeb4e6-c373-43b0-9117-25b7023c6c80 | 待办项添加到m-02 | 0 | +--------------------------------------+----------------+-----------+ 2 rows in set (0.00 sec) mysql>
成功。
2.6 Docker Compose
Docker Compose是一个用来帮助定义多容器应用的工具。
查看Docker Compose版本
[root@localhost ~]# docker compose version Docker Compose version v2.17.2 [root@localhost ~]#
我们以getting-started应用为例,说明Docker Compose的使用方法,我们要配置两个镜像,一个应用本身的镜像,另一个mysql的镜像,我们把需要配置的镜像写到下面
docker run -dp 3000:3000 \ -w /app -v "$(pwd):/app" \ --network todo-app \ -e MYSQL_HOST=mysql \ -e MYSQL_USER=root \ -e MYSQL_PASSWORD=secret \ -e MYSQL_DB=todos \ node:18-alpine \ sh -c "yarn install && yarn run dev"
和
docker run -d \ --network todo-app --network-alias mysql \ -v todo-mysql-data:/var/lib/mysql \ -e MYSQL_ROOT_PASSWORD=secret \ -e MYSQL_DATABASE=todos \ mysql:8.0
我们使用docker compose来达到上述两个命令相同的结果
首先,进入到项目源码根路径下,创建 docker-compose.yml 文件,其内容为
services: app: image: node:18-alpine command: sh -c "yarn install && yarn run dev" ports: - 3000:3000 working_dir: /app volumes: - ./:/app environment: MYSQL_HOST: mysql MYSQL_USER: root MYSQL_PASSWORD: secret MYSQL_DB: todos mysql: image: mysql:8.0 volumes: - todo-mysql-data:/var/lib/mysql environment: MYSQL_ROOT_PASSWORD: secret MYSQL_DATABASE: todos volumes: todo-mysql-data:
然后我们使用命令 docker compose up -d 启动
[root@localhost app]# pwd /data/srcs/getting-started/app [root@localhost app]# docker compose up -d [+] Running 4/4 ✔ Network app_default Created 0.4s ✔ Volume "app_todo-mysql-data" Created 0.0s ✔ Container app-mysql-1 Started 5.4s ✔ Container app-app-1 Started 5.6s [root@localhost app]#
我们可以看一下启动日志
[root@localhost app]# docker compose logs -f ... app-app-1 | yarn install v1.22.19 app-app-1 | [1/4] Resolving packages... app-app-1 | success Already up-to-date. app-app-1 | Done in 1.31s. app-app-1 | yarn run v1.22.19 app-app-1 | $ nodemon src/index.js app-app-1 | [nodemon] 2.0.20 app-app-1 | [nodemon] to restart at any time, enter `rs` app-app-1 | [nodemon] watching path(s): *.* app-app-1 | [nodemon] watching extensions: js,mjs,json app-app-1 | [nodemon] starting `node src/index.js` app-app-1 | Waiting for mysql:3306. app-app-1 | Connected! app-app-1 | Connected to mysql db at host mysql app-app-1 | Listening on port 3000
然后我们打开浏览器访问应用,能够成功访问,说明启动成功了
我们使用 docker compose down 命令来关闭Docker Compose启动的所有容器。
[root@localhost app]# docker compose down [+] Running 3/3 ✔ Container app-mysql-1 Removed 3.4s ✔ Container app-app-1 Removed 0.4s ✔ Network app_default Removed 0.3s [root@localhost app]#
2.7 Image Layering镜像分层
镜像分层,意思是一个镜像是由很多层组成。我们使用如下命令进行查看
docker image history getting-started
然后其输出是这样的
[root@localhost ~]# docker image history getting-started:latest IMAGE CREATED CREATED BY SIZE COMMENT b5e92c3a7fce 3 days ago EXPOSE map[3000/tcp:{}] 0B buildkit.dockerfile.v0 <missing> 3 days ago CMD ["node" "src/index.js"] 0B buildkit.dockerfile.v0 <missing> 3 days ago RUN /bin/sh -c npm install --production --re… 50.5MB buildkit.dockerfile.v0 <missing> 3 days ago COPY . . # buildkit 4.59MB buildkit.dockerfile.v0 <missing> 4 days ago WORKDIR /app 0B buildkit.dockerfile.v0 <missing> 8 days ago /bin/sh -c #(nop) CMD ["node"] 0B <missing> 8 days ago /bin/sh -c #(nop) ENTRYPOINT ["docker-entry… 0B <missing> 8 days ago /bin/sh -c #(nop) COPY file:4d192565a7220e13… 388B <missing> 8 days ago /bin/sh -c apk add --no-cache --virtual .bui… 7.78MB <missing> 8 days ago /bin/sh -c #(nop) ENV YARN_VERSION=1.22.19 0B <missing> 8 days ago /bin/sh -c addgroup -g 1000 node && addu… 160MB <missing> 8 days ago /bin/sh -c #(nop) ENV NODE_VERSION=18.16.0 0B <missing> 3 weeks ago /bin/sh -c #(nop) CMD ["/bin/sh"] 0B <missing> 3 weeks ago /bin/sh -c #(nop) ADD file:9a4f77dfaba7fd2aa… 7.05MB
我们对照Dockerfile查看
[root@localhost ~]# cat /data/srcs/getting-started/app/Dockerfile # syntax=docker/dockerfile:1 FROM node:18-alpine WORKDIR /app COPY . . RUN npm install --production --registry=https://registry.npm.taobao.org CMD ["node", "src/index.js"] EXPOSE 3000
他们是一一对应的。
2.8 镜像分层原理
参考https://blog.csdn.net/weixin_37926734/article/details/123267870
镜像是一个轻量级、可执行的独立软件包,它包含了运行软件所需要的所有内容
镜像中在每一层上只记录本层所做的更改,而且这些层是只读层。当启动一个容器,Docker会在最顶部添加读写层,在容器内作的所有更改(写日志、修改、删除文件等,都保存到读写层内),一般称该层为容器层,如下图所示。
容器需要读取某个文件时,直接从底部只读层去读即可,而如果需要修改某文件,则将该文件拷贝到顶部读写层进行修改,只读层保持不变。
docker容器底层使用的是linux的容器化技术:namespace,在宿主机看来,容器内的进程和普通进程并没有差别,他们共享同一个内核,共同参与cpu时间片调度。
然而,他们其实是有差别的,他们的差别就是所在的namespace不同,对于一个容器,内核使用的是宿主机内核,但是发行版使用的是自己的发行版。普通的发行版镜像动辄几个G大小,但是容器不需要这个大而全的发行版,容器对发行版进行了裁剪,只保留运行应用最基础的软件和依赖,往往只有200M就够了。
3 Docker Desktop
docker desktop是一个工具,它提供了GUI界面,我们通过docker desktop可以方便的管理镜像和容器。
windows下安装docker desktop需要开启虚拟化,win10默认是开启的,可以通过任务管理器-CPU查看是否开启虚拟化。此外,还需要在启用和关闭windows功能中启用Hyper-V。具体安装过程不再细说。
安装后的界面是这样的
建议配置一下国内的镜像,这样下载速度会快很多。
3.1 简单例子
上面我们安装了docker desktop之后,我们在powershell中启动一个容器
docker run -p 8088:80 -d --name welcome-to-docker docker/welcome-to-docker
然后我们在Docker Desktop中查看,可以看到我们运行中的容器,如上图所示。
然后我们可以在浏览器中打开这个容器应用。
并且,通过Docker Desktop,我们可以查看系统日志、容器内部的文件、打开终端等功能
我们也可以T通过Docker Desktop关闭容器
3.2 制作镜像并通过Docker Desktop运行
下载源码
git clone https://github.com/docker/welcome-to-docker
构建镜像
PS E:\code\welcome-to-docker> docker build -t welcome-to-docker . [+] Building 326.7s (11/11) FINISHED => [internal] load build definition from Dockerfile 0.5s => => transferring dockerfile: 32B 0.0s => [internal] load .dockerignore 0.7s => => transferring context: 34B 0.0s => [internal] load metadata for docker.io/library/node:18-alpine 18.7s => [internal] load build context 0.4s => => transferring context: 393B 0.0s => [1/6] FROM docker.io/library/node:18-alpine@sha256:ca5d399560a9d239cbfa28eec00417f1505e5e108f3ec6938d230767ea 0.0s => CACHED [2/6] WORKDIR /app 0.0s => CACHED [3/6] COPY package*.json ./ 0.0s => CACHED [4/6] COPY ./src ./src 0.0s => CACHED [5/6] COPY ./public ./public 0.0s => [6/6] RUN npm install && npm install -g serve && npm run build && rm -fr node_modules 302.3s => exporting to image 3.8s => => exporting layers 3.2s => => writing image sha256:95a572fa36a516f602db7a408350baecf21380b07067590c2e51d649de9dd0ae 0.1s => => naming to docker.io/library/welcome-to-docker 0.0s PS E:\code\welcome-to-docker>
Desktop中启动
然后我们会看到容器启动日志
然后我们访问localhost:8089将会看到应用运行了
3.3 运行Docker Hub镜像
Docker desktop中有一个搜索框,可以搜索Docker Hub的镜像,并直接启动。
此外,我们也可以通过Docker Desktop发布自己的镜像到Docker Hub仓库。
4 java相关
4.1 构建镜像
这里,我们以一个springboot项目为例,说明如何把一个项目构建成镜像
1 下载源码
我们进入到E:\code\路径下,执行如下命令
git clone https://github.com/spring-projects/spring-petclinic.git
然后进入到E:\code\spring-petclinic路径下,创建两个文件Dockfile文件和.dockerignore文件
Dockfile
# syntax=docker/dockerfile:1 FROM eclipse-temurin:17-jdk-focal WORKDIR /app COPY .mvn/ .mvn COPY mvnw pom.xml ./ RUN ./mvnw dependency:resolve COPY src ./src CMD ["./mvnw", "spring-boot:run"]
.dockerignore
target
2 构建镜像
docker build --tag java-docker .
3 查看镜像
docker images REPOSITORY TAG IMAGE ID CREATED SIZE java-docker latest b1b5f29f74f0 47 minutes ago 567MB
4 给镜像打Tag
docker tag java-docker:latest java-docker:v1.0.0
然后我们再次查看镜像
docker images REPOSITORY TAG IMAGE ID CREATED SIZE java-docker latest b1b5f29f74f0 59 minutes ago 567MB java-docker v1.0.0 b1b5f29f74f0 59 minutes ago 567MB
5 删除特定tag的镜像
docker rmi java-docker:v1.0.0 Untagged: java-docker:v1.0.0
然后我们再次查看镜像,发现v1.0.0的那个Tag被删除了
docker images REPOSITORY TAG IMAGE ID CREATED SIZE java-docker latest b1b5f29f74f0 59 minutes ago 567MB
4.2 启动容器
上面我们已经生成了镜像,现在我们启动运行,我们可以选择在Docker Desktop中启动,也可以选择在powershell中启动
docker run -p 8080:8080 java-docker
启动过程中,其实是执行的./mvnw springboot:run命令,会下载依赖包,会比较慢,大约会20分钟。
然后我们浏览器中访问http://localhost:8080/actuator/health验证服务是否正常。
4.3 多容器应用
这里我们构建两个镜像,一个运行mysql,另一个运行springboot。
1 创建两个volume
PS C:\Users\root> docker volume create mysql_data mysql_data PS C:\Users\root> docker volume create mysql_config mysql_config PS C:\Users\root> docker volume ls DRIVER VOLUME NAME local mysql_config local mysql_data
2 创建一个network
PS C:\Users\root> docker network create mysqlnet
843f80283398d4cc0cb3d12b22d6683e418118eb6dc7065441a676b50045b940
PS C:\Users\root>
3 构建并运行mysql镜像
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 3306:3306 mysql:8.0
其中--rm参数表示,如果有这个容器则删除旧的
4 修改Dockerfile文件
我们修改4.1小节中的那个java项目的Dockerfile文件,最后一行修改为如下内容。这里指定了profile变量。
CMD ["./mvnw", "spring-boot:run", "-Dspring-boot.run.profiles=mysql"]
指定了profile之后,springboot的配置文件将使用application-mysql.properties
# database init, supports mysql too database=mysql spring.datasource.url=${MYSQL_URL:jdbc:mysql://localhost/petclinic} spring.datasource.username=${MYSQL_USER:petclinic} spring.datasource.password=${MYSQL_PASS:petclinic} # SQL is written to be idempotent so this is safe spring.sql.init.mode=always
5 构建springboot镜像
docker build --tag java-docker .
6 启动springboot容器
docker run --rm -d \ --name springboot-server \ --network mysqlnet \ -e MYSQL_URL=jdbc:mysql://mysqlserver/petclinic \ -p 8080:8080 java-docker
因为我们指定了-d参数,因此,我们看不到启动过程(如果想看启动过程可以去Docker Desktop中查看),启动可能要一段时间。启动之后,我们访问一下链接看看效果
$ curl --request GET --url http://localhost:8080/vets --header 'content-type: application/json' {"vetList":[{"id":1,"firstName":"James","lastName":"Carter","specialties":[],"nrOfSpecialties":0,"new":false},{"id":2,"firstName":"Helen","lastName":"Leary","specialties":[{"id":1,"name":"radiology","new":false}],"nrOfSpecialties":1,"new":false},{"id":3,"firstName":"Linda","lastName":"Douglas","specialties":[{"id":3,"name":"dentistry","new":false},{"id":2,"name":"surgery","new":false}],"nrOfSpecialties":2,"new":false},{"id":4,"firstName":"Rafael","lastName":"Ortega","specialties":[{"id":2,"name":"surgery","new":false}],"nrOfSpecialties":1,"new":false},{"id":5,"firstName":"Henry","lastName":"Stevens","specialties":[{"id":1,"name":"radiology","new":false}],"nrOfSpecialties":1,"new":false},{"id":6,"firstName":"Sharon","lastName":"Jenkins","specialties":[],"nrOfSpecialties":0,"new":false}]}
4.4 多阶段Dockerfile
多阶段Dockerfile的意思是,在一个Dockerfile中,我们定义多个阶段,然后,我们启动一个镜像时,可以指定运行哪个阶段。
因为一个项目有开发和生产部署阶段,如果使用多阶段Dockerfile,我们可以在一个Dockerfile中定义多个阶段:开发环境、编译、生产。在开发阶段,我们只需要运行开发阶段,在生产部署时,我们运行容器时指定生产阶段就可以了,这样方便我们开发和部署。
例子,在这个Dockerfile中,我们定义了4个stage,分别是base,development,build,production。并且后一阶段可以用前一阶段的结果。
# syntax=docker/dockerfile:1 FROM eclipse-temurin:17-jdk-focal 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-focal 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"]
4.5 使用Compose进行本地开发
在上一小节基础上,更进一步,我们在Dockerfile同级目录下创建docker-compose.dev.yml文件
version: '3.8'
services:
petclinic:
build:
context: .
target: development
ports:
- "8000:8000"
- "8080:8080"
environment:
- SERVER_PORT=8080
- MYSQL_URL=jdbc:mysql://mysqlserver/petclinic
volumes:
- ./:/app
depends_on:
- mysqlserver
mysqlserver:
image: mysql:8.0
ports:
- "3306:3306"
environment:
- MYSQL_ROOT_PASSWORD=
- MYSQL_ALLOW_EMPTY_PASSWORD=true
- 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_config:
然后我们通过docker-compose启动应用,这个过程会比较慢,大约10分钟左右会启动完毕
docker-compose -f docker-compose.dev.yml up --build
其中,--build表示重构镜像,在yml文件中,我们指定了build的阶段是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'"]
然后我们访问一下看一下效果
$ curl --request GET --url http://localhost:8080/vets --header 'content-type: application/json' {"vetList":[{"id":1,"firstName":"James","lastName":"Carter","specialties":[],"nrOfSpecialties":0,"new":false},{"id":2,"firstName":"Helen","lastName":"Leary","specialties":[{"id":1,"name":"radiology","new":false}],"nrOfSpecialties":1,"new":false},{"id":3,"firstName":"Linda","lastName":"Douglas","specialties":[{"id":3,"name":"dentistry","new":false},{"id":2,"name":"surgery","new":false}],"nrOfSpecialties":2,"new":false},{"id":4,"firstName":"Rafael","lastName":"Ortega","specialties":[{"id":2,"name":"surgery","new":false}],"nrOfSpecialties":1,"new":false},{"id":5,"firstName":"Henry","lastName":"Stevens","specialties":[{"id":1,"name":"radiology","new":false}],"nrOfSpecialties":1,"new":false},{"id":6,"firstName":"Sharon","lastName":"Jenkins","specialties":[],"nrOfSpecialties":0,"new":false}]}
4.6 连接Debugger进行远程调试
我们使用idea打开这个项目,然后Run-Edit Configuration,添加RemoteDebug,如下
然后我们启动debug
然后我们在如下接口代码处打断点
@GetMapping({ "/vets" }) public @ResponseBody Vets showResourcesVetList() { // Here we are returning an object of type 'Vets' rather than a collection of Vet // objects so it is simpler for JSon/Object mapping Vets vets = new Vets(); vets.getVetList().addAll(this.vetRepository.findAll());//断点打在此处 return vets; }
然后我们调用接口
curl --request GET --url http://localhost:8080/vets --header 'content-type: application/json'
我们会看到,远程调试起作用了
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具