Earthly的工作原理和Docker的介绍

442 阅读7分钟

我们使用容器进行软件打包已经有很长一段时间了。但让我们面对现实吧,Docker的用户体验其实并不是最好的。构建和测试通常需要有一堆的Makefile、繁琐的Dockerfiles、一点黑魔法和一捆Bash脚本。这是一条可能导致疯狂的道路。

这是一种耻辱,因为Docker可以更加友好。地球上的开发者当然相信这一点:他们已经创造了一个工具,填补了Docker和Make之间的空间。

在本教程的最后,你会知道Earthly是如何工作的。作为奖励,你将拥有一个可以构建、测试并将Docker镜像推送到远程仓库的管道。

Final pipeline

Earthly是如何工作的

Earthly是一个基于Docker的构建工具。然而,它并没有取代Maven、Gradle或Webpack等特定语言的工具。相反,它利用并整合了这些工具--充当了胶水的角色。

使用Docker容器作为实现可重复性的核心机制,每个Earthly命令都在一个隔离的环境中运行,并尽可能毫不费力地进行并行化。

Earthly由CLI二进制文件和一个名为earthly/buildkit的Docker镜像组成,基于Docker的BuildKitd

Earthly components

Earthly组件

Earthly的开发者希望你在任何地方都能获得相同的体验。BuildKit的作用是产生可移植的Docker兼容层。因此,无论是在我们的本地机器上还是在我们的CI系统中,我们都会得到同样的结果。

Earthly是否实现了它的承诺?

Earthly 承诺了更好的、可重复的构建。但是,它能实现吗?这里有一些最好的功能和一些在做试驾时发现的问题。

优点。

  • Earthly,像Docker一样,是不分语言的。它的语法感觉很熟悉,因为它混合了Make和Dockerfiles。
  • 他们的网站有很好的文档。
  • 配置是低维护的,只有少数设置需要调整。
  • 即使在你不需要Docker的情况下也很有用。容器镜像只是可能的输出之一。例如,你可以使用Earthly在一个干净的环境中测试和编译二进制文件。
  • 导入系统在multirepo和monorepos中运行良好。

缺点:

  • 它实际上并没有取代Makefile或Docker Compose。而且你仍然需要一些shell脚本。
  • 它比本地Docker慢,因为它依赖于复制文件而不是挂载。当然,这也是为了确保可重复性而设计的。
  • 多平台构建有时会使用仿真,所以对于某些用例来说,它可能太慢。
  • 错误信息可能会变得非常混乱。

Error message

设置Earthly

Earthly的配置文件被称为,如软件工程中的典型,Earthfiles 。打开一个文件让我们想起了Dockerfiles和Makefiles的区别。

Earthly采用了Docker的语法,并将其扩展到一般的使用情况。虽然Dockerfiles只是为了生成容器镜像,但Earthly可以生成人工制品。

下面的Earthfile取自Earthly的官方指南。它描述了两个目标:builddocker

FROM golang:1.15-alpine3.13
WORKDIR /go-example

build:
    COPY main.go .
    RUN go build -o build/go-example main.go
    SAVE ARTIFACT build/go-example /go-example AS LOCAL build/go-example

docker:
    COPY +build/go-example .
    ENTRYPOINT ["/go-example/go-example"]
    SAVE IMAGE go-example:latest

build 目标在容器内编译一个 Go 程序,而docker 则将其复制到一个生产就绪的镜像中,可以推送到任何容器注册表并在任何地方运行。

如果你熟悉Dockerfiles的话,FROM 关键字的作用与你预期的一样。同样,COPYENTRYPOINTWORKDIRRUN ,也有类似的行为。有两个概念值得暂停一下,以进一步解释。

  • 目标:我们想要构建的东西。Earthly使用一个基于目标的系统,其灵感来自Make。
  • 引用:目标可以引用其他目标。就像在Make中一样,Earthly会对依赖关系进行分类并做正确的事情。
  • 扩展命令:Earthly具有专门的命令,如SAVE IMAGE和SAVE ARTIFACT。

要运行这个Earthfile,我们需要运行:earthly +TARGET 。第一次,Earthly会拉出图像并创建一个缓存卷。缓存将在两次运行之间持续保存构建文件:

$ earthly +build

           buildkitd | Found buildkit daemon as docker container (earthly-buildkitd)
golang:1.15-alpine3.13 | --> Load metadata linux/arm64
             context | --> local context .
               +base | --> FROM golang:1.15-alpine3.13
             context | transferred 1 file(s) for context . (2.1 MB, 4 file/dir stats)
               +base | *cached* --> WORKDIR /go-example
              +build | *cached* --> COPY main.go .
              +build | *cached* --> RUN go build -o build/go-example main.go
              output | --> exporting outputs
