Loading

Docker入门之官方Getting-started

官方示例

官方地址: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 VolumesBind 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 Dockerfile
  • sh -c "yarn install && yarn run dev" - the command. We’re starting a shell using sh(alpine doesn’t have bash) and running yarn install to install all dependencies and then running yarn run dev. If we look in the package.json, we’ll see that the dev script is starting nodemon.

您可以使用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 server
  • MYSQL_USER - the username to use for the connection
  • MYSQL_PASSWORD - the password to use for the connection
  • MYSQL_DB - the database to use once connected

虽然使用env vars来设置连接设置对于开发来说通常是可以的,但是在生产环境中运行应用程序时非常不鼓励这样做

更安全的机制是使用容器编排框架提供的秘密支持。在大多数情况下,这些秘密作为文件装载在正在运行的容器中

您将看到许多应用程序(包括MySQL映像和todo应用程序)也支持env vars,并带有一个∗FILE后缀来指向包含该变量的文件

例如,设置MYSQL\u PASSWORD\u 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文件是一种仅选择性地复制图像相关文件的简单方法,在这种情况下,在第二个复制步骤中应该省略node\u 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

 

posted @ 2021-06-06 23:55  BigBender  阅读(1599)  评论(0编辑  收藏  举报