如何在 devops 生命周期的开发阶段使用 docker?

How to use docker in the development phase of a devops life cycle?

我有几个关于在开发阶段使用 Docker 的问题。

我将提出三种不同的方案,说明我认为 Docker 可以在开发环境中使用的方式。假设我们正在 Java 和 Spring Boot 中创建一个 REST API。为此,我需要一个 MySQL 数据库。

  1. 第一种情况是 docker-compose 用于开发 MySQL 容器和生产 docker-compose 与 MySQL和另一个容器中的 Java 应用程序 (jar)。为了开发,我启动 docker-compose-dev.yml 以仅启动数据库。使用 IDE 启动和调试应用程序,例如 IntelliJ Idea。对代码所做的任何更改,IDE 将通过应用更改来识别并重新启动应用程序。

  2. 第二种情况是,对于开发和生产环境,docker-与数据库和应用程序容器组合。这样,每次我对代码进行更改时,我都必须重建图像,以便将更改加载到图像中并再次启动容器。这个场景可能是最典型的,用在Docker的开发中,但是每次有变化都要重建镜像,所以显得很慢。

  3. 第三种情况是前两种情况的混合。两个docker-组成。开发 docker-compose 包含两个容器,但具有允许实时重新加载应用程序、映射卷和使用 Spring 开发工具等机制。通过这种方式,容器被启动,如果文件发生任何变化,应用程序容器将检测到有变化并重新启动。对于生产,docker-compose 将简单地使用两个容器创建,但没有实时重新加载的功能。在我看来,这将是理想的场景,但我认为这在很大程度上取决于所使用的技术,因为并非所有技术都允许实时重新加载。

题目如下

我提出情景2存在的问题后,我提出的疑惑和情景就出现了。每次更改代码时,都必须重新构建映像并重新启动容器,这是对时间的极大浪费。简而言之,一个问题是:如何避免这种情况?

提前感谢您的宝贵时间。

注意: 这可能是一个有见地的问题,但如果知道开发人员通常如何处理这些问题就更好了。

免责声明:这是我对火星先生提出的问题的个人看法。尽管我已尽力用实际来源来支持我的回答,但这主要是基于我自己的经验和一些常识

Which of these scenarios is the most typical when using Docker for development?

我已经在几个项目中看到了所有 3 种情况,每种情况都有其优点和缺点。但是,我认为场景 3 Docker Compose 允许动态代码重新加载在灵活性和一致性方面是最有利的:

  • Dev 和 Prod Docker Compose 是紧密匹配的,意味着 Dev 环境尽可能接近 Prod 环境
  • 开发时不必不断重建镜像,但需要时也很容易
  • 很多技术都支持这种场景,比如你提到的 Spring Dev Tools,还有 Python Flask 等
  • 您可以轻松利用 Docker Compose extends a.k.a configuration sharing mechanism(方案 2 也可能)

Is scenario 1 well raised? That is, dockerize only external services, such as databases, queues, etc. and perform the development and debugging of the application with the IDE without using Docker for it.

场景 1 很常见,但是 IDE 环境可能与 Docker 容器中的环境不同(并且很难为每个库维护版本匹配,从 IDE 环境到 Docker 环境的依赖关系等)。它还可能需要通过开发和生产之间的中间步骤来实际测试在开发工作之后构建的 Docker 图像,然后再进入生产。

根据我自己的经验,当您不想在实际进行开发时处理太多 Docker 时,这样做非常好 and/or 您使用的语言或技术不适用于动态重新加载,因为在场景 3 中进行了描述。但最终它只会增加您的环境之间的差异,并增加 Dev 和 Prod 部署方法之间的复杂性。

having to rebuild the image and start the containers again is a significant waste of time. In short, a question would be: How to avoid this?

除了您描述的场景之外,您还可以通过利用 Docker build cache and designing your Dockerfile 适当地(甚至大幅地)减少图像构建时间。例如,Python 应用程序通常会将代码复制为构建的最后(或几乎最后)步骤,以避免使缓存无效,而对于 Java 应用程序,可以拆分代码以便避免每次更改一点代码时都编译整个应用程序——这取决于您的实际设置。


