Docker Machine 执行器弹性伸缩配置

弹性伸缩功能引入于极狐GitLab Runner 1.1.0。

弹性伸缩提供了一种以更灵活和动态的方式使用资源的能力。

极狐GitLab Runner 可以弹性伸缩,您的基础设施可以在任何时候包含必须数量的构建实例。如果您将您的极狐GitLab Runner 配置为仅使用弹性伸缩,安装 Runner 的系统会成为所有其创建的机器的堡垒机。这个机器被称为 “Runner Manager”。

noteDocker 废弃了 Docker Machine,一种用于在公有云虚拟机上弹性伸缩 Runner 的底层技术。

概览

当启用并正确配置了这个功能后,在按需创建的机器上就会执行作业。这些机器可以在作业完成后,运行下一个作业或者在配置的 IdleTime 后被移除。

系统要求

在配置弹性伸缩功能之前,您必须:

  • 准备您的环境.
  • 可选。使用极狐GitLab 提供的 Docker Machine 的派生版本,它有一些额外的修复。

支持的云提供商

弹性伸缩机制基于 Docker Machine

Runner 配置

本部分描述了重要的弹性伸缩参数。更多关于配置细节的信息,请参见高级配置

Runner 全局选项

参数 描述
concurrent integer 全局限制可以并发运行的作业的数量。这是使用所有定义的 Runner、本地和弹性伸缩的作业的最大值。它与 limit ([[runners]] 部分) 和 IdleCount ([runners.machine] 部分)一起影响创建的机器的上限。

[[runners]] 选项

参数 描述
executor string 如果想使用弹性伸缩功能, executor 必须被设置为 docker+machinedocker-ssh+machine
limit integer 限制可以被特定令牌并发处理的作业数量。0 表示不作限制。 对于弹性伸缩,数量为此供应商(与 concurrentIdleCount 一起)创建的机器数量的上限。

[runners.machine] 选项

可以在极狐GitLab Runner - 高级配置 - [runners.machine] 部分查看配置参数详细信息。

[runners.cache] 选项

可以在极狐GitLab Runner - 高级配置 - [runners.cache] 部分查看配置参数详细信息。

附加配置信息

当您设置 IdleCount = 0 时,还有一种特殊的模式。在这种模式下,机器永远在每个作业前按需创建(如果在闲置状态下没有可用机器)。作业完成后,弹性伸缩功能就会生效, 和下面描述的一样。 机器会等待下一个作业,如果没有需要执行的作业,经过 IdleTime 时间后,这个机器会被移除。没有作业的时候,闲置状态下不会有机器。

如果 IdleCount 设置为比 0 大的数字,则会在后台创建闲置的虚拟机。Runner 在申请处理新作业之前会获得一个现存的闲置虚拟机。

  • 如果把作业分配给 Runner,这个作业会被发送到之前获得的那台虚拟机。
  • 如果没有把作业分配给 Runner,那么闲置虚拟机上的锁会打开,虚拟机会被释放到资源池中。

限制 Docker Machine 执行器创建的虚拟机的数量

为了限制 Docker Machine 执行器创建的虚拟机的数量,我们在 config.toml 文件的 [[runners]] 部分中使用 limit 参数。

concurrent 参数限制虚拟机的数量。

下面的例子展示了 config.toml 文件中为 Runner 进程设置的值:

concurrent = 100

[[runners]]
name = "first"
executor = "shell"
limit = 40
(...)

[[runners]]
name = "second"
executor = "docker+machine"
limit = 30
(...)

[[runners]]
name = "third"
executor = "ssh"
limit = 10

[[runners]]
name = "fourth"
executor = "virtualbox"
limit = 20
(...)

通过这个配置:

  • 一个 Runner 进程可以创建四个不同的 Runner Worker,且他们使用不同的执行环境。
  • concurrent 值设置为 100,所以这个 Runner 会执行最多 100 个并发极狐GitLab CI/CD 作业。
  • 只有 second Runner Worker 被配置为使用 Docker Machine 执行器,因此可以自动创建虚拟机。
  • limit 设置为 30 表示 second Runner Worker 可以在任意时间,在弹性伸缩虚拟机上执行最多 30 个 CI/CD 作业。
  • 尽管 concurrent 定义了多个 [[runners]] worker 上全局并发的限制,limit 定义了单个 [[runners]] worker 的最大并发。