================================ SUCCESS [main] ================================
              +build | Artifact +build/go-example as local build/go-example

或者我们可以直接进入Docker生成目标用:

$ earthly +docker

               +base | --> FROM golang:1.15-alpine3.13
               +base | *cached* --> WORKDIR /go-example
             context | transferred 3 file(s) for context . (2.1 MB, 4 file/dir stats)
              +build | *cached* --> COPY main.go .
              +build | *cached* --> RUN go build -o build/go-example main.go
              +build | *cached* --> SAVE ARTIFACT build/go-example +build/go-example AS LOCAL build/go-example
             +docker | --> COPY +build/go-example ./
              output | [██████████] exporting layers ... 100%
              output | [          ] exporting manifest
================================ SUCCESS [main] ================================
             +docker | Image +docker as go-example:latest
              +build | Artifact +build/go-example as local build/go-example

由于docker 目标依赖于build ,Earthly首先运行构建步骤,然后继续构建并导出镜像到本地Docker目录。

扩展Earthfiles

让我们在一个更复杂的例子中试试Earthly。请继续前进,分叉这个演示仓库:

TomFern / semaphore-demo-earthly

提供的Dockerfile构建了一个Node Alpine容器镜像:

FROM node:14.17-alpine3.12
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm install
COPY src src
EXPOSE 3000
ENTRYPOINT ["npm", "start"]

你可以用:docker build . -t mydemo 构建它,然后以:docker run -it -p 3000:3000 mydemo 的方式运行它。浏览3000端口的localhost应该打印出 "hello, world "的信息。

我们将如何把它变成一个工作的Earthfile?让我们从头开始。

前两行保持不变,将用于每个目标:

FROM node:14.17-alpine3.12
WORKDIR /app

现在我们添加构建目标,它可以下载依赖项并复制源文件:

build:
    COPY package.json package-lock.json ./
    COPY --if-exists node_modules node_modules
    RUN npm install
    COPY src src

我们添加了一个COPY 语句,将node_modules文件夹复制到构建环境中。

由于npm在容器中运行,我们需要把它的输出文件复制到外面。我们用Earthly的特殊命令SAVE ARTIFACT ... AS LOCAL 来做这个:

    SAVE ARTIFACT node_modules AS LOCAL ./node_modules
    SAVE ARTIFACT package.json AS LOCAL ./package.json
    SAVE ARTIFACT package-lock.json AS LOCAL ./package-lock.json

运行earthly +build 应该可以安装Node的依赖关系。这个过程的最终结果类似于我们直接运行npm install ,还有一个好处是我们在任何地方都能得到同样的结果。

接下来,我们应该创建Docker镜像。我们可以通过以下方式实现:

docker:
    FROM +build
    EXPOSE 3000
    ENTRYPOINT ["npm", "start"]
    SAVE IMAGE semaphore-demo-earthly:latest

新的目标从构建停止的地方接续。它添加了启动命令并将镜像导出到主机的本地Docker注册表。

用Earthly测试

Earthly带来的最强大的功能之一是能够用测试来扩展Dockerfiles。让我们通过扩展build ,添加单元测试,运行目标是:npm test :

tests:
    FROM +build
    COPY spec spec
    RUN npm test

现在我们可以运行earthly +tests 来获得结果。

添加一个提示目标也是微不足道的:

lint:
    FROM +build
    COPY .jshintrc ./
    RUN npm run lint

为了运行更多的测试,如集成测试或端到端测试,我们可以重用现有的Docker Compose清单,以便在集成阶段调出其他容器:

integration-tests:
    FROM +build
    COPY docker-compose.yml ./
    COPY integration-tests integration-tests
    WITH DOCKER --compose docker-compose.yml --service db
        RUN sleep 10 && npm run integration-tests
    END

Earthly使用Docker-in-Docker方法来启动辅助容器:

  1. 在主容器(应用容器)中复制docker-compose.yml 和集成测试。
  2. 在主容器内启动一个PostgreSQL容器。特殊的WITH DOCKER 命令正好接受一个 RUN 语句。
  3. 运行集成测试。

我们的Earthfile现在已经足够好了。让我们把它上传到资源库,这样我们就可以在下一节中配置CI/CD管道:

$ git add Earthfile
$ git commit -m "push Earthfile"
$ git push origin master

地球上的持续集成管道

