官方示例
官方地址:https://docs.docker.com/get-started/02_our_app/
Docker作为一个容器,可以在容器中安装各种应用
获取官方提供的应用示例
官方地址:https://github.com/docker/getting-started/tree/master/app
建立应用容器镜像
为了创建一个应用,需要使用一个 Dockerfile
一个Dockerfile只是一个基于文本的指令脚本,用于创建容器映像。如果之前已经创建过Dockerfile,可能已经见识过了Dockerfile的暇疵
# syntax=docker/dockerfile:1 FROM node:12-alpine RUN apk add --no-cache python g++ make WORKDIR /app COPY . . RUN yarn install --production CMD ["node", "src/index.js"]
构建docker镜像
docker build -t getting-started .
首先会下载相应的镜像依赖,然后开始构建,然后执行yarn指令,下载应用所需要的依赖
参数解释 -t 和 .
- -t 会标记我们的镜像,可以当作起名字,这个镜像的名字就是 getting-started,也可以理解为这个镜像的引用
- . 在这条构建docker指令的最后,通知Docker在当前目录查找Dockerfile
启动应用容器
构建步骤结束后,我们已经有了一个镜像,通过 docker run 指令启动容器
docker run -dp 3000:3000 getting-started
参数解释 -d 和 -p
- -d,Run container in background and print container ID,后台运行并打印容器的ID
- -p,Publish a container's port(s) to the host,给主机暴露该容器的端口
当前指令的意思是把容器的3000端口映射到主机的3000端口,没有这个映射我们不能直接管理应用
更新应用
需要查看容器列表然后停下指定容器
docker ps
使用docker stop指令
# Swap out <the-container-id> with the ID from docker ps docker stop <the-container-id>
删除该容器
docker rm <the-container-id>
也可以使用强制删除指令
docker rm -f <the-container-id>
把新的应用覆盖上去,然后重新启动即可
docker run -dp 3000:3000 getting-started
共享应用
现在我们已经创建好了镜像,可以共享镜像了,必须使用一个Docker注册处才能实现共享,默认的注册处是Docker Hub,我们所有使用的镜像都来源于注册处
一个Docker ID允许访问Docker Hub,这是世界上最大的容器镜像的库和社区
创建Docker ID的链接:https://hub.docker.com/signup
创建仓库
在推送一个镜像前,我们首先需要在Docker Hub上创建一个仓库
- 注册并使用Docker Hub分享镜像
- 在Docker Hub登录
- 点击创建仓库按钮
- 使用getting-started给仓库设置名字,保证可见性为 Public
推送镜像
$ docker push docker/getting-started The push refers to repository [docker.io/docker/getting-started] An image does not exist locally with the tag: docker/getting-started
在命令行中,尝试运行在Docker Hub上看到的push命令。请注意,您的命令将使用您的命名空间,而不是“docker”
为什么失败了?push命令正在查找名为docker/getting started的图像,但没有找到。如果运行docker image ls,也不会看到
要解决这个问题,我们需要“标记”我们已经建立的现有镜像,以给它起另一个名字
使用一下指令在Docker Hub上登录
docker login -u YOUR-USER-NAME
使用docker tag命令为getting-start赋予一个新名称,一定要把你的用户名换成你的Docker ID
docker tag getting-started YOUR-USER-NAME/getting-started
现在再试一次你的推送命令
docker push YOUR-USER-NAME/getting-started
如果要从Docker Hub复制值,可以删除标记名部分,因为我们没有向图像名称添加标记。如果不指定标记,Docker将使用名为latest的标记
持久化数据库
容器的文件系统
当一个容器运行时,它使用来自一个映像的不同层作为它的文件系统。每个容器也有自己的“暂存空间”来创建/更新/删除文件。在另一个容器中看不到任何更改,即使它们使用相同的图像
开启一个Ubuntu容器,该容器能够创建一个被称为 /data.txt 的文件,该文件携带1到10000的随机数
docker run -d ubuntu bash -c "shuf -i 1-10000 -n 1 -o /data.txt && tail -f /dev/null"
这条命令,启动一个bashshell并调用两个命令(所以使用&&)。第一部分选取一个随机数并将其写入/data.txt。第二个命令只是监视一个文件以保持容器运行
我们可以通过在容器中执行来查看输出。为此,打开仪表板并单击运行ubuntu映像的容器的第一个操作
您将看到在ubuntu容器中运行shell的终端,运行以下命令以查看/data.txt文件的内容,之后再次关闭此终端
cat /data.txt
如果喜欢命令行,也可以使用docker exec命令来执行相同的操作,首先需要获取容器的ID(使用docker ps获取它)并使用以下命令获取内容
docker exec <container-id> cat /data.txt
同时启动另一个ubuntu容器(相同的镜像),我们将看到没有相同的文件
docker run -it ubuntu ls /
通过以下指令删除容器
docker rm -f
容器卷
在前面的实验中,我们看到每个容器每次启动时都从图像定义开始
虽然容器可以创建、更新和删除文件,但当容器被删除并且所有更改都与该容器隔离时,这些更改将丢失,通过卷,我们可以改变这一切
卷提供了将容器的特定文件系统路径连接回主机的能力,如果装载了容器中的目录,则在主机上也会看到该目录中的更改,如果我们跨容器重新启动装载相同的目录,我们会看到相同的文件
有两种主要的卷类型。我们最终将两者都使用,但我们将从命名卷开始
Persist the todo data
默认情况下,todo应用程序将其数据存储在SQLite数据库/etc/todos/todo.db中
由于数据库是一个单独的文件,如果我们能够将该文件持久化到主机上并使其可供下一个容器使用,那么它应该能够从上一个容器停止的地方恢复
通过创建一个卷并将其附加(通常称为“装载”)到存储数据的目录中,我们可以持久化数据,当容器写入todo.db文件时,它将被持久化到卷中的主机
将命名卷简单地看作一个数据桶
Docker维护磁盘上的物理位置,您只需记住卷的名称,每次使用卷时,Docker都会确保提供正确的数据
通过下面指令创建卷
docker volume create todo-db
在仪表板中再次停止并移除todo应用程序容器(或使用docker rm-f<id>),因为它仍在运行,而不使用持久卷
启动todo应用程序容器,添加-v标志以指定卷装载,将使用命名卷并将其装载到/etc/todos,它将捕获在该路径上创建的所有文件
docker run -dp 3000:3000 -v todo-db:/etc/todos getting-started
一旦容器启动,打开应用程序并将一些项目添加到您的待办事项列表中
停止并删除todo应用程序的容器,使用仪表板或 docker ps 获取ID,然后使用 docker rm-f<ID> 将其删除
Dive into the volume(侦测卷)
使用下面这条指令
docker volume inspect
docker volume inspect todo-db [ { "CreatedAt": "2019-09-26T02:18:36Z", "Driver": "local", "Labels": {}, "Mountpoint": "/var/lib/docker/volumes/todo-db/_data", "Name": "todo-db", "Options": {}, "Scope": "local" } ]
挂载点是磁盘上存储数据的实际位置,在大多数计算机上,您需要具有root访问权限才能从主机访问此目录
在Docker Desktop中运行时,Docker命令实际上是在您机器上的一个小虚拟机中运行的,如果您想查看Mountpoint目录的实际内容,则需要首先进入VM内部
每次更改重建图像都需要相当长的时间,使用绑定挂载,这个方法更好!
绑定挂载
通过绑定挂载
- 可以控制主机上的确切挂载点
- 可以使用它来持久化数据,但它通常用于向容器提供额外的数据
- 在处理应用程序时,可以使用绑定挂载将源代码挂载到容器中,让它看到代码更改、响应,并立即看到更改
- 对于基于节点的应用程序,nodemon是一个很好的工具,可以监视文件更改,然后重新启动应用程序
在大多数其他语言和框架中都有相应的工具
快速卷类型比较
绑定装载和命名卷是Docker引擎附带的两种主要类型的卷。但是,可以使用其他卷驱动程序来支持其他用例(SFTP、Ceph、NetApp、S3等)
Named Volumes | Bind Mounts | |
---|---|---|
Host Location | Docker chooses | You control |
Mount Example (using -v ) |
my-volume:/usr/local/data | /path/to/data:/usr/local/data |
Populates new volume with container contents | Yes | No |
Supports Volume Drivers | Yes | No |
开启一个调试模式的容器
要运行容器以支持开发工作流,我们将执行以下操作:
- 将源代码装入容器
- 安装所有依赖项,包括“dev”依赖项
- 启动nodemon以监视文件系统更改
确保没有运行任何以前的getting-start容器,运行以下命令
docker run -dp 3000:3000 -w /app -v "$(pwd):/app" node:12-alpine sh -c "yarn install && yarn run dev"
如果用的是windows的powershell,那么使用以下指令
docker run -dp 3000:3000 ` -w /app -v "$(pwd):/app" ` node:12-alpine ` sh -c "yarn install && yarn run dev"
参数解释
-dp 3000:3000
- Run in detached (background) mode and create a port mapping,后台运行并创建一个端口映射-w /app
- sets the “working directory” or the current directory that the command will run from,设置工作目录或者指令运行的当前目录-v "$(pwd):/app"
- bind mount the current directory from the host in the container into the/app
directory,绑定挂载从/app目录下的容器下的主机中的当前目录node:12-alpine
- the image to use. Note that this is the base image for our app from the Dockerfilesh -c "yarn install && yarn run dev"
- the command. We’re starting a shell usingsh
(alpine doesn’t havebash
) and runningyarn install
to install all dependencies and then runningyarn run dev
. If we look in thepackage.json
, we’ll see that thedev
script is startingnodemon
.
您可以使用docker logs-f<container id>查看日志
docker logs -f <container-id> $ nodemon src/index.js [nodemon] 1.19.2 [nodemon] to restart at any time, enter `rs` [nodemon] watching dir(s): *.* [nodemon] starting `node src/index.js` Using sqlite database at /etc/todos/todo.db Listening on port 3000
停止容器并构建新镜像
docker build -t getting-started .
对于本地开发设置,使用绑定装载非常常见,其优点是开发人员机器不需要安装所有的构建工具和环境
使用一个docker run命令,将拉动开发环境并准备就绪
多容器应用
我们现在想将MySQL添加到应用程序堆栈中,下面的问题经常出现
- MySQL将在哪里运行?
- 在同一个容器中安装或单独运行?
一般来说,每个容器都应该做一件事并做好它
- 很有可能目前必须以不同于数据库的方式扩展API和前端
- 单独的容器允许独立地设置版本和更新版本
- 虽然可以在本地为数据库使用容器,但更希望在生产环境中为数据库使用托管服务,没人想把数据库引擎和自己的应用一起发布
- 运行多个进程将需要一个进程管理器(容器只启动一个进程),这增加了容器启动/关闭的复杂性
因此,我们将更新我们的应用程序以如下方式工作:
容器网络系统
在默认情况下,容器是独立运行的,不知道同一台计算机上的其他进程或容器的任何信息
如果两个容器在同一个网络上,它们可以相互通信,如果他们不是,他们就不能
启动MySQL
将容器放到网络上有两种方法
- 在开始时分配它
- 连接现有容器
现在,我们将首先创建网络,并在启动时附加MySQL容器
创建网络
docker network create todo-app
启动一个MySQL容器并将其连接到网络,定义一些数据库用来初始化数据库的环境变量
参阅MySQL Docker Hub清单中的“环境变量”部分--https://hub.docker.com/_/mysql/
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:5.7
如果使用windows的powershell
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:5.7
这里使用了一个名为todo mysql data的卷,并将其挂载在/var/lib/mysql上
mysql就是在这里存储数数据,我们从未运行docker volume create命令,Docker认识到我们想要使用一个命名卷,并自动为我们创建一个
为了确认数据库已经启动并运行,连接到数据库并校验连接
docker exec -it <mysql-container-id> mysql -u root -p
当密码提示出现时,键入密码,在MySQL shell中,列出数据库并验证您看到了todos数据库。
mysql> SHOW DATABASES;
输出可能是这个样子
+--------------------+ | Database | +--------------------+ | information_schema | | mysql | | performance_schema | | sys | | todos | +--------------------+ 5 rows in set (0.00 sec)
连接到MySQL
现在我们知道MySQL已经启动并运行了,如果在同一网络上运行另一个容器,如何找到该容器(记住每个容器都有自己的IP地址)?
为了解决这个问题,我们将使用nicolaka/netshot容器,它附带了很多工具,这些工具对于解决或调试网络问题非常有用。
使用nicolaka/netshoot映像启动新容器,确保将其连接到同一网络
docker run -it --network todo-app nicolaka/netshoot
在容器内部,我们将使用dig命令,这是一个有用的DNS工具,我们将查找主机名mysql的IP地址
dig mysql
输出应该是
; <<>> DiG 9.14.1 <<>> mysql
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 32162
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
;; QUESTION SECTION:
;mysql. IN A
;; ANSWER SECTION:
mysql. 600 IN A 172.23.0.2
;; Query time: 0 msec
;; SERVER: 127.0.0.11#53(127.0.0.11)
;; WHEN: Tue Oct 01 23:47:24 UTC 2019
;; MSG SIZE rcvd: 44
在“ANSWER”部分,您将看到mysql的A记录,解析为172.23.0.2(您的IP地址很可能有不同的值)
虽然mysql通常不是有效的主机名,但Docker能够将其解析为具有该网络别名的容器的IP地址(还记得我们前面使用的--network alias标志吗?)
这意味着应用程序只需要连接到一个名为mysql的主机,它就会与数据库通信
使用MySQL运行APP
todo应用程序支持设置一些环境变量来指定MySQL连接设置
MYSQL_HOST
- the hostname for the running MySQL serverMYSQL_USER
- the username to use for the connectionMYSQL_PASSWORD
- the password to use for the connectionMYSQL_DB
- the database to use once connected
虽然使用env vars来设置连接设置对于开发来说通常是可以的,但是在生产环境中运行应用程序时非常不鼓励这样做
更安全的机制是使用容器编排框架提供的秘密支持。在大多数情况下,这些秘密作为文件装载在正在运行的容器中
您将看到许多应用程序(包括MySQL映像和todo应用程序)也支持env vars,并带有一个∗FILE后缀来指向包含该变量的文件
例如,设置MYSQLu PASSWORDu FILE var将导致应用程序使用引用文件的内容作为连接密码
Docker没有做任何事情来支持这些环境变量,您的应用程序需要知道如何查找变量并获取文件内容
我们将指定上面的每个环境变量,并将容器连接到我们的应用程序网络
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:12-alpine
sh -c "yarn install && yarn run dev"
如果使用windows的powershell
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:12-alpine `
sh -c "yarn install && yarn run dev"
如果我们查看容器的日志(docker logs<container id>),我们应该会看到一条消息,指示它正在使用mysql数据库。
# Previous log messages omitted
$ nodemon src/index.js
[nodemon] 1.19.2
[nodemon] to restart at any time, enter `rs`
[nodemon] watching dir(s): *.*
[nodemon] starting `node src/index.js`
Connected to mysql db at host mysql
Listening on port 3000
连接mysql
docker exec -it <mysql-container-id> mysql -p todos
输出
mysql> select * from todo_items;
+--------------------------------------+--------------------+-----------+
| id | name | completed |
+--------------------------------------+--------------------+-----------+
| c906ff08-60e6-44e6-8f49-ed56a0853e85 | Do amazing things! | 0 |
| 2912a79e-8486-4bc3-a4c5-460793a575ab | Be awesome! | 0 |
+--------------------------------------+--------------------+-----------+
通过docker-compose将前面的操作合并,使得项目管理更加人性化
使用Docker Compose
Docker Compose是一个用于帮助定义和共享多容器应用程序的工具
使用Compose,我们可以创建一个YAML文件来定义服务,并且通过一个命令,可以将所有内容都拧在一起起来或扯开
使用Compose的最大优点是,您可以在文件中定义应用程序堆栈,将其保留在项目repo的根目录下(它现在是版本控制的),并且可以方便地让其他人为您的项目贡献力量
安装Docker Compose
如果您为Windows或Mac安装了Docker Desktop/Toolbox,那么您已经拥有Docker Compose!
如果您在Linux机器上,则需要安装Docker Compose:https://docs.docker.com/compose/install/
安装结束后查看docker compose的版本
docker-compose version
创建一个compose文件
在应用程序项目的根目录下,创建一个名为docker-compose.yml的文件
在compose文件中,我们将首先定义模式版本,在大多数情况下,最好使用支持的最新版本
version: "3.7"
接下来定义要作为应用程序的一部分运行的服务(或容器)列表
version: "3.7"
services:
现在可以一次性把一个服务迁移到一个compose文件中
定义应用服务
这个是之前用来定义应用容器的指令
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:12-alpine
sh -c "yarn install && yarn run dev"
如果使用windows的power shell,使用下面指令
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:12-alpine `
sh -c "yarn install && yarn run dev"
首先,为容器定义服务入口和镜像。我们可以为这项服务取任何名字。该名称将自动成为网络别名,这在定义MySQL服务时非常有用
version: "3.7"
services:
app:
image: node:12-alpine
通常会在镜像定义部分看到命令,尽管对排序没有要求
version: "3.7"
services:
app:
image: node:12-alpine
command: sh -c "yarn install && yarn run dev"
通过定义服务的ports来迁移命令的 -p3000:3000 部分
version: "3.7"
services:
app:
image: node:12-alpine
command: sh -c "yarn install && yarn run dev"
ports:
- 3000:3000
接下来,我们将使用工作目录和卷定义来迁移工作目录(-w/app)和卷映射(-v“$(pwd):/app”)
Docker Compose卷定义的一个优点是我们可以使用当前目录中的相对路径
version: "3.7"
services:
app:
image: node:12-alpine
command: sh -c "yarn install && yarn run dev"
ports:
- 3000:3000
working_dir: /app
volumes:
- ./:/app
最后,使用environment键迁移环境变量定义
version: "3.7"
services:
app:
image: node:12-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服务
之前用过的容器定义指令
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:5.7
如果用windows的power shell则使用下面指令
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:5.7
我们将首先定义新的服务并将其命名为mysql,以便它自动获取网络别名。我们将继续并指定要使用的镜像
version: "3.7"
services:
app:
# The app service definition
mysql:
image: mysql:5.7
接下来,我们将定义卷映射。当我们使用docker run运行容器时,命名卷是自动创建的
但是,使用Compose运行时不会发生这种情况,需要在顶层volumes:部分中定义卷,然后在服务配置中指定挂载点。只需提供卷名,就可以使用默认选项。不过,还有更多的选择
version: "3.7"
services:
app:
# The app service definition
mysql:
image: mysql:5.7
volumes:
- todo-mysql-data:/var/lib/mysql
volumes:
todo-mysql-data:
最后我们只需要描述环境变量
version: "3.7"
services:
app:
# The app service definition
mysql:
image: mysql:5.7
volumes:
- todo-mysql-data:/var/lib/mysql
environment:
MYSQL_ROOT_PASSWORD: secret
MYSQL_DATABASE: todos
volumes:
todo-mysql-data:
此时完整的 docker-compose.yml 文件内容应该如下所示
version: "3.7"
services:
app:
image: node:12-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:5.7
volumes:
- todo-mysql-data:/var/lib/mysql
environment:
MYSQL_ROOT_PASSWORD: secret
MYSQL_DATABASE: todos
volumes:
todo-mysql-data:
运行应用栈
保证没有其他的应用/数据库备份在之前运行
docker ps and docker rm -f <ids>
使用下面指令启动应用栈,我们需要使用 -d 标识位使得一切都在后台运行
docker-compose up -d
理论上终端的输出为
Creating network "app_default" with the default driver
Creating volume "app_todo-mysql-data" with default driver
Creating app_app_1 ... done
Creating app_mysql_1 ... done
卷和一个网络一起创建的,默认情况下,Docker Compose会自动为应用程序栈创建一个专门的网络,这就是为什么没有在Compose文件中定义一个网络
使用下面命令查看日志,将看到每个服务的日志交织到一个流中
docker compose logs-f
f标志“跟随”日志,因此将在生成时提供实时输出
mysql_1 | 2019-10-03T03:07:16.083639Z 0 [Note] mysqld: ready for connections.
mysql_1 | Version: '5.7.27' socket: '/var/run/mysqld/mysqld.sock' port: 3306 MySQL Community Server (GPL)
app_1 | Connected to mysql db at host mysql
app_1 | Listening on port 3000
服务名称显示在行的开头(通常是彩色的),以帮助区分消息。如果要查看特定服务的日志,可以将服务名称添加到logs命令的末尾,例如
docker-compose logs -f app
当应用程序启动时,它实际上会在那里等待MySQL启动并准备就绪,然后再尝试连接到它
Docker没有任何内置的支持来等待另一个容器完全启动、运行并准备就绪,然后再启动另一个容器,对于基于节点的项目,可以使用等待端口依赖关系
把服务全部停掉
当准备把服务全部停掉时,只需要运行指令
docker-compose down
所有容器都会停止而且网络会被移除
删除卷
默认情况下,运行docker compose时不会删除compose文件中的命名卷。如果要删除卷,则需要添加--volumes标志
docker-compose down --volumes
镜像构建最佳实践
构建映像后,最好使用docker scan命令对其进行安全漏洞扫描(Docker与Snyk合作提供漏洞扫描服务)
docker scan getting-started
扫描使用一个不断更新的漏洞的数据库,因此看到的输出将随着新漏洞的发现而变化,但可能如下所示:
✗ Low severity vulnerability found in freetype/freetype
Description: CVE-2020-15999
Info: https://snyk.io/vuln/SNYK-ALPINE310-FREETYPE-1019641
Introduced through: freetype/freetype@2.10.0-r0, gd/libgd@2.2.5-r2
From: freetype/freetype@2.10.0-r0
From: gd/libgd@2.2.5-r2 > freetype/freetype@2.10.0-r0
Fixed in: 2.10.0-r1
✗ Medium severity vulnerability found in libxml2/libxml2
Description: Out-of-bounds Read
Info: https://snyk.io/vuln/SNYK-ALPINE310-LIBXML2-674791
Introduced through: libxml2/libxml2@2.9.9-r3, libxslt/libxslt@1.1.33-r3, nginx-module-xslt/nginx-module-xslt@1.17.9-r1
From: libxml2/libxml2@2.9.9-r3
From: libxslt/libxslt@1.1.33-r3 > libxml2/libxml2@2.9.9-r3
From: nginx-module-xslt/nginx-module-xslt@1.17.9-r1 > libxml2/libxml2@2.9.9-r3
Fixed in: 2.9.9-r4
输出列出了漏洞的类型、要了解更多信息的URL,以及修复漏洞的相关库的版本
除了在命令行上扫描新构建的镜像外,还可以配置Docker Hub自动扫描所有新推的镜像
镜像分层
使用docker image history命令,可以看到用于在图像中创建每个层的命令
docker image history getting-started
你可以看到以下输出
IMAGE CREATED CREATED BY SIZE COMMENT
a78a40cbf866 18 seconds ago /bin/sh -c #(nop) CMD ["node" "src/index.j… 0B
f1d1808565d6 19 seconds ago /bin/sh -c yarn install --production 85.4MB
a2c054d14948 36 seconds ago /bin/sh -c #(nop) COPY dir:5dc710ad87c789593… 198kB
9577ae713121 37 seconds ago /bin/sh -c #(nop) WORKDIR /app 0B
b95baba1cfdb 13 days ago /bin/sh -c #(nop) CMD ["node"] 0B
<missing> 13 days ago /bin/sh -c #(nop) ENTRYPOINT ["docker-entry… 0B
<missing> 13 days ago /bin/sh -c #(nop) COPY file:238737301d473041… 116B
<missing> 13 days ago /bin/sh -c apk add --no-cache --virtual .bui… 5.35MB
<missing> 13 days ago /bin/sh -c #(nop) ENV YARN_VERSION=1.21.1 0B
<missing> 13 days ago /bin/sh -c addgroup -g 1000 node && addu… 74.3MB
<missing> 13 days ago /bin/sh -c #(nop) ENV NODE_VERSION=12.14.1 0B
<missing> 13 days ago /bin/sh -c #(nop) CMD ["/bin/sh"] 0B
<missing> 13 days ago /bin/sh -c #(nop) ADD file:e69d441d729412d24… 5.59MB
每一行代表图像中的一层。这里显示的是底部的底座和顶部最新的一层,使用此功能,还可以快速查看每个层的大小,帮助诊断大型图像
您会注意到有几行被截断了,如果添加--no trunc标志,您将得到完整的输出
docker image history --no-trunc getting-started
层缓存
已经看到了分层的实际效果,那么有一个重要的前车之鉴可以帮助减少容器映像的构建时间
一旦图层发生变化,所有下游图层也必须重新创建
再看一次Dockerfile
# syntax=docker/dockerfile:1 FROM node:12-alpine WORKDIR /app COPY . . RUN yarn install --production CMD ["node", "src/index.js"]
回到图像历史输出,我们看到Dockerfile中的每个命令都成为图像中的一个新层。
您可能还记得,当我们对映像进行更改时,必须重新安装依赖项,每次构建时都围绕相同的yarn依赖项进行发布是没有多大意义
要解决这个问题,我们需要重新构造Dockerfile,以帮助支持依赖项的缓存
对于基于节点的应用程序,这些依赖项在package.json文件中定义。所以,如果我们先只复制那个文件,安装依赖项,然后再复制其他所有内容
最后我们只在package.json发生更改时重新创建yarn依赖关系即可
首先更新Dockerfile以在package.json中复制,安装依赖项,然后复制包中的所有其他内容。
# syntax=docker/dockerfile:1 FROM node:12-alpine WORKDIR /app COPY package.json yarn.lock ./ RUN yarn install --production COPY . . CMD ["node", "src/index.js"]
在Dockerfile所在的文件夹中创建一个名为.dockerginore的文件,该文件包含以下内容
node_modules
.dockerginore文件是一种仅选择性地复制图像相关文件的简单方法,在这种情况下,在第二个复制步骤中应该省略nodeu modules文件夹
因为否则,它可能会覆盖由RUN步骤中的命令创建的文件,有关为什么建议对Node.js应用程序和其他最佳实践使用此方法的更多详细信息,请参阅他们的Node.js web应用程序停靠指南
使用docker build指令创建一个新镜像
docker build -t getting-started .
输出如下
Sending build context to Docker daemon 219.1kB Step 1/6 : FROM node:12-alpine ---> b0dc3a5e5e9e Step 2/6 : WORKDIR /app ---> Using cache ---> 9577ae713121 Step 3/6 : COPY package.json yarn.lock ./ ---> bd5306f49fc8 Step 4/6 : RUN yarn install --production ---> Running in d53a06c9e4c2 yarn install v1.17.3 [1/4] Resolving packages... [2/4] Fetching packages... info fsevents@1.2.9: The platform "linux" is incompatible with this module. info "fsevents@1.2.9" is an optional dependency and failed compatibility check. Excluding it from installation. [3/4] Linking dependencies... [4/4] Building fresh packages... Done in 10.89s. Removing intermediate container d53a06c9e4c2 ---> 4e68fbc2d704 Step 5/6 : COPY . . ---> a239a11f68d8 Step 6/6 : CMD ["node", "src/index.js"] ---> Running in 49999f68df8f Removing intermediate container 49999f68df8f ---> e709c03bc597 Successfully built e709c03bc597 Successfully tagged getting-started:latest
现在在前端html文件随意更改一处,重新执行一下指令
docker build -t getting-started .
现在输出如下
Sending build context to Docker daemon 219.1kB Step 1/6 : FROM node:12-alpine ---> b0dc3a5e5e9e Step 2/6 : WORKDIR /app ---> Using cache ---> 9577ae713121 Step 3/6 : COPY package.json yarn.lock ./ ---> Using cache ---> bd5306f49fc8 Step 4/6 : RUN yarn install --production ---> Using cache ---> 4e68fbc2d704 Step 5/6 : COPY . . ---> cccde25a3d9a Step 6/6 : CMD ["node", "src/index.js"] ---> Running in 2be75662c150 Removing intermediate container 2be75662c150 ---> 458e5c6f080c Successfully built 458e5c6f080c Successfully tagged getting-started:latest
注意看1到4步,using cache,已经使用了缓存
多阶段构建优点
- 将生成时依赖项与运行时依赖项分开
- 通过只提供应用程序运行所需的内容来减小整体图像大小
Maven/Tomcat实例
在构建基于Java的应用程序时,需要使用JDK将源代码编译成Java字节码
然而,JDK在生产中是不需要的,此外,可能正在使用Maven或Gradle等工具来帮助构建应用程序,在我们的最终镜像中也不需要这些
# syntax=docker/dockerfile:1 FROM maven AS build WORKDIR /app COPY . . RUN mvn package FROM tomcat COPY --from=build /app/target/file.war /usr/local/tomcat/webapps
- 在本例中,我们使用一个阶段(称为build)来使用Maven执行实际的Java构建
- 在第二阶段(从tomcat开始),我们从构建阶段复制文件
- 最后一个映像只是创建的最后一个阶段(可以使用--target标志覆盖)
React 实例
在构建React应用程序时,我们需要一个节点环境来将JS代码(通常是JSX)、SASS样式表等编译成静态HTML、JS和CSS
如果我们不做服务器端渲染,我们甚至不需要为我们的产品构建节点环境。为什么不把静态资源放在一个静态nginx容器中呢
# syntax=docker/dockerfile:1 FROM node:12 AS build WORKDIR /app COPY package* yarn.lock ./ RUN yarn install COPY public ./public COPY src ./src RUN yarn run build FROM nginx:alpine COPY --from=build /app/build /usr/share/nginx/html