在这个例子中,Runner 进程处理:

  • 对于所有 [[runners]] worker,多达 100 个并发作业。
  • 对于 first worker,使用 shell 执行器处理不多于 40 个作业。
  • 对于 second worker, 使用 docker+machine 执行器处理不多于 30 个作业。此外,runner 会基于 [runners.machine] 中的弹性伸缩配置维护虚拟机,但是所有状态(闲置、使用中、创建中、移除中)的虚拟机的数量不超过 30 台。
  • 对于 third worker, 使用 ssh 执行器处理不多于 10 个作业。
  • 对于 fourth worker, 使用 virtualbox 执行器处理不多于 20 个作业。

在第二个例子中,配置了两个 [[runners]] worker 以使用 docker+machine 执行器。通过这个配置,每个 Runner Worker 管理一个独立的虚拟机资源池,虚拟机受 limit 参数的值的限制。

concurrent = 100

[[runners]]
name = "first"
executor = "docker+machine"
limit = 80
(...)

[[runners]]
name = "second"
executor = "docker+machine"
limit = 50
(...)

在这个例子中:

  • 处理不多于 100 个作业 (concurrent 的值)。
  • Runner 进程处理两个 [[runners]] worker 中的作业,每个都使用 docker+machine 执行器。
  • first Runner 可以创建最多 80 个虚拟机,因此这个 Runner 可以在任意时间执行最多 80 个作业。
  • second Runner 可以创建最多 50 个虚拟机,因此这个 Runner 可以在任意时间执行最多 50 个作业。
note尽管限制值的和是 130 (80 + 50 = 130),但是全局级别的concurrent 的值 100 表示这个 Runner 进程可以并发执行最多 100 个作业。

弹性伸缩算法和参数

弹性伸缩算法基于以下参数:

  • IdleCount
  • IdleCountMin
  • IdleScaleFactor
  • IdleTime
  • MaxGrowthRate
  • limit

每个不运行作业的机器都处于闲置状态。当极狐GitLab Runner 在弹性伸缩模式下,它会监控所有机器并确保一直有机器的 IdleCount 处于闲置状态。

note我们在极狐GitLab Runner 14.5 中添加了 IdleScaleFactorIdleCountMin 设置,改变了一些这种行为。

如果闲置机器数量不足,极狐GitLab Runner 就会依据 MaxGrowthRate 的限制部署新的机器。 如果申请的机器的数量超过 MaxGrowthRate 的值,申请流程会暂停,直到创建的机器的数量低于 MaxGrowthRate

同时,极狐GitLab Runner 检查每台机器处于闲置状态的时间。 如果时间超过 IdleTime 的值,会自动移除机器。


示例: 我们假设已经使用以下弹性伸缩参数配置了极狐GitLab Runner:

[[runners]]
  limit = 10
  # (...)
  executor = "docker+machine"
  [runners.machine]
    MaxGrowthRate = 1
    IdleCount = 2
    IdleTime = 1800
    # (...)

最初没有作业排队的时候,极狐GitLab Runner 开启 2 台机器 (IdleCount = 2),并将他们设置成闲置状态。注意,我们也同样将 IdleTime 设置成 30 分钟 (IdleTime = 1800)。

现在我们假设在极狐GitLab CI 中有 5 个作业正在排队。前两个作业被发送到闲置状态的机器,这个状态的机器有 2 台。 极狐GitLab Runner 现在注意到闲置的数量少于 IdleCount (0 < 2),所以它会开启新机器。这些机器按顺序分配,以防超过 MaxGrowthRate 的值。

余下的 3 个作业被分配到准备好的第一台机器。作为优化, 它可以是一台忙碌状态的机器,但是已经执行完了作业,或是一台新分配的机器。我们假设机器分配过程很快,并且新机器的分配在之前的作业完成之前就已经完成了。