我们现在已经准备好用Earthly和持续集成来测试应用程序了。我们的管道将从Docker Hub拉入和推送,这就需要对服务进行认证。

Semaphore的认证是通过秘密进行的。要创建一个加密的秘密,进入你的组织菜单,点击设置 > 秘密。然后,按创建秘密,输入变量DOCKER_USERNAMEDOCKER_PASSWORD ,如下图所示:

Docker Hub secret

Docker Hub秘密

接下来,初始化分叉的演示仓库。添加项目的程序在入门指南中有详细说明。如果这是第一次使用Semaphore,请去看看。

由于Earthly没有预装在Ubuntu CI镜像中,我们需要为每个作业安装它。实现这一目标的最简单方法是使用管道级的序言。全局序言中的命令会在管道中的每个作业之前执行。

Linux下的Earthly安装命令是:

sudo /bin/sh -c 'wget https://github.com/earthly/earthly/releases/latest/download/earthly-linux-amd64 -O /usr/local/bin/earthly && chmod +x /usr/local/bin/earthly && /usr/local/bin/earthly bootstrap --with-autocomplete'

点击流水线,在序言部分输入这一行:

Pipeline config

构建块

第一个区块将开始一个构建阶段。在其中,我们将下载并缓存Node的依赖。

在作业中输入以下命令:

echo "${DOCKER_PASSWORD}" | docker login -u "${DOCKER_USERNAME}" --password-stdin
checkout
cache restore
earthly --ci +build
cache store

我们将在管道中的每个连续作业中运行这些命令或它们的变体:

  • docker login:用秘密中定义的凭证登录Docker Hub。
  • checkout:将版本库克隆到CI机器上。
  • cache:通过cache命令,我们可以在Semaphore提供的项目级存储中存储node_modules
  • earthly ci:用一些CI优化的设置运行Earthly。

Build block

为了完成这个块,启用 "dockerhub "秘密,所以变量被解密并在作业中导出。

测试块

下一个块将运行快速测试,如单元测试和linting。在该块的序言中添加以下命令。

echo "${DOCKER_PASSWORD}" | docker login -u "${DOCKER_USERNAME}" --password-stdin
checkout
cache restore

New block

接下来,创建两个作业:

earthly --ci +tests

和:

earthly --ci +lint

Test block

集成测试块

最后一个作业将运行集成测试。输入以下命令。-P 开关需要在Docker内部启用特权模式(Docker-in-Docker需要)。

echo "${DOCKER_PASSWORD}" | docker login -u "${DOCKER_USERNAME}" --password-stdin
checkout
cache restore
earthly --ci -P +integration-tests

Integration test block

不要忘记检查所有区块中的秘密是否被启用。准备好后,运行工作流以尝试CI管道。

Try CI pipeline

CI pipeline

CI管道

用Earthly持续交付

CI/CD流水线的最终结果是交付可以部署的东西。这里就是持续交付部分发挥作用的地方。我们将把图像推送到远程存储库,以便以后可以在生产中部署。

一个Semaphore工作流可以跨越多个管道,只要它们是通过促销活动连接的。回到工作流程编辑器中,点击添加推广。点击新的流水线,再次复制全局的Earthly安装命令。

sudo /bin/sh -c 'wget https://github.com/earthly/earthly/releases/latest/download/earthly-linux-amd64 -O /usr/local/bin/earthly && chmod +x /usr/local/bin/earthly && /usr/local/bin/earthly bootstrap --with-autocomplete'

管道中的区块将包含一个生成Docker镜像并将其推送到Docker Hub的单一作业。这些命令如下:

echo "${DOCKER_PASSWORD}" | docker login -u "${DOCKER_USERNAME}" --password-stdin
checkout
cache restore
earthly +docker
docker tag semaphore-demo-earthly $DOCKER_USERNAME/semaphore-demo-earthly
docker push $DOCKER_USERNAME/semaphore-demo-earthly

我们使用earthly +docker ,将镜像导出到CI机器中,然后用docker tagdocker push 的组合,将其发送到远程注册中心。

Push block

剩下的就是启用Docker Hub的秘密了......我们就完成了!

重新运行工作流程,点击推广,尝试推送镜像。

Final build

最终构建

你觉得怎么样?

Earthly项目还很年轻,正在大力开发,许多功能仍处于实验阶段(在文章的过程中我们坚持使用稳定的功能)。它并不完美,但它感觉是在正确的方向上迈出了一大步。

用这些教程提高你的Docker知识:

你尝试过Earthly吗?告诉我们你的经验。