其他分享
首页 > 其他分享> > 云计算:Docker 学习

云计算:Docker 学习

作者:互联网

运行容器

docker run -d -p 80:80 docker/getting-started
docker run -dp 80:80 docker/getting-started

-d - 以分离模式(在后台)运行容器

-p 80:80 - 将主机的80端口映射到容器的80端口

docker/getting-started - 运行的镜像名称

部署项目

下载项目

下载一个demo:https://github.com/docker/getting-started/tree/master/app

Dockerfile

将 app 复制到 linux 里

在文件 package.json 所在的文件夹中创建一个名为 Dockerfile 的文件

# syntax=docker/dockerfile:1
FROM node:12-alpine
RUN apk add --no-cache python2 g++ make
WORKDIR /app
COPY . .
RUN yarn install --production
CMD ["node", "src/index.js"] # 指令指定从此图像启动容器时运行的默认命令
EXPOSE 3000

创建镜像

打开一个终端,并进入 Dockerfile 所在的目录。使用 docker build 命令构建容器映像。

docker build -t getting-started .

-t - 标记镜像,可以看成是镜像最终的名称。由于我们将这个映像命名为 getting-started,因此在运行容器时可以引用这个映像。

. - 在 Docker build 命令的末尾告诉 Docker 应该在当前工作目录文件中寻找 Dockerfile

启动

docker run -dp 3000:3000 getting-started

在浏览器中访问:http://[host ip]:3000

更新应用程序

我们希望将 web 页面中的字符串改成 You have no todo items yet! Add one above!

对 src/static/js/app.js 文件中的第56行进行修改

 -                <p className="text-center">No items yet! Add one above!</p>
 +                <p className="text-center">You have no todo items yet! Add one above!</p>

使用之前的命令更新版本并启动

docker build -t getting-started .
docker run -dp 3000:3000 getting-started

此时我们需要移除旧的容器才能成功启动

docker ps
docker stop <container-id>
docker rm <container-id>
docker rm -f <container-id> # 强制移除正在运行的容器

现在,开始更新你的应用程序

docker run -dp 3000:3000 getting-started

在浏览器中访问:http://[host ip]:3000

共享 Docker 镜像

注册或登录到 Docker Hub,单击 Create Repository 按钮,对于仓库的名称,使用 getting-started。确保 Visibility 为 Public。点击创建按钮!

完成之后你可以看到一个示例 Docker 命令。这个命令将推送这个仓库。

此时我们需要为我们已经构建的现有图像“添加标签”,以便给它另一个名称

使用命令登录到 Docker Hub

docker login -u <用户名>

使用 docker 标记命令为刚开始使用的图像命名

docker tag getting-started <用户名>/getting-started

现在再试一次 push 命令。如果要从 dockerhub 复制值,可以删除 tagname 部分,因为我们没有向图像名称添加标记。如果不指定标记,Docker 将使用名为 latest 的标记。

docker push <用户名>/getting-started

在新实例上运行镜像

打开浏览器登录:https://labs.play-with-docker.com/

登录后,单击左侧栏上的 ADD NEW INSTANCE 选项

在终端中,启动刚刚推出的应用程序

docker run -dp 3000:3000 <用户名>/getting-started

此时,点击 Open Port 按钮,并输入 3000 端口号

命名卷

如何在重新启动时持久化数据?

卷提供了将容器的特定文件系统路径连接回主机的能力。如果挂载了容器中的目录,那么在主机上也可以看到该目录中的更改。如果我们在容器重新启动时挂载相同的目录,我们会看到相同的文件。

由于数据库是一个单独的文件,我们可以将该文件持久化到主机上,并使其可用于下一个容器。

通过创建一个卷并将其附加到存储数据的目录(通常称为“挂载”)。

创建一个名为 todo-db 的命名卷

docker volume create todo-db

停止并删除 todo app 容器

docker stop <container-id>
docker rm <container-id>
docker rm -f <container-id> # 强制移除

启动 todo 应用程序容器时添加-v 标志来指定卷挂载。

我们将使用指定的卷并将其挂载到/etc/todos,它将捕获在路径中创建的所有文件。

docker run -dp 3000:3000 -v todo-db:/etc/todos <用户名>/getting-started

在这个启动的容器开启后,往 todo 里添加数据。当删除这个容器再创建一个新的容器时,之前添加的数据就被持久化下来了。

可以查看卷的详细内容:

docker volume inspect todo-db