我们现在有 1 台闲置机器,所以极狐GitLab Runner 开启另一个新机器去满足 IdleCount。因为有新作业在排队,那 2 个机器会保持闲置状态并且极狐GitLab Runner 也被满足。


会发生如下情况: 我们有 2 台闲置状态的机器等待新作业。5 个作业排队后,创建了新作业,所以我们一共有 7 台机器。 其中 5 台在运行作业,其中 2 台处于闲置状态,等待下个作业。

算法也是同样的方式;极狐GitLab Runner 为每台用于执行作业的机器创建一台新的闲置机器,直到满足 IdleCount。 创建机器的数量受 limit 参数的限制。如果极狐GitLab Runner 注意到创建的机器的数量存在 limit 限制,它就会停止弹性伸缩,并且新作业必须在作业队列中排队,直到机器开始返回闲置状态。

在上面的例子中,我们一直都有 2 台闲置的机器。IdleTime 只有在超过 IdleCount 时适用。然后我们试图将机器的数量减少到 IdleCount


向下伸缩 作业完成后,机器被设置为闲置状态,并等待执行下一个作业。我们假设队列中没有新的作业。 在 IdleTime 指定的时间过去后,闲置机器被移除。在我们的例子中,30 分钟后,所有的机器都被移除 (最后一个作业执行完毕后的 30 分钟),并且极狐GitLab Runner 开始保持闲置机器的 IdleCount 运行,就像例子开头那样。


总结:

  1. 启动极狐GitLab Runner。
  2. 极狐GitLab Runner 创建 2 台闲置机器。
  3. 极狐GitLab Runner 选择 1 个作业。
  4. 极狐GitLab Runner 创建 1 台机器,满足一直要有 2 台闲置机器的强烈要求。
  5. 作业完成后,拥有 3 台闲置机器。
  6. 当 3 台闲置机器中的 1 台从最后一次选择作业的时候算起超过了 IdleTime, 会被移除。
  7. 极狐GitLab Runner 一直有至少 2 台闲置机器等待作业。

下面是作业状态和机器状态的比较图表。

Autoscale state chart

concurrentlimitIdleCount 怎样生成运行机器的上限

没有魔法方程告诉您怎样设置 limitconcurrent。根据您的需求操作。闲置机器的 IdleCount 是一个加速功能。您不必等待 10s/20s/30s 创建实例。但是作为一个用户,您希望您的所有机器(您付费的)都运行作业,而不是处于闲置状态。所以您应该将 concurrentlimit 设置为您愿意支付的机器的最大数量的值。 至于 IdleCount,应该设置为当队列中没有作业时,生成未使用机器的最小数量的值。

我们假设下面的例子:

concurrent=20

[[runners]]
  limit = 40
  [runners.machine]
    IdleCount = 10

在上面的场景中,我们可以拥有的机器总数是 30。 所有机器(构建中和闲置)的 limit 可以是 40。我们可以拥有 10 台闲置的机器,但是 concurrent 作业是 20。所以我们一共可以拥有 20 台并发机器运行作业和 10 台闲置机器,总计 30 台。

但是当 limit 小于可以创建的机器的总数时会发生什么呢? 下面举例说明:

concurrent=20

[[runners]]
  limit = 25
  [runners.machine]
    IdleCount = 10

在这个例子中,您可以拥有最多 20 个并发作业和 25 台机器。 最坏的情况下,您不可以拥有 10 台闲置机器,只能有 5 台,因为 limit 是 25。

IdleScaleFactor 策略

IdleCount 参数定义了 Runner 应该保持的闲置机器的静态数量。 您赋的值取决于您的用例。

您开始可以分配一个相对较少的闲置状态的机器,并让它们根据当前使用情况自动适应更大的数量。为达到这个效果,您需要使用实验性的 IdleScaleFactor 设置。

cautionIdleScaleFactor 内部是一个 float64 值,需要使用浮点格式。 例如: 0.01.01.5 等。如果使用了整数格式(例如 IdleScaleFactor = 1), Runner 的进程会失败报错: FATAL: Service run failed error=toml: cannot load TOML value of type int64 into a Go float

