当前位置: 首页 > 科技观察

编写Docker Compose时要注意的五大常见错误

时间:2023-03-20 17:41:34 科技观察

编写DockerCompose时要注意的5个常见错误虽然业界有很多方法可以做到这一点,但DockerCompose是迄今为止最受欢迎的一种。它可以很容易地:指定需要在开发过程中启动的容器。设置一组快速的代码测试调试以促进开发循环。通常,团队会提前编写一个docker-compose.yml文件,指定开发所需的一切,并将其提交到存储库。然后,每个开发人员都可以简单地运行docker-composeup来启动他们测试代码所需的所有容器。但是,让docker-compose设置发挥最佳性能,例如在不到一分钟内启动开发环境并在几秒钟内测试每个更改需要团队花费大量时间工作。在这些准备过程中,由于每个开发人员每天花费不同的时间来测试他们的代码,任何小的更改都可能对整个开发团队的生产力产生巨大影响。因此,我们有必要讨论一下他们在编写DockerCompose时常犯的五个错误,以及相应的解决方法。错误1:频繁的容器重建Docker构建通常很耗时,尤其是在测试每个代码更改时。如果能在这方面节省时间,对于加快开发周期是非常有利的。过去,对于非容器化应用,我们通常采用以下传统工作流程:编写代码、构建和运行。多年来,业界不断优化这个过程,并提出了诸如:增量构建和热重载)等实用技巧。随着容器技术的出现,我们在现有的工作流程中加入了docker构建的步骤,如下图所示。编写代码BuildDockerbuildRun当然,如果构建不好,dockerbuild这一步也可能会引入额外的时间开销。例如:使用apt-get进行依赖项的重新加载步骤。有时这些步骤会使整个测试过程比添加Docker之前还要慢。解决方案:在Docker外部运行代码第一个解决方案是在DockerCompose中启动所有依赖项,然后在本地运行测试代码。此举模仿了非容器化应用程序开发的工作流程。您只需将依赖项通告到localhost,并将您正在使用的服务指向所有localhost:地址。然而,这种方法并不总是可行的。如果您使用的代码依赖于容器映像中的内置元素,则用户的计算机可能无法访问特定内容。解决方案:最大化缓存优化Dockerfile如果必须构建Docker镜像,那么我们可以写一个Dockerfile通过最大化缓存将Docker构建时间从10分钟压缩到1分钟。生产环境中Dockerfile的典型模式是通过将各个命令链接到单个RUN语句中来减少层数。毕竟,图像的大小在开发过程中并不重要,重要的是层数。下面显示的是生产中的Dockerfile:RUN\goget-d-v\&&goinstall-v\&&gobuild但是,每次重新运行命令时,Docker都会重新下载所有依赖项并重新安装它们。我们可以通过增量构建来提高效率。同时,可以将开发专用的Dockerfile拆分成几个小步骤,让那些经常变化的代码步骤排在最后,很少变化的步骤(比如拉依赖)放在最前面。因此,在重建Dockerfile时,您不必构建整个项目,只需构建更改的那几个结束块即可。对于这方面的示例,您可以参考以下用于Blimp的Dockerfile(参见--https://kelda.io/blimp)开发。按照上述方法,您可以将繁琐的构建过程缩短到几秒钟。FROMgolang:1.13-alpineasbuilderRUNapkaddbusybox-staticWORKDIR/go/src/github.com/kelda-inc/blimpADD./go.mod./go.modADD./go.sum./go.suMADD./pkg./pkgARGCOMPILE_FLAGSRUNCGO_ENABLED=0goinstall-i-ldflags"${COMPILE_FLAGS}"./pkg/...ADD./login-proxy./login-proxyRUNCGO_ENABLED=0goinstall-i-ldflags"${COMPILE_FLAGS}"./login-proxy/...ADD。/registry./registryRUNCGO_ENABLED=0goinstall-i-ldflags"${COMPILE_FLAGS}"./registry/...ADD./sandbox./sandboxRUNCGO_ENABLED=0goinstall-i-ldflags"${COMPILE_FLAGS}"./sandbox/...ADD./cluster-controller./cluster-controllerRUNCGO_ENABLED=0goinstall-i-ldflags"${COMPILE_FLAGS}"./cluster-controller/...RUNmkdir/gobinRUNcp/go/bin/cluster-controller/gobin/blimp-cluster-controllerRUNcp/go/bin/syncthing/gobin/blimp-syncthingRUNcp/go/bin/init/gobin/blimp-initRUNcp/go/bin/sbctl/gobin/blimp-sbctlRUNcp/go/bin/registry/gobin/blimp-authRUNcp/go/bin/vcp/gobin/blimp-vcpRUNcp/go/bin/login-proxy/gobin/login-proxyFROMalpineCOPY--from=builder/bin/busybox.static/bin/busybox.staticCOPY--from=builder/gobin/*/bin/最后但并非最不重要的是,对于多阶段构建,请参见--https://docs.docker.com/develop/develop-images/multistmuage-build/),我们现在可以创建各种分层漂亮、镜像更小的Dockerfiles不过,这里我们将不做详细讨论。解决方案:使用主机卷大多数语言都提供了一种方法来监视程序代码并在代码发生变化时自动重新运行。例如,nodemon是JavaScript语言的Node自动重启工具(参见--https://www.npmjs.com/package/nodemon)。由于宿主卷可以将你电脑上的目录镜像到运行的容器中,当你使用文本编辑器编辑文件时,各种变化会自动同步到容器中,并立即在容器中执行。最初,您可能需要花一点时间准备,然后在Docker中,您可以在1-2秒内立即看到代码更改的结果。因此,我们会选择使用宿主卷将代码直接挂载到容器中,这样我们就可以在Docker容器中以原生的方式运行自己的代码,包括它的运行时依赖。错误2:主机卷慢如果您使用过主机卷,您是否注意到在Windows和Mac上读取和写入文件会非常慢?事实上,对于Node.js和PHP等具有复杂依赖关系的应用程序,这是一个已知的问题,对于需要读写大量文件的命令,例如程序。其背后的原因:Docker主要在Windows和Mac上的虚拟机中运行。我们在挂载宿主卷的时候,要经过很多转换才能让文件夹进入容器,这有点类似于网络文件系统。而这种额外的开销在Linux本地运行Docker时不会出现。解决方案:放宽强一致性造成该问题的关键原因之一是文件系统在默认挂载时需要保持强一致性。也就是说:对一个特定文件的所有读写过程都必须统一文件修改的顺序,这样文件的内容才能最终达成一致。然而,强一致性是非常昂贵的,需要所有文件写入进程之间不断协调,以确保它们不会干扰或破坏彼此的更改。虽然生产环境中的数据库需要保持强一致性。但是在开发的时候,由于写的过程是代码文件本身,而目标是我们的仓库,强一致性就没那么必要了。那么,我们可以认为Docker在挂载卷时放宽了强一致性。例如:在DockerCompose中,我们可以简单地将这个cached关键字添加到卷挂载中,以获得显着的性能保证。对应代码如下:volumes:-"./app:/usr/src/app/app:cached"注意:这里只适用于开发环境,不适用于生产环境。解决方案:代码同步另一种方法是设置代码同步。您可以使用工具来检测主机和容器之间的变化,并通过复制文件(类似于rsync)而不是挂载卷来解决差异。在最新版本中,Docker内置了一种缓存模式——Mutagen(参见——https://mutagen.io/)来替换卷。另外,上面提到的Blimp使用Syncthing(参见--https://http//syncthing.net/)来实现类似的功能。解决方案:不要挂载包像Node这样的语言通常会将大部分文件操作放在packages目录下(如node_modules)。那么,我们可以尝试从卷中删除此类目录以显着提高性能。以下示例是将专用卷安装到容器中的代码,它覆盖了node_modules目录。volumes:-".:/usr/src/app"-"/usr/src/app/node_modules"这个挂载操作会告诉Docker使用node_modules目录下的标准卷,这样当npminstall运行时,不再使用慢速主机挂载。为了让它工作,我们应该在入口点执行npminstall以安装依赖项并在容器首次启动时更新node_modules目录。具体代码如下:entrypoint:-"sh"-"-c"-"npminstall&&./node_modules/.bin/nodemonserver.js"如果想查看并运行上面的完整示例,请参考--https//凯尔达。io/飞艇/文档/示例/#nodejs。错误#3:脆弱的配置如果你曾经深入研究过你的代码,你可能已经注意到DockerCompose也是大量复制和粘贴的。显然,我们需要干净整洁的DockerCompose文件,以便我们可以轻松地根据需要进行更改。解决方案:使用各种env文件Env文件能够将环境变量与主要的DockerCompose配置分开,以便:避免将代码泄漏到git的历史记录中。开发人员可以根据需要自定义设置。例如,每个开发人员都可以拥有一个唯一的访问密钥。他们将配置保存在.env文件中,这样就不用修改提交的docker-compose.yml文件,也不必处理文件更新时的各种冲突。如果你想使用环境文件,只需添加一个.env文件,或使用env_file字段设置一个显式路径(参见--https://docs.docker.com/compose/environment-variables/#the-env_file-配置选项)。解决方案:使用替代文件替换文件(参见--https://docs.docker.com/compose/extends/)可以方便您在基本配置的基础上在其他文件中指定修改。此功能与DockerSwarm及其YAML文件完美配合。您可以将生产环境的配置存储在docker-compose.yml中,然后在覆盖文件中指定开发所需的任何修改(例如:使用主机卷)。解决方案:使用extends如果您使用的是DockerComposev2,则可以使用extends关键字在多个地方导入YAML片段。例如,您可以在开发DockerCompose文件中定义公司中的所有服务都需要某些五种特定配置。然后,您可以使用extends关键字将其放置在任何您想要的位置以实现模块化。当然,仅在YAML中执行此操作可能很麻烦,我们可以通过编程方式执行此操作。尽管Composev3移除了对extends关键字的支持。但是,您仍然可以使用YAML锚点(请参阅--https://support.atlassian.com/bitbucket-cloud/docs/yaml-anchors/)来获得类似的结果。错误4:FlakyBoots如果docker-composecrash了,我们是否可以只使用docker-composerestart来重启服务?事实上,此类问题主要与服务启动顺序错误有关。比如,你的web应用可能依赖于一个数据库,当web应用启动的时候,如果数据库没有准备好,就会出现crash。解决方案:使用depends_ondepends_on可以控制启动顺序。depends_on默认只判断依赖是否已经创建,不判断依赖是否“健康”。虽然DockerComposev2可以支持depends_on和健康检查的结合。但是,此功能在DockerComposev3中也被删除了。当然,您可以使用wait-for-it.sh等脚本手动实现类似的功能。和上面提到的放宽强一致性一样,虽然Docker文档不推荐在生产环境中使用depends_on和wait-for-it.sh来指定容器的特定启动顺序。但是为了开发,我们可以使用depends_on。错误5:资源管理不善如果您遇到Docker无法全速运行或无法获得顺利运行所需的资源的阻塞开发过程,请考虑以下几点:解决方案:更改分配DockerDesktopDockerDesktop需要大量RAM和CPU,尤其是在Mac和Windows虚拟机上。DockerDesktop的默认配置往往没有分配足够的RAM和CPU,因此我们通常需要调整相关设置。开发时,我的经验是:给Docker分配8GB左右的RAM和4个CPU,不用的时候及时关闭DockerDesktop。解决方案:删除未使用的资源人们在使用Docker时通常会遇到数百个卷和旧的容器映像。这是对各种资源的无形浪费。要释放这些资源,我们建议偶尔运行dockersystemprune以删除当前未使用的所有卷、容器和网络。综上所述,为了提升开发者在使用DockerCompose时的体验,我建议大家做到以下五点:尽量减少容器的重建。使用主机卷。像对待代码一样谨慎对待配置文件,以便于维护。使开机更可靠。谨慎分配行政资源。此外,您还可以使用链接--https://kelda.io/blog/docker-volumes-for-development/了解如何设置主机卷并加速Docker开发。原标题:编写DockerCompose时的5个常见错误,作者:EthanJJackson