其中的 Mountpoint 就是数据存储在磁盘上的实际位置,默认存储在 /var/lib/docker/volumes/todo-db/_data

如果我们只是想存储数据,命名卷是很好的,因为我们不必担心数据存储在哪里。

绑定挂载

使用绑定挂载,我们可以控制主机上的精确挂载点。我们可以使用它来持久化数据,但是它通常用于向容器中提供额外的数据。

在处理应用程序时,我们可以使用绑定挂载将源代码挂载到容器中,让容器查看代码更改、响应,并让我们立即查看更改。

绑定挂载命名卷 是 Docker 引擎附带的两种主要卷类型。

命名卷 绑定挂载
Host Location Docker chooses You control
Mount Example (using -v) my-volume:/usr/local/data /path/to/data:/usr/local/data
用容器内容填充新卷 Yes No
Supports Volume Drivers Yes No

确保您以前没有运行任何启动容器。在 app 目录中运行下面的命令

docker run -dp 3000:3000 \
     -w /app -v "$(pwd):/app" \
     node:12-alpine \
     sh -c "yarn install && yarn run dev"

-dp 3000:3000 - 在分离(背景)模式下运行并创建一个端口映射

-w /app - 设定「工作目录」或指令运行的工作目录

-v "$(pwd):/app" - 将当前工作目录挂载到容器里面的/app目录

node:12-alpine - 要使用的镜像及版本

sh -c "yarn install && yarn run dev" - 用 sh -c 开启一个 shell 执行字符串中的内容

可以通过日志来查看内部的执行信息,查看完日志后,按 Ctrl+C 退出

docker logs -f <container-id>

现在我们修改 src/static/js/app.js 中的第109行的内容

 -                         {submitting ? 'Adding...' : 'Add Item'}
 +                         {submitting ? 'Adding...' : 'Add'}

只需刷新页面(或打开页面),你就会立即看到浏览器中按钮的变化。

当你完成后,就可以停止容器,用下面的方法创建你的新镜像

docker build -t getting-started .

对于本地开发设置,使用绑定挂载是非常常见的。优点是开发机器不需要安装所有的构建工具和环境。使用一个 docker 运行命令,就可以拉动开发环境并准备运行。

多容器应用

当需要在应用中增加MySQL时,为了满足一个容器只做一件事的原则,我们需要将数据库容器和应用容器进行联网。

在网络上放置容器有两种方法: 1)在开始时分配它; 2)连接现有的容器。

现在,我们将首先创建网络并在启动时附加 MySQL 容器。

创建网络

docker network create todo-app

启动一个 MySQL 容器并将其附加到网络上。我们还将定义一些环境变量,数据库将使用这些环境变量来初始化数据库

docker run -d \
     --network todo-app --network-alias mysql \
     -v todo-mysql-data:/var/lib/mysql \
     -e MYSQL_ROOT_PASSWORD=123456 \
     -e MYSQL_DATABASE=todos \
     mysql:5.7

要确认数据库已经建立并运行,请连接到数据库并验证其连接。

docker exec -it <mysql-container-id> mysql -u root -p

连接到 MySQL

我们将使用 nicolaka/netshoot 容器用来找到容器(每个容器都有自己的 IP 地址)

使用 nicolaka/netshoot 镜像启动一个新容器,确保它连接到同一个网络。

docker run -it --network todo-app nicolaka/netshoot

在容器内部,我们将使用 dig 命令,这是一个有用的 DNS 工具。我们将查找主机名 mysql 的 IP 地址。

dig mysql

ANSWER SECTION 中,您将看到 mysql 的 a 记录解析到 172.23.0.2 (您的 IP 地址很可能具有不同的值)。

虽然 mysql 通常不是一个有效的主机名,但 Docker 可以将其解析为具有该网络别名的容器的 IP 地址(还记得我们前面使用的 -- network-alias 标志吗?)。这意味着我们的应用程序只需要连接到一个名为 mysql 的主机,它就会与数据库通信!

用 MySQL 运行你的应用程序

这个 todo app 支持设置一些环境变量来指定 MySQL 连接设置,它们是:

MySQL 8.0及以上版本,请确保提前运行了以下命令

ALTER USER 'root' IDENTIFIED WITH mysql_native_password BY '123456';
flush privileges;

我们将指定上面的每个环境变量,并将容器连接到我们的应用程序网络