当您使用这个设置,极狐GitLab Runner 会尽量保持一定数量的处于闲置状态的机器。 然而,这个数量并不是静态的。极狐GitLab Runner 不再使用 IdleCount, 而是检查正在使用中的机器的数量,并将期望的闲置容量定义为那个数量的一个因素。

当然,如果没有当前使用的机器,IdleScaleFactor 会取值为没有需要保持的闲置机器。 由于弹性伸缩算法的工作方式,如果 IdleCount 大于 0 (只有那个时候 IdleScaleFactor 才适用),Runner 不会在没有能够处理作业的闲置机器的情况下要求处理作业。没有新作业,使用的机器的数量就不会增加,所以 IdleScaleFactor 会一直等于 0。这也会将 Runner 阻塞到一种不可用的状态。

因此,我们引入了第二种设置: IdleCountMin。它定义了无论 IdleScaleFactor 的值为多少, 需要保持的闲置机器的最小数量。如果使用了 IdleScaleFactor,那么这个设置不可以设置为小于 1。如果设置为 1,Runner 会自动将它设置为 1。

您也可以使用 IdleCountMin 定义应该一直可用的闲置机器的最小数量。 这允许新作业进入队列快速启动。 至于 IdleCount,您赋的值取决于您的用例。

示例:

concurrent=200

[[runners]]
  limit = 200
  [runners.machine]
    IdleCount = 100
    IdleCountMin = 10
    IdleScaleFactor = 1.1

在这种情况下,当 Runner 接近了决策点,它会检查当前使用的机器的数量。 假设当前有 5 台闲置机器和 10 台正在使用的机器。 乘以 IdleScaleFactor, Runner 决定应该有 11 台闲置机器,所以它会再创建 6 台。

如果您有 90 台闲置机器和 100 台正在使用的机器。基于 IdleScaleFactor,极狐GitLab Runner 判断应该有 100 * 1.1 = 110闲置机器。 因此它又会创建新机器。然而,当达到 100闲置机器时,它会发现这个数量是 IdleCount 定义的上限, 就不会再创建新的闲置机器了。

如果正在使用的闲置机器的数量从 100 下降到了 20,期望的闲置机器的数量是 20 * 1.1 = 22。 极狐GitLab Runner 会慢慢开始关闭机器。如上所述,极狐GitLab Runner 会移除不用于 IdleTime的机器。因此,不会大幅度地移除太多闲置虚拟机。

如果闲置机器的数量将为 0, 期望的闲置机器的数量是 0 * 1.1 = 0。 然而,这个数量小于定义的 IdleCountMin 设置,所以 Runner 会慢慢开始移除闲置虚拟机, 只保留 10 台。这个点过后,向下伸缩会停止,Runner 保持拥有 10 台闲置状态的机器。

弹性伸缩时间段配置

引入于极狐GitLab Runner 13.0。

弹性伸缩可以配置为,在不同时间段拥有不同的值。 组织机构可能会在固定的时间大量处理作业, 其他时间作业数量很少。 例如,大多数商业公司的工作时间为周一到周五的固定时间,比如上午 9 点到下午 6 点。 晚上和周末不会启动流水线。

您可以通过 [[runners.machine.autoscaling]] 部分配置时间段。 每个都支持基于一组 Periods 设置 IdleCountIdleTime

弹性伸缩时间段如何工作

[runners.machine] 设置中,您可以添加多个 [[runners.machine.autoscaling]] 部分,每个都有它自己的 IdleCountIdleTimePeriodsTimezone 属性。每个配置都应该定义一个部分,按顺序从最普通的场景到最具体的场景。

所有的部分都被解析。最后一个匹配当前时间的部分是活跃的。如果没有匹配的部分,会使用 [runners.machine] 的根的值。

示例:

[runners.machine]
  MachineName = "auto-scale-%s"
  MachineDriver = "google"
  IdleCount = 10
  IdleTime = 1800
  [[runners.machine.autoscaling]]
    Periods = ["* * 9-17 * * mon-fri *"]
    IdleCount = 50
    IdleTime = 3600
    Timezone = "UTC"
  [[runners.machine.autoscaling]]
    Periods = ["* * * * * sat,sun *"]
    IdleCount = 5
    IdleTime = 60
    Timezone = "UTC"

在本配置中,每个工作日的 9:00 点和 16:59 点(UTC),超量部署了机器,用以在运行时间处理大量流量。在周末, IdleCount 为适应流量下降,下降为 5。 其余时间,使用根 IdleCount = 10IdleTime = 1800 中的默认值。

note您指定的任何时间段中最后一分钟的第 59 秒都 被视为这个时间段中的时间。

您可以指定时间段中的 Timezone,例如 "Australia/Sydney"。如果您没有指定, 会使用每个 Runner 的主机机器的系统设置。 这个默认值可以被明确描述为 Timezone = "Local"

更多关于 [[runner.machine.autoscaling]] 部分语法的内容,请参见极狐GitLab Runner - 高级配置 - [runners.machine] 部分

非峰值时间模式配置(废弃)

这个设置已被废弃,并于极狐GitLab Runner 14.0 移除。

弹性伸缩不可以再配置非峰值模式时间段,而是将其转化为弹性伸缩时间段。

将非峰值配置转化为弹性伸缩配置

将非峰值配置转化为弹性伸缩配置,创建 [[runners.machine.autoscaling]]部分,并按以下规则添加内容:

  • Periods 字段从 OffpeakPeriods 取值。
  • IdleCount 字段从 OffpeakIdleCount 取值。
  • IdleTime 字段从 OffpeakIdleTime 取值。
  • Timezone 字段从 OffpeakTimezone 取值。

通过例子展示以下非峰值配置的转化:

[runners.machine]
  MachineName = "auto-scale-%s"
  MachineDriver = "google"
  IdleCount = 10
  IdleTime = 1800
  OffPeakPeriods = ["* * 9-17 * * mon-fri *"]
  OffPeakIdleCount = 50
  OffPeakIdleTime = 3600
  OffPeakTimezone = "UTC"

转化结果:

[runners.machine]
  MachineName = "auto-scale-%s"
  MachineDriver = "google"
  IdleCount = 10
  IdleTime = 1800
  [[runners.machine.autoscaling]]
    Periods = ["* * 9-17 * * mon-fri *"]
    IdleCount = 50
    IdleTime = 3600
    Timezone = "UTC"

分布式 Runner 缓存

note阅读如何使用分布式缓存

为加快您的作业,极狐GitLab Runner 提供了缓存机制, 用以将选中的目录和/或文件在后续作业中保存和共享。

当作业在同一个主机上运行时,这种方式没有问题。但是当您开始使用极狐GitLab Runner 弹性伸缩功能时,您的大部分作业都会在新(或几乎新的)主机上运行,这些主机在新 Docker 容器上执行作业。在这种情况下,您就无法使用缓存功能。

为解决这个问题,引入了分布式 Runner 缓存功能和弹性伸缩功能。

本功能使用配置的对象存储服务器在使用的 Docker 主机上共享缓存。 极狐GitLab Runner 查询服务器,下载这些归档以还原缓存,或者将其上传以归档缓存。

启用分布式缓存,您需要使用 [runners.cache] 指示,在 config.toml 中对其进行定义。

[[runners]]
  limit = 10
  executor = "docker+machine"
  [runners.cache]
    Type = "s3"
    Path = "path/to/prefix"
    Shared = false
    [runners.cache.s3]
      ServerAddress = "s3.example.com"
      AccessKey = "access-key"
      SecretKey = "secret-key"
      BucketName = "runner"
      Insecure = false

在上面的例子中,S3 URL 遵循这个结构 http(s)://<ServerAddress>/<BucketName>/<Path>/runner/<runner-id>/project/<id>/<cache-key>