我个人使用大致符合场景3的工作流程如:

  • 一个docker-compose.yml文件对应我的Production环境
  • a docker-compose.dev.yml 将覆盖我的主要 Docker Compose 文件的某些方面,例如来自我的机器的 mouting 代码,向命令添加开发特定标志等 - 它会是 运行 比如
    docker-compose -f docker-compose.yml -f docker-compose.dev.yml 
    
    但也可以将 docker-compose.override.yml 作为 Docker Compose uses by default for override
  • 在某些情况下,我必须针对特定情况使用其他覆盖,例如 CI 上的 docker-compose.ci.yml,但通常主 Docker Compose 文件足以描述我的 Prod环境(如果不是这种情况,docker-compose.prod.yml 就可以了)

我正在为我的网络开发人员使用类似于您的第 3 个场景的东西,但它是基于节点的。所以我有 3 个 docker-compose 文件(实际上是 4 个,一个是基础文件和 having all common stuff for others)用于开发、暂存和生产环境。

暂存 docker-compose 配置类似于生产配置,不包括 SSL、端口和其他可能不允许在本地使用的配置。

我为每个服务(如数据库、队列)都有一个单独的容器,对于开发,我还有额外的开发数据库和队列容器,主要用于 运行ning 自动测试。在开发环境中,所有源都安装到容器中,因此它允许在容器外使用 IDE/editor 选择,并在容器内查看变化。

我使用 supervisor 来管理容器内的工作人员,并有一些命令可以在我需要时手动重启我的工作人员。也许您可以拥有类似于 recompile/restart 您的 Java 应用程序的东西。或者,如果您知道如何组织应用程序源代码更改检测和应用程序自动重新加载,那么这可能是最好的变体。顺便说一句,你给了我一个想法来研究适合我的情况的类似东西。

对于暂存和生产环境,我的源代码使用生产 Docker 文件包含在相应的容器中。我有一些命令可以使用我需要的环境重新启动所有东西,这通常包括重建容器,但由于 Docker 缓存,它不会花费太多时间(大约 20 秒)。并且考虑到在环境之间切换不是太频繁的操作,我对此感到很满意。

生产 docker-compose 配置仅在部署期间使用,因为它启用 SSL、适当的端口并具有一些额外的生产内容。

有关使用 Supervisor 重新启动后端应用程序的详细信息的更新:

这就是我在我的项目中使用它的方式:

我的 Docker 安装 Supervisor 的文件的一部分:

FROM node:10.15.2-stretch-slim

RUN apt-get update && apt-get install -y \
  # Supervisor
    supervisor \
    ...

...