docker run -dp 3000:3000 \
   -w /app -v "$(pwd):/app" \
   --network todo-app \
   -e MYSQL_HOST=mysql \
   -e MYSQL_USER=root \
   -e MYSQL_PASSWORD=123456 \
   -e MYSQL_DB=todos \
   node:12-alpine \
   sh -c "yarn install && yarn run dev"

此时查看容器的日志,可以看到它正在使用 mysql 数据库

docker logs <container-id>

现在,打开浏览器在应用程序中添加内容

连接容器中的 todos 数据库

docker exec -it <mysql-container-id> mysql -p todos

查看数据表中的数据

select * from todo_items;

Docker Compose

创建 compose 文件

在 app 项目的根目录下,创建一个名为 docker-compose.yml 的文件。

在 compose 文件中,我们将从定义模式版本开始。在大多数情况下,最好使用最新支持的版本。

接下来,我们将定义要作为应用程序的一部分运行的服务(或容器)列表。

现在,我们开始每次将一个服务转移到 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"
  1. 首先,定义容器的入口和容器的镜像。

    我们可以为服务取任何名称(app)。该名称将自动成为网络别名,这在定义 MySQL 服务时非常有用。

  2. 通常,你会看到 image 下面有个 command,尽管不要求排序。

  3. 让我们通过 -p 3000:3000 部分来定义服务的端口。

  4. 接下来,我们将使用 working_dir 和卷定义,来传递工作目录(-w /app)和卷映射(-v "$(pwd):/app")。

    Docker Compose 卷的优点是我们可以是使用当前目录的相对路径。

  5. 最后,我们需要使用 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=123456 \
  -e MYSQL_DATABASE=todos \
  mysql:5.7
  1. 我们将首先定义新服务,并将其命名为 mysql,以便它自动获取网络别名。我们将继续并指定要使用的镜像。

  2. 接下来,我们将定义卷映射。

    当用 docker 运行容器时,将自动创建指定的卷。但是,在使用 Compose 运行时就不会发生这种情况。

    我们需要在 top-level volumes 中定义卷,然后在服务配置中指定挂载点。

    只需提供卷名,就可以使用默认选项。不过还有更多的选择。

  3. 最后,我们只需要指定环境变量。

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: 123456
      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: 123456
      MYSQL_DB: todos

  mysql:
    image: mysql:5.7
    volumes:
      - todo-mysql-data:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: 123456
      MYSQL_DATABASE: todos

volumes:
  todo-mysql-data:

运行应用程序堆栈

确保没有其他的 app/db 副本首先运行

docker ps
docker rm -f $(docker ps -aq)

启动应用程序堆栈。我们将添加 -d 标志来运行后台的所有内容。

docker-compose up -d

我们可以看到卷像网络一样被创建。

默认情况下,Docker Compose 会自动为应用程序堆栈创建一个网络(这就是为什么我们没有在 Compose 文件中定义一个网络)。

我们可以使用 docker-compose logs -f 来查看日志,-f 标志“跟随”日志,因此将在生成时给出实时输出。

服务名显示在行首(通常是彩色的),以帮助区分消息。

如果要查看特定服务的日志,可以将服务名添加到 logs 命令的末尾(例如,docker-compose logs -f app)。

此刻,你应该能够打开你的应用程序,看到它运行。

如果我们看一下 Docker Dashboard,我们会看到一个名为 app 的组。这是 dockercompose 中的“项目名称”,用于将容器组合在一起。

默认情况下,项目名称只是 docker-compose.yml 所在目录的名称。

当你准备完全删除它时,只需运行 docker-compose down

默认情况下,在文件中的已命名卷不会被删除。如果要删除卷,则需要添加 --volume 标志。

安全扫描

当您已经构建了一个镜像时,最好使用 docker scan 命令对其进行扫描以发现安全漏洞。

必须登录到 dockerhub 才能扫描镜像。

运行命令 docker scan --login,然后使用 docker scan < image-name > 扫描您的镜像。例如:

docker scan getting-started

扫描使用了一个不断更新的漏洞数据库,因此当发现新的漏洞时,你看到的输出会有所不同。

输出列出了漏洞的类型、要了解的 URL,以及重要的是相关库的哪个版本修复了漏洞。

还有其他一些选项,您可以在 docker 扫描文档中了解。

除了在命令行上扫描新建的镜像,你还可以配置 Docker Hub 来自动扫描所有新推出的镜像,然后你可以在 Docker Hub 和 Docker Desktop 中看到结果。

镜像分层