在两个或多个 Runner 之间共享缓存,将 Shared 标志设置为 true。 这个标志从 URL (runner/<runner-id>)中移除了 Runner 令牌, 所有配置的 Runner 共享同一个缓存。 您也可以设置 Path,当启用共享缓存时,在 Runner 中分离缓存。

分布式容器镜像库镜像

为加快 Docker 容器内执行的作业,您可以使用 Docker 库镜像服务。这个服务在 Docker 机和所有使用的镜像库之间提供了代理。 镜像库镜像一次将图像下载下来。 在每个新主机上,或在镜像不可用的现存的主机上,图像从配置的镜像库镜像上下载下来。

如果镜像在您的 Docker 机 LAN 中,图像下载步骤在主机上会快很多。

配置 Docker 镜像库镜像,您必须向 config.toml 中的配置添加 MachineOptions

[[runners]]
  limit = 10
  executor = "docker+machine"
  [runners.machine]
    (...)
    MachineOptions = [
      (...)
      "engine-registry-mirror=http://10.11.12.13:12345"
    ]

10.11.12.13:12345 是 IP 地址和端口号,您的镜像库镜像正在侦听来自 Docker 服务的连接。Docker Machine 创建的主机必须可以访问。

详情请参见使用容器代理

config.toml 的完整示例

下面的 config.toml 使用 google Docker 机驱动

concurrent = 50   # All registered runners can run up to 50 concurrent jobs

[[runners]]
  url = "https://gitlab.com"
  token = "RUNNER_TOKEN"             # Note this is different from the registration token used by `gitlab-runner register`
  name = "autoscale-runner"
  executor = "docker+machine"        # This runner is using the 'docker+machine' executor
  limit = 10                         # This runner can execute up to 10 jobs (created machines)
  [runners.docker]
    image = "ruby:2.7"               # The default image used for jobs is 'ruby:2.7'
  [runners.machine]
    IdleCount = 5                    # There must be 5 machines in Idle state - when Off Peak time mode is off
    IdleTime = 600                   # Each machine can be in Idle state up to 600 seconds (after this it will be removed) - when Off Peak time mode is off
    MaxBuilds = 100                  # Each machine can handle up to 100 jobs in a row (after this it will be removed)
    MachineName = "auto-scale-%s"    # Each machine will have a unique name ('%s' is required)
    MachineDriver = "google" # Refer to Docker Machine docs on how to authenticate: https://docs.docker.com/machine/drivers/gce/#credentials
    MachineOptions = [
      "google-project=GOOGLE-PROJECT-ID",
      "google-zone=GOOGLE-ZONE", # e.g. 'us-central-1'
      "google-machine-type=GOOGLE-MACHINE-TYPE", # e.g. 'n1-standard-8'
      "google-machine-image=ubuntu-os-cloud/global/images/family/ubuntu-1804-lts",
      "google-username=root",
      "google-use-internal-ip",
      "engine-registry-mirror=https://mirror.gcr.io"
    ]
    [[runners.machine.autoscaling]]  # Define periods with different settings
      Periods = ["* * 9-17 * * mon-fri *"] # Every workday between 9 and 17 UTC
      IdleCount = 50
      IdleCountMin = 5
      IdleScaleFactor = 1.5 # Means that current number of Idle machines will be 1.5*in-use machines,
                            # no more than 50 (the value of IdleCount) and no less than 5 (the value of IdleCountMin) 
      IdleTime = 3600
      Timezone = "UTC"
    [[runners.machine.autoscaling]]
      Periods = ["* * * * * sat,sun *"] # During the weekends
      IdleCount = 5
      IdleTime = 60
      Timezone = "UTC"
  [runners.cache]
    Type = "s3"
    [runners.cache.s3]
      ServerAddress = "s3.eu-west-1.amazonaws.com"
      AccessKey = "AMAZON_S3_ACCESS_KEY"
      SecretKey = "AMAZON_S3_SECRET_KEY"
      BucketName = "runner"
      Insecure = false

注意 MachineOptions 参数包含 google 驱动的选项,Docker Machine 使用该驱动生成托管在 Google Compute Engine 上的机器,和 Docker Machine 自身的选项(engine-registry-mirror)。