# Configs for services/workers managed by supervisor
COPY some/path/worker-configs/*.conf /etc/supervisor/conf.d/

这是工人主管配置之一的示例:

[program:myWorkerName]
command=/usr/local/bin/node /app/workers/my-worker.js
user=root
numprocs=1
stopsignal=INT
autostart=true
autorestart=true
startretries=10

在此示例中,您的情况 command 应该 运行 您的 Java 应用程序。

这是一个命令别名示例,可以方便地从容器外部管理 Supervisor。我将 Makefile 用作所有命令的通用 运行ner,但这可以是其他东西。

# Used to run all workers
su-start:
    @docker exec -t MY-WORKERS-CONTAINER-NAME supervisorctl start all

# Used to stop all workers
su-stop:
    @docker exec -t MY-WORKERS-CONTAINER-NAME supervisorctl stop all

# Used to restart all workers
su-restart:
    @docker exec -t MY-WORKERS-CONTAINER-NAME supervisorctl restart all

# Used to check status of all workers
su-status:
    @docker exec -t MY-WORKERS-CONTAINER-NAME supervisorctl status

正如我上面所描述的,这些 Supervisor 命令需要手动 运行,但我认为有可能实现另一个基于 Node 的 worker 或容器外的一些 watcher,其中的 worker 将检测文件系统自动更改源目录和 运行 这些命令。我认为使用 Java 和 like this or this 也可以实现类似的功能。

另一方面,需要小心完成,以避免每次微小的变化都不断重启 worker。

我见过它们都用于不同的场景。有一些陷阱需要避免:

  1. 容器内的应用程序不应该依赖于主机上容器外的东西 运行ning。所以你所有的依赖都应该首先容器化。

  2. 主机卷的文件权限可能很复杂,具体取决于您的 docker 版本。一些较新的 Docker 桌面安装会自动处理 uid 映射,但如果您直接在 Linux 上开发,则需要确保容器 运行 与您的主机用户具有相同的 uid。

  3. 如果容器未映射到主机卷,请避免在容器内部进行更改,因为这些更改将在重新创建容器时丢失。

查看每个选项,这是我对每个选项的评估:

  1. 仅对数据库进行容器化: 当开发人员已经拥有所选语言的开发环境并且不存在外部依赖项蔓延的风险时,这很有效,例如开发人员将他们的 JDK 安装升级到比构建映像所使用的版本更新的版本。它遵循首先将依赖项容器化的想法,同时还为开发人员提供熟悉的 IDE 与其应用程序的集成。

  2. 为每次更改重建图像:这对于开发人员工作流程来说往往是最不理想的,但是当您不熟悉工具。我会给出第四个选项,我认为可以对此进行改进。

  3. 一切都在容器里,卷挂载,实时重载:这个实现起来最复杂,需要语言本身支持live之类的东西重新加载。但是,当他们这样做时,对于开发人员来说几乎是无缝的,并且可以让他们快速上手新项目,而无需安装任何其他工具即可开始。

  4. 用卷挂载在容器中重建应用程序: 这是 2 和 3 之间的中间点。当你没有实时重新加载时,你可能需要重新编译或重新启动解释器才能看到任何更改。我没有重建图像,而是将重新编译步骤放在开发图像的入口点。我会将代码装载到容器中,运行 一个完整的 JDK 而不仅仅是一个 JRE(或任何需要的编译器)。我为任何依赖项缓存使用命名卷,因此它们不需要在每次重新启动时下载。然后查看更改的方法是重新启动那个容器。这些步骤与在容器外部编译二进制文件相同,停止旧服务,重新编译并重新启动服务,但现在它发生在容器内部,该容器应该具有构建生产映像时使用的相同工具。

对于选项 4,我倾向于使用包含构建、开发和发布阶段的多阶段构建。构建阶段引入代码并编译它,开发阶段是与构建相同的基础映像,但具有执行 compile/run 的入口点,发布阶段将构建阶段的结果复制到最小 运行时间。然后,开发人员有一个用于开发的组合文件,用于创建开发映像和 运行 卷安装和任何调试端口打开的文件。

首先,docker-compose 仅​​用于开发和测试阶段,不用于生产。示例:

通过最基本的 docker-compose,您的所有容器都将 运行 在同一台机器上?出于开发目的,没问题,但在生产中,将所有应用程序放在一台机器上是有风险的

我们假设

  • 01 java api
  • 01 mysql 数据库
  • 01 需要 api
  • 的 Web 应用程序
  • 所有这些应用程序都已投入生产

快速回答

如果您需要修复或向 java api 添加新功能,我建议您使用 ide,如 eclipse 或 IntelliJ Idea。为什么?

  • 因为java需要编译。
  • 编译 inside 一个 docker 容器由于 maven 依赖关系将花费更多时间
  • IDE 有代码自动完成
  • 等等

在此开发阶段,Docker 可帮助您使用其最强大的功能之一:“将生产容器带到您的本地主机”。是的,在这种情况下,docker-compose.yml 是最佳选择,因为使用一个文件,您可以启动所需的一切:mysql 数据库和网络应用程序,但不是您的 java api。用你最喜欢的ide打开你的javaapi。

无论如何,如果您想使用 docker 进行“开发”,您只需要 Docker 文件并执行 docker 构建 ... 当您需要 运行 您本地主机中的源代码

具有 docker

的基本 Devops 生命周期
  • 开发人员使用 git
  • 推送源代码更改
  • 您的持续集成 (C.I) 平台检测到此更改并执行
    • docker build ...(这一步会触发单元测试)
    • docker 推送到您的专用中心。容器在此步骤中上传,将用于部署到其他服务器上。
    • docker 运行 或者容器部署到下一个环境:testing
  • 人类测试人员、硒或其他自动化开始他们的工作
  • 如果未检测到任何错误,您的 C.I 将对上传的容器执行最终部署到您的生产环境。不需要 docker 构建,只需部署或 docker 运行.

一些小技巧

Docker 功能很棒,但有时会增加太多的复杂性。所以停止使用卷、硬盘依赖、日志或复杂的配置。如果你使用卷,当你的容器在不同主机时会发生什么?

Java 和 Nodejs 是一种稳定的语言,您的其他 api 或网络应用程序不需要疯狂的配置。只需 maven 编译和 java -jar ... 或 npm install 和 npm 运行 start.

对于日志,您可以使用 https://www.graylog.org/, google stasckdriver 或其他日志管理。

和Heroku一样,尽可能停止使用硬盘依赖。在 heroku 平台磁盘是一次性的,这意味着当应用程序重新启动时消失。因此,您可以使用另一种具有很多功能的文件存储服务来代替本地文件存储。

通过这种方法,您的容器可以以简单的方式部署到任何地方