你知道是什么构成了一个镜像吗?

使用 docker image history 命令,您可以看到用于在镜像中创建每个分层的命令。

查看教程前面创建的入门镜像中的分层。

docker image history getting-started

您将注意到有几行被截断了。如果添加 --no-trunc 标志,就会得到完整的输出

docker image history --no-trunc getting-started

分层缓存

现在您已经看到了分层的实际应用,接下来有一个重要的经验教训可以帮助您减少容器映像的构建时间。

一旦一个层发生变化,所有下游层也必须重新创建

删除所有之前的镜像

docker rmi -f $(docker images -aq)

让我们再次看看我们使用过的 Dockerfile

# syntax=docker/dockerfile:1
FROM node:12-alpine
WORKDIR /app
COPY . .
RUN yarn install --production
CMD ["node", "src/index.js"]

回到图像历史输出,我们可以看到 Dockerfile 中的每个命令都成为图像中的一个新层。

您可能还记得,当我们对镜像进行更改时,必须重新安装 yarn 依赖项。

有办法解决这个问题吗?每次构建时都围绕着相同的依赖关系发布并没有多大意义,对吧?

为了解决这个问题,我们需要重新构造 Dockerfile 来支持依赖项的缓存。

对于基于 node 的应用程序,这些依赖项在 package.json 文件中定义。

那么,如果我们首先只复制这个文件,安装依赖项,然后再将其他东西拷贝进去,怎么样?

然后,只有在 package.json 发生更改时,我们才重新创建 yarn 的依赖关系。有道理?

  1. 首先更新 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"]
  1. 在与 Dockerfile 相同的文件夹中创建一个名为 .dockerignore 的文件,其内容如下。
node_modules

.文件 是一个简单的方法,选择性地复制图像相关的文件。

在这种情况下,应该在第二个 COPY 步骤中省略 node_modules 文件夹,因为否则,它可能会覆盖 RUN 步骤中命令创建的文件。

为了进一步了解为什么在 Node.js 应用程序和其他最佳实践中推荐使用这种方法,请看他们关于对 Node.js web 应用程序进行 Dockerizing 的指南。

  1. 使用 docker Build 构建一个新的镜像。
docker build -t getting-started .

你会看到所有的图层都被重建了,非常好,因为我们对 Dockerfile 做了很大的改动。

现在,对 src/static/index.html 文件进行修改(比如将 <title> 更改为 The Awesome Todo App)。

现在使用 docker build -t getting-started . 再次构建 Docker 镜像。

这一次,您的输出应该看起来有些不同。

首先,您应该注意到构建速度要快得多!

并且,您将看到步骤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 

在本例中,我们使用一个阶段(称为构建)来使用 Maven 执行实际的 Java 构建。

在第二阶段(从 tomcat 开始),我们从构建阶段复制文件。

最终镜像只创建的是最后一个阶段(可以使用 --target 标志覆盖该阶段)。

React 示例

在构建 React 应用程序时,我们需要一个 Node 环境来编译 JS 代码(通常是 JSX)、SASS 样式表,以及更多的静态资源(HTML、JS 和 CSS)。

如果我们不进行服务器端渲染,那么我们甚至不需要生产构建所需的 Node 环境。

为什么不将静态资源迁移到静态 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

这里,我们使用一个 node:12 镜像来执行构建(最大化分层缓存),然后将输出复制到 nginx 容器中。

What next?

虽然我们已经完成了我们的工作坊,但还有很多关于容器的东西要学!

我们不打算在这里深入探讨,但这里有一些其他领域,看看下一步!

容器编排

运行的容器在生产环境中是不能变动的。

您不希望登录到一台机器并简单地运行 docker rundocker-compose up,为什么呢?

是的,如果容器死了怎么办?你如何在多台机器之间进行扩展?

容器编排解决了这个问题。像 Kubernetes, Swarm, Nomad 和 ECS 这样的工具都以略微不同的方式帮助解决了这个问题。

一个普遍的想法是拥有一个可以接收预期状态的“管理员”。

这种状态可能是“我想运行我的 web 应用程序的两个实例并公开端口80”,然后管理员查看集群中的所有机器,并将工作委托给“工作者”节点。

管理人员观察变化(例如容器退出),然后用 实际状态 反来映 预期状态

标签:容器,app,MYSQL,学习,3000,计算,mysql,Docker,docker
来源: https://www.cnblogs.com/toki-plus/p/16120338.html