记录我们迁移到Docker的挑战和经验教训

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"几周之前,我们宣布了"},{"type":"link","attrs":{"href":"https:\/\/www.artifakt.com\/blog\/product\/artifakt-any-app-platform-powered-by-container-technology\/","title":"xxx","type":null},"content":[{"type":"text","text":"最新的产品"}]},{"type":"text","text":"发布,以及由容器技术和Docker支持的Artifakt平台的全新的任意App功能。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在过去几年中,Artifakt一直专注于PHP栈。但PHP并不是Web应用程序的唯一语言。通过使用Docker集成,我们提前完成了宏伟的计划!"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"基于应用程序打包的事实标准来重新调整我们的PaaS,对于各种形式和规模的开发团队来说都是一个好消息。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在这个版本中,你会发现许多额外的功能,在代码、更改可跟踪性、运行时性能和客户价值等方面,现在是加入容器友好型PaaS基础设施的最佳时机。更不用说,Docker是DevOps核心原则(敏捷性、抗敏捷性和上市时间)的最好朋友。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"但所有的旅程,无论结果如何,通常都伴随着奋斗。在本文中,我想要深入讨论我们在迁移到Docker的过程中所面临的挑战和我们学到的经验教训。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果你想要迁移到容器,请读一读本文——我相信你至少会找到一点相关信息,也许我们处理某些问题和挑战的方案可以帮助你避免一些潜在的错误。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"问题:软件工程中没有免费的午餐"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"PaaS解决方案非常方便。我相信任何从本地部署服务或IaaS迁移其应用程序到PaaS的人都会同意。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"其中很方便的一点是虚拟机(Virtual Machines,VM)的使用。这是一款很好的软件,几十年来至今,都非常安全和成熟。现在没有人做事情能离得开它们,所以它们是非常必要的。尽管如此,这还不够。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"虚拟机的阴暗面是,它们倾向于作为私人宠物成长。日常维护、定期支持和偶尔的急救支持需要大量的固定成本以及人为协助。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"现在,面对痛苦的现实:虚拟机已经不再适应云基础设施。目前的条件对它们来说是苛刻的,可能采用严格的规章制度。尽管科技领域不断发展,但虚拟机仍然建立在20年前相同的原则上。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"放大:哪里会出问题"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"你应该时刻记住所有可能出错的情况并做好准备。让我们看一看容器迁移过程中可能出现的几个问题。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"不良后果 #1:速度不够快"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我们以前都听过,速度是所有战斗之母。不仅是在战略上,而且从构思到执行都是如此。变化需要快速发生,跟上生态系统的快速进化。正如通用电气前CEO杰克·韦尔奇(Jack Welch)所说:“如果外部变化的速度超过内部变化的速度,终结就不远了。”"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/9f\/9ff4ddbf5ece1fea88d7c8a707ced6e9.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"不是大吃小,而是快吃慢。拥有好主意,但是执行慢,意味着死刑。拥有糟糕的主意,但是执行良好,意味着你仍然可以调整和实验直到适应市场获得成功。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"不良后果 #2:缺乏自信"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果你在一个软件项目中听过“但是......它在我的笔记本电脑上可以运行!”,请举手——我怀疑我会看到很多人举手。当然,我们有代码、持续集成和所有现代机器等基础设施。别误会,有一些本地优化确实非常有用。但真正有用的是打破局限,将团队提升到足够高的成熟度水平,以便“谁构建谁运行”。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我们不希望PaaS成为新的“运维问题”和“支持问题”。还记得“灾难女孩”咒语吗?黑暗运维更加危险,你不希望开发团队自己运行容器,"},{"type":"link","attrs":{"href":"https:\/\/blog.newsblur.com\/2021\/06\/28\/story-of-a-hacking\/","title":"xxx","type":null},"content":[{"type":"text","text":"并在你的防火墙上戳洞"}]},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"是什么触发了变革的需要以及我们向Docker的迁移?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"容器是上述所有挑战的答案——一个成熟、甚至枯燥的软件打包标准。但情况并非总是如此。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"回想20世纪,全球化是通过一个非常具体的物质变化实现的:一个通用的箱子来移动汽车、食品等等。这就是我们今天所知道的“多式联运(inter-modal transportation)”。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/4d\/4d6f47084ded8817b3a32fde7c164a04.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null},"content":[{"type":"text","text":"图片来源:Docker Inc."}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"自2014年以来,软件容器在Docker Inc.公司旗下的事实解决方案中迅速成熟,并有一个简单却有效的承诺:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"“无论底层执行系统是什么,Docker都可以使用完全相同的代码逐字节运行它。”"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/9f\/9fac5ee09c18f018dbe206e00d131a44.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null},"content":[{"type":"text","text":"图片来源:Docker Inc."}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我们敢说,在Artifakt,容器已经在后台运行了好几个月,即使是有状态的东西。我们现在可以在几秒中内运行不同的配置更改,而不是需要10到30分钟的虚拟机配置。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在我们的下一个主要控制台版本中,Artifakt将容器作为部署单元公开。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"转变与见证:我们如何让Magento 2更加闪亮"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"你可以想象Docker迁移对我们日常工作的开创性影响。协调虚拟机需要与我们的云提供商在某种专有技术上进行强耦合。对于AWS来说,是CloudFormation和OpsWorks。我们花了很多时间消化云的复杂性,这样我们亲爱的客户就不必这么做了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最终,出色的重写让我们拥有了开源和开放格式:Dockerfile、docker-compose.yaml 和稳定的Docker API。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在笔记本上运行完全相同的Magento 2栈并将其投入生产如何?这在Artifakt是可能的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Magento 2是自7月早些时候发布"},{"type":"link","attrs":{"href":"https:\/\/headwayapp.co\/artifakt-updates\/stack-5-0-with-docker-inside-201413","title":"xxx","type":null},"content":[{"type":"text","text":"Stack v5"}]},{"type":"text","text":"以来我们正式支持的九个运行时的一部分。在许多方面,这个发布版本将所有挑战集中在一个地方:"}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"crontab管理"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"容器测试"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"部署过程"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"ISO生产环境本地堆栈"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"让我们来看看我们是如何克服这些挑战的,以及这将给我们带来什么。Docker迁移部分 I: 好的方面先从Docker的好处开始。我们已经意识到在PaaS环境中容器化的好处。有些方面真的很容易实现。成熟度足够高,生态系统蓬勃发展,云供应商已经为未来几年铺平了道路。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我们不可能一一讲述所有的好处,很多好处在2021年讲都会很无聊,所以让我把重点放在最有启发性的事情上。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"好处 #1:使用Argo的工作流引擎"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https:\/\/argoproj.github.io\/argo-workflows\/","title":"xxx","type":null},"content":[{"type":"text","text":"Argo Workflows"}]},{"type":"text","text":"是实现响应式GitOps管道的一个非常好的工具。要讲的有很多,让我们拆开来一个个讲。GitOps使团队能够执行Git中的更改,而不仅仅是代码更改,比如基础设施、网络、存储等。机器的每一个部分,创建、升级或停用,都可以链接到一个Git提交。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"感觉兴奋吗?我们确实很兴奋!我们的工作流很好地隐藏在Argo中,可以为许多不同的堆栈和语言运行部署任务等基础操作。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我们组织中的每个人都可以访问过去的工作流及其日志,从而轻松解决问题。我们现在受益于一个共享的平台,客户支持和工程团队可以帮助客户测量和优化他们的流程,而不是将日志分散在许多复杂的层次上。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在未来,我们也期待着尝试Argo CD以及它为像Artifakt这样的PaaS产品提供的许多机会。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"好处 #2:在容器中格式化和测试"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"事实证明,Docker镜像有很多出错的方式。由于自动测试在代码中是一种很好的实践,同样的原则也适用于Dockerfiles。官方文档已经确保了Dockerfile的正确性:所有命令都应该返回0,否则构建将失败。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我们需要在这里增加两个维度:有效性和内容。有效性确保我们在编写Dockerfile和语义内容检查时具有正确的风格。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我们可以只使用本机Dockerfile指令完成所有操作吗?几乎可以,但是即使如此,它也会导致包含许多非生产层的膨胀镜像,然后进入多阶段构建,等等。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"换句话说,如果构建成功,它必须装载恰当数量的正确软件。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"因此,我们选择遵循关注点分离的原则,并在测试、验证和语义检查之间划清界限。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"对于简单的语法格式化,我们使用hadolint,这是针对Dockerfiles的一种格式化工具。它功能强大,很容易上手,可以与持续集成\/持续部署很好地集成,并得到了积极的维护。当然,它本身就存在于一个Docker镜像中!让我们看一下基本选项:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"hadolint - Dockerfile Linter written in Haskell\n\n\nUsage: hadolint [-v|--version] [--no-fail] [--no-color] [-c|--config FILENAME]\n \t[-V|--verbose] [-f|--format ARG] [DOCKERFILE...]\n \t[--error RULECODE] [--warning RULECODE] [--info RULECODE]\n \t[--style RULECODE] [--ignore RULECODE]\n \t[--trusted-registry REGISTRY (e.g. docker.io)]\n \t[--require-label LABELSCHEMA (e.g. maintainer:text)]\n \t[--strict-labels] [-t|--failure-threshold THRESHOLD]\n \t[--file-path-in-report FILEPATHINREPORT]\n Lint Dockerfile for errors and best practices"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在默认的帮助屏幕上,我们可以看到一系列不错的选项和示例:检查所需选项、以不同格式输出报告、声明私有的受信任的注册表等。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"要在持续集成中包含hadolint ,我们建议以下三个步骤:"}]},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"使用--no-fail 调用hadolint ,并在不中断当前流的情况下安全地评估结果。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"在使用选项并找到恰当的平衡点后,对测试的Dockerfile进行必要的修复。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"删除--no-fail 选项,并将hadolint 作为强制步骤启用。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"为了演示hadolint 有多好,让我们看一下我们的Docker基础镜像中的这条简单命令:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"$ docker run --rm -i hadolint\/hadolint:v2.6.0 hadolint - < .\/akeneo\/5-apache\/Dockerfile"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"接着会给出一个附带严重程度(从style到error)的建议列表,附带有具体的行号,非常整洁。例如:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"-:16 DL4006 warning: Set the SHELL option -o pipefail before RUN with a pipe in it. If you are using \/bin\/sh in an alpine image or if your shell is symlinked to busybox then consider explicitly setting your SHELL to \/bin\/ash, or disable this check\n-:17 DL3008 warning: Pin versions in apt get install. Instead of `apt-get install ` use `apt-get install =`\n-:17 DL3059 info: Multiple consecutive `RUN` instructions. Consider consolidation."}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所有规则,70多条,都在"},{"type":"link","attrs":{"href":"https:\/\/github.com\/hadolint\/hadolint\/wiki","title":"xxx","type":null},"content":[{"type":"text","text":"官方库的wiki中"}]},{"type":"text","text":"有参考和解释。它们包括非常基础的简单检查(“不要使用apt-get upgrade”)到高度专业化的用例(“运行yarn install之后,没有运行yarn cache clean”)。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"结合在一起,hadolint 选项更加强大。看看这个命令:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"docker run --rm -i hadolint\/hadolint:v2.6.0 hadolint --require-label author:text --ignore=DL4006 --failure-threshold=warning - .\/Dockerfile"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在这段代码中,我们告诉hadolint 去扫描我们的Dockerfile文件,通过检查一个强制标签author ,除了DL4006规则,最后,只有在反馈包含警告时才会失败,从而忽略style 和info 级别。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"对于开发人员来说,hadolint 还支持Dockerfile中的#ignore 内联注解,这使得在一组Dockerfile文件上使用相同的命令更容易。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"总而言之,这为我们的测试提供了一个良好的开端。稍后,当我们准备好应用更高级别的良好实践时,我们可以降低错误阈值到notice 。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"拥有语法正确且遵循良好实践的Docker镜像是非常棒的。现在,我们还需要检查内容的有效性。毕竟,PaaS产品应该有一套应用的内部规则。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"为此,我们使用了另一个很棒的工具——谷歌的"},{"type":"link","attrs":{"href":"https:\/\/github.com\/GoogleContainerTools\/container-structure-test","title":"xxx","type":null},"content":[{"type":"text","text":"container-structure-test"}]},{"type":"text","text":"。它是Github上GoogleContainerTools命名空间的一部分,在这个命名空间下还托管了一些你可能已经知道或使用过的著名项目:Skaffold、distroless、Jib和Kaniko。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Container-structure-test是一个开源项目,读取测试套件并对照现有的Docker镜像进行检查。它有一个命令:test 。注意,这与hadolint 不同,hadolint只需要一个纯文本的Dockerfile文件就足够了。Container-structure-test需要一个二进制构件:Docker镜像或者一个导出为.tar的文件。和hadolint 一样,测试也是用普通的YAML编写的,这似乎是云生态系统中第二有用的技能(仅次于Git,对吧?)。下面是用YAML编写的一个有意思的声明式测试示例:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"schemaVersion: '2.0.0'\nmetadataTest:\n labels:\n\t- key: 'vendor'\n \tvalue: 'Artifakt'\n\t- key: 'author'\n \tvalue: \"^\\\\w+([-+.']\\\\w+)*@\\\\w+([-.]\\\\w+)*\\\\.\\\\w+([-.]\\\\w+)*$\"\n \tisRegex: true\n volumes: []\n entrypoint: [\"\/usr\/local\/bin\/docker-entrypoint.sh\"]\n\n\nfileExistenceTests:\n- name: 'bash'\n path: '\/bin\/bash'\n shouldExist: true\n permissions: '-rwxr-xr-x'\n uid: 0\n gid: 0\n isExecutableBy: 'any'\n\n\ncommandTests:\n - name: \"debian based server\"\n\tcommand: \"which\"\n\targs: [ \"apt-get\"]\n\texpectedOutput: ['\/usr\/bin\/apt-get']"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在这个代码块中,我们测试了一个镜像的元数据、文件和命令结果,分别由metadataTest 、fileExistenceTests 和commandTests 三个主键定义。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"但是为什么container-structure-test需要一个预先存在的构建步骤呢?因为,在后台,它在活跃容器上使用docker exec 命令来运行每一个测试。最重要的是,由于一个容器只能运行一个测试,所以测试也保证是隔离的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"当你运行测试时使用--save 选项来保持容器,这个行为就很容易查看。让我们在一个官方镜像上运行如下测试:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"container-structure-test test --image registry.artifakt.io\/sylius:1.10-apache --save --config .\/global.yaml"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"检查保存的容器:"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"caae8bd3159c \t678b262bc2d6 \t\"NOOP_COMMAND_DO_NOT…\" 2 minutes ago \tCreated \tpractical_einstein\n3b63b4f5fcd1 \tregistry.artifakt.io\/sylius:1.10-apache \"NOOP_COMMAND_DO_NOT…\" 2 minutes ago \tCreated \tadmiring_solomon\n44497e1d88cf \td10ef6baba46 \t\"which apt-get\" \t2 minutes ago \tExited (0) 2 minutes ago \tbold_tesla\n6e87be191af3 \tregistry.artifakt.io\/sylius:1.10-apache \"NOOP_COMMAND_DO_NOT…\" 2 minutes ago \tCreated \tangry_bouman"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"当你想使用docker logs 或docker diff 检查保存的容器以及它们如何受影响时,这非常有用。使用最后一个命令,我们可以轻松编写一个测试来确保我们的容器是足够无状态的,或者不会泄露意料外的文件,或者对活跃容器进行任何其它评估。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"正如我们在第一个示例中已经提到的,container-structure-test评估三类开箱即用的检查:镜像元数据(labels、volumes和entrypoints等等)、文件是否存在、任意命令结果。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在测试是否存在的基础上,我们还编写测试来检查最终Docker镜像中我们不需要的内容。想想开发包、编译器和工具,它们可能到处都是,在生产环境肯定不受欢迎。防止这种情况的一种方法是:"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"fileExistenceTests:\n- name: 'forbid gcc'\n path: '\/usr\/bin\/gcc'\n shouldExist: false"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最后,另一个有用的选项是将setup 关键词与测试命令结合使用。有些测试只在Docker入口点运行之后才有意义。我们使用setup 键来处理这种情况。请看下面的示例:"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"- name: \"check mounted folder private\"\n\tsetup: [[\"\/usr\/local\/bin\/docker-entrypoint.sh\"]]\n\tcommand: \"ls\"\n\targs: [ \"-la\", \"\/opt\/drupal\/web\/sites\/default\/private\"]\n\texpectedOutput: [ 'lrwxrwxrwx 1 www-data www-data .+ \/opt\/drupal\/web\/sites\/default\/private -> \/data\/web\/sites\/default\/private' ]"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"官方文档和高级案例中还有很多功能,比如测试守护进程,因此我鼓励你们进行深入研究。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"Docker迁移部分 II:坏的方面(和有趣的方面!)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在容器化的道路上,许多挑战是预料不到的,由于我们获取了一些有价值的见解,因此我们必须分享其中一些挑战。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"挑战 #1:crontab集成"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我们最完整的运行时是Magento 2和Akeneo,它们是cron任务的重度用户:索引、缓存、镜像大小调整、导入\/导出等等。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Docker对于异步间歇进程处理得怎么样?其实并不太好。Docker 101中众所周知,你不能在与主进程相同的容器中运行cron。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"那么,有效的替代方案是什么?我们考虑了以下几个方案:"}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Swarm cronjob"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"cron job containers"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Docker exec bridge"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首先,Docker刚刚升级了Swarm编排层来运行cron作业,就像Kubernetes那样。这可能行得通,但是Swarm不在我们最初的路线图上。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"其次,我们可以为每个cron作业运行额外的容器,在节点级别使用一个cron守护进程。这个方法有利有弊。由于时间和计划的限制,我们不得不加快步伐。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最后,我们可以声明将crontab保持在节点级别,并使用docker exec 将命令运行到活跃的容器中。这可能起作用,因为我们仍然在每个服务器上运行一个应用程序容器,所以现在这是有意义的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我们选择了最后一个选项,结果是简单、优化,并且尊重我们热爱的Linux精神。下面是将cron作业注入到活跃容器的三个简单步骤:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"步骤 1"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"编写一个docker exec 包装器,其中实际上有2行代码足以指向 容器。"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"#!\/bin\/bash\nsudo docker exec -t sh -c \"$2\""}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"步骤 2"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"将包装器保存在crontab用户空间的某个地方(我们将其命名为dockercron.sh)。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"用你的脚本添加\/替换默认的contab SHELL env. var :"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"### Crontab Managed By Artifakt ###\nSHELL=\/home\/ec2-user\/dockercron.sh\n5\/* * * * * uptime > \/tmp\/uptime.txt"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"步骤 3"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"运行docker events 并检查crontab是否正在调用应用程序容器:"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"2021-07-28T13:55:02.338665047Z container exec_create: sh -c uptime > \/tmp\/uptime.txt a360afb6e (artifakt.io\/image=616787838396.dkr.ecr.eu-west-1.amazonaws.com\/sylius, execID=8b14a4e96eb5175c74b689260e1b692af34f22260e9572fbd2bb2c2b663a3c1e, image=616787838396.dkr.ecr.eu-west-1.amazonaws.com\/sylius:1.10, vendor=Artifakt)\n2021-07-28T13:55:02.339417333Z container exec_start: sh -c uptime > \/tmp\/uptime.txt a360afb6e (artifakt.io\/image=616787838396.dkr.ecr.eu-west-1.amazonaws.com\/sylius, execID=8b14a4e96eb5175c74b689260e1b692af34f22260e9572fbd2bb2c2b663a3c1e, image=616787838396.dkr.ecr.eu-west-1.amazonaws.com\/sylius:1.10, vendor=Artifakt)\n2021-07-28T13:55:02.822814763Z container exec_die a360afb6e (artifakt.io\/image=616787838396.dkr.ecr.eu-west-1.amazonaws.com\/sylius, execID=8b14a4e96eb5175c74b689260e1b692af34f22260e9572fbd2bb2c2b663a3c1e, exitCode=0, image=616787838396.dkr.ecr.eu-west-1.amazonaws.com\/sylius:1.10, vendor=Artifakt)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"这个方案保持了完全相同的执行环境,同时将资源使用保持在一个容器中,保证了总体稳定性。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"挑战 #2:取消OpsWorks"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"取消一个工作多年的遗留层几乎是不可能的。因此,说实话,我们并没有完全成功。然而,我们尽了最大的努力,设法使这一层尽可能无关紧要。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"从OpsWorks到Docker的转变,需要将移除过去的平台规则,并转移到编写docker-compose YAML和bash脚本。结果是我们所有的部署都是一个独特的分支。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"经过快速的多次提交,并经过许多尝试和错误后,OpsWorks现在所做的就是安装Docker Engine以及屈指可数的一些容器依赖项。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"其它一切都是通过Docker API操作,由普通的过去的OpsWorks作业触发,比以往任何时候都更可预测。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"这是简化和技术债务平抑的一个伟大里程碑!"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"挑战 #3:本地运行HTTPS"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"当我们接近发布时间时,我们的CEO看了看我们的demo,说:“嘿,如果开发者可以用HTTPS本地运行他们的应用程序,那就太好了”。作为开发者,团队中的每个人都马上理解了这个机遇。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"工作站越接近生产环境,我们送出的bug就越少。这是表达"},{"type":"link","attrs":{"href":"https:\/\/12factor.net\/","title":"xxx","type":null},"content":[{"type":"text","text":"12因素应用程序"}]},{"type":"text","text":"的"},{"type":"link","attrs":{"href":"https:\/\/12factor.net\/dev-prod-parity","title":"xxx","type":null},"content":[{"type":"text","text":"第10章"}]},{"type":"text","text":"——“dev\/prod对等”的另一种方式。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下面是我们如何做到的。只需要2个附加组件(容器!):"},{"type":"link","attrs":{"href":"https:\/\/github.com\/nginx-proxy\/nginx-proxy\/","title":"xxx","type":null},"content":[{"type":"text","text":"Nginx-proxy"}]},{"type":"text","text":"和"},{"type":"link","attrs":{"href":"https:\/\/github.com\/sebastienheyd\/docker-self-signed-proxy-companion","title":"xxx","type":null},"content":[{"type":"text","text":"Cert companion"}]},{"type":"text","text":"。我们使用它们修改了原始的docker-compose.yaml ,除此之外没有修改其它代码:"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"version: '3'\nservices:\n proxy:\n\timage: jwilder\/nginx-proxy\n\tcontainer_name: base-wordpress-proxy\n\trestart: always\n\tports:\n \t - \"8000:80\"\n \t - \"8443:443\"\n\tvolumes:\n \t - \/var\/run\/docker.sock:\/tmp\/docker.sock:ro\n \t - .\/certs:\/etc\/nginx\/certs\n\n\n proxy-companion:\n\timage: nginx-proxy-companion:latest\n\trestart: always\n\tenvironment:\n \t - \"NGINX_PROXY_CONTAINER=base-wordpress-proxy\"\n\tvolumes:\n \t - \/var\/run\/docker.sock:\/var\/run\/docker.sock:ro\n \t - .\/certs:\/etc\/nginx\/certs"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最后,为了使这个设置生效,我们的应用程序容器还有两个env.变量是很重要的,因此我们按如下方式添加了它们:"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":" app:\n\timage: base-wordpress:5-apache\n\tvolumes:\n \t - \".:\/var\/www\/html\"\n\tenvironment:\n \t VIRTUAL_HOST: \"localhost\"\n \t SELF_SIGNED_HOST: \"localhost\""}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在第一次运行时,nginx 寻找一个ca.cert ,如果不存在就在线为你生成一个。然后,我们必须告诉浏览器要像信任其它CA一样信任这个ca.cert 。遗憾的是,这仍然需要一次手工设置。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我们尝试了Let's Encrypt等各种方案,但没有开箱即用的解决方案。"},{"type":"link","attrs":{"href":"https:\/\/letsencrypt.org\/docs\/certificates-for-localhost\/","title":"xxx","type":null},"content":[{"type":"text","text":"你不能使用Let's Encrypt作为一个CA来提供本地主机证书。"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最后,还需要一些额外的步骤,通常会弄乱本地的根证书。下面是我们为开发人员找到的一条最短路径,即一次性安装本地证书颁发机构并在所有本地开发堆栈上使用它。请注意,以下步骤仅适用于Google Chrome。"}]},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"打开Chrome设置并搜索“certificates”。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"打开Manage Certificates菜单,它会弹出密钥链入口。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"在System密钥链,打开Certificate选项卡,将ca.cert从Nginx-Proxy Companion中删除。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null},"content":[{"type":"text","text":"双击Nginx-proxy Companion证书为Always Trust。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":5,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/37\/37c9621647b35de8e968cf9debb50d5f.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"Docker 迁移部分 III:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"前方我们还有很多方法可以让一个好的平台更好。以下是我们正在考虑的下一步行动。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"持久化数据"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我们严重依赖AWS持久化数据。这已经"},{"type":"link","attrs":{"href":"https:\/\/www.artifakt.com\/blog\/cloud\/building-scalable-resilient-applications-using-aws\/","title":"xxx","type":null},"content":[{"type":"text","text":"在弹性和可伸缩性方面发挥了神奇的作用"}]},{"type":"text","text":"。我们的客户需要他们的数据安全且封闭。另一方面,开发者更喜欢便利性和易用性。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"网络带宽"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"由于速度是一种竞争优势,我们正朝着更快的部署迈进。Docker镜像可能会变大,构建任意代码的速度会变得非常慢。我们希望在未来实施的一些最佳实践包括:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"代理依赖管理器(例如composer、npm、maven)"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Docker层缓存,而不是原始构建环境"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"从一个构建到另一个构建的共享数据卷"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"构建并推动镜像更接近生产环境,而不是通过区域移动"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"这些步骤可以显著减少延迟,我强烈建议你试一试。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"Docker构建中的高级用例"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一些高级Docker镜像需要一个实时数据库来完成构建阶段。这听起来有点儿奇怪,但我们经常看到这样的情况。我们正考虑以下几条实例:"}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"为构建单独绑定一个虚拟数据库"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"当兼容的时候,使用SQLite作为卷——不需要服务器"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"随你发挥,容器化的方案很多样化,并不枯燥!"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"以上就是我们迁移到Docker的经历。如果你当前正在迁移到容器或者希望迁移到容器,我希望你能够在本文中找到一些有用的点子。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"关于如何让开发人员的工作更轻松,如果你有什么想法或建议可以"},{"type":"link","attrs":{"href":"https:\/\/feedback.artifakt.io\/tabs\/5-coming-soonhttps:\/\/feedback.artifakt.io\/tabs\/5-coming-soon","title":"xxx","type":null},"content":[{"type":"text","text":"在此"}]},{"type":"text","text":"与我们交流。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"作者介绍:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Djalal Elbe"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"原文链接:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https:\/\/www.artifakt.com\/blog\/cloud\/documenting-our-migration-to-docker","title":"xxx","type":null},"content":[{"type":"text","text":"Documenting our migration to Docker—challenges and lessons learned"}]}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章