如何使用Packer和Terraform管理镜像版本?

3
我目前使用运行在裸机节点上经由Ansible配置的Kubernetes集群。计划迁移到云端,并为此阅读Terraform和Packer的相关内容。除了数据迁移之外,似乎我们有一个相当直接的迁移路径:
1. 使用现有的Ansible脚本构建图像。 2. 通过Terraform将构建的镜像部署到云端。 3. 使用当前的工具部署我们的Kubernetes资源。
这非常棒。现在我们拥有了使用最先进工具的不可变基础设施。
我遇到的问题是如何对通过Packer构建的图像进行版本控制。在这些图像中,我们必须升级一些软件。有时,Ansible脚本会更改,但有时只是需要最新的安全更新。无论哪种情况,Packer都必须为我们构建新的镜像,我们必须通过Terraform部署它。如果新的镜像出现问题,我们将不得不回退到旧版。
我可以想象通过编辑模板并更改terraform配置来手动完成此操作,但这不适用于CI/CD流水线。另一个问题是我们可能会在不同的区域和服务商之间移动。因此,某个版本的镜像可能存在于一个区域中,而在另一个区域中则不存在,理想情况下,流水线应创建镜像(如果不存在)并使用现有镜像(如果已经存在)。这可能会导致不同区域或云中的映像不同,特别是因为它们可能在不同日期构建,并且应用了不同的安全更新。
所有这些都内置于Docker工作流程中,但是使用Packer远非显而易见。我找不到任何涵盖此主题的文档或教程。Packer和Terraform中是否有内置的版本控制功能?如果缺少图像,则Terraform能够调用Packer吗?是否有公认的最佳实践?
我可以想象通过使用云提供商的API检查所需镜像是否存在并在执行Terraform之前为任何缺失的镜像调用Packer来自动化此过程。这将起作用,但我不想为每个云提供商编写自定义集成,并且听起来应该由Terraform提供。我以前没有使用过Terraform,所以也许我不知道该去哪里寻找,也许在Terraform中实现它并不难,但是为什么没有任何教程向我展示呢?

我刚发现了这个链接:https://www.davidbegin.com/packer-and-terraform/,看起来这可能是一个未解决的问题,人们会想出自己的解决方案。如果您正在生产中使用Terraform和Packer,请告诉我您是如何处理这个问题的。 - Erik B
版本控制并没有真正内置到Packer中,因为它集成了多个提供程序、配置管理工具等。每个都有自己特定的版本控制。例如,您提到Docker有这个功能,因为它是Packer可以集成的一个特定提供程序之一。Virtualbox、AWS、GCP、AZR、VMWare等都是其他具有自己版本控制的提供程序。还有很多问题需要解决,但这就是为什么您在Packer中找不到该功能的原因。 - Matt Schuchard
@MattSchuchard 我不明白为什么Packer不能统一版本控制,或者为什么Terraform不能像处理其他云资源一样处理镜像,并将Packer视为这些镜像的提供程序。然后我们使我们的虚拟机依赖于这些镜像,以便在启动虚拟机之前必须创建/存在这些镜像。对我来说,这似乎是显而易见的工作流程,也是我所期望的。我觉得使用另一个工具来构建这种依赖关系图是没有意义的。 - Erik B
3个回答

1
这在很大程度上取决于提供商,你没有指定你使用的云提供商,但 AWS 在这里是一个很好的例子。
Terraform 和 Packer 都有一种方式来选择与筛选器匹配的最新 AMI。
Packer 的 AWS AMI 构建器使用 source_ami_filter 可以用于选择最新的镜像来创建你的镜像。amazon-ebs builder documentation 中给出了一个示例。
{
  "source_ami_filter": {
    "filters": {
      "virtualization-type": "hvm",
      "name": "ubuntu/images/\*ubuntu-xenial-16.04-amd64-server-\*",
      "root-device-type": "ebs"
    },
    "owners": ["099720109477"],
    "most_recent": true
  }
}

一个典型的情况是始终使用最新的官方Ubuntu镜像进行构建。如果您为不同的用例(例如Kubernetes工作节点与etcd节点)生成多个AMI,则可以从此处开始构建,以创建具有已知命名方案(例如ubuntu/20.04/base/{{isotime | clean_resource_name}})的黄金基础映像,其中包含您要在每个AMI中使用的所有内容,然后其他AMI也可以使用source_ami_filter选择您发布的最新基本AMI。
Terraform的AWS提供程序具有aws_ami数据源,其工作方式相同,可以用于自动选择与过滤器匹配的最新AMI,以便发布新AMI,然后运行Terraform将生成计划以替换引用AMI数据源的实例或启动配置/模板的实例。
示例在aws_instance资源文档中给出。
data "aws_ami" "ubuntu" {
  most_recent = true

  filter {
    name   = "name"
    values = ["ubuntu/images/hvm-ssd/ubuntu-trusty-14.04-amd64-server-*"]
  }

  filter {
    name   = "virtualization-type"
    values = ["hvm"]
  }

  owners = ["099720109477"] # Canonical
}

resource "aws_instance" "web" {
  ami           = "${data.aws_ami.ubuntu.id}"
  instance_type = "t2.micro"

  tags = {
    Name = "HelloWorld"
  }
}

一般情况下,您应该依赖这些机制来自动选择最近发布的AMI,并使用它来代替在代码中硬编码AMI ID。
管理镜像的生命周期超出了 Packer 本身的范围,应将其作为更大系统的一部分使用。如果您想要回滚镜像,则有两个可用选项:
  • 如果您的构建是可重现的,并且问题在于可重复的事物中,则可以构建和注册一个新镜像,其中包含旧代码,以便您的最新镜像现在与 2 个镜像之前的镜像相同
  • 取消注册最新的镜像,这样在搜索最新镜像时将开始重新获取旧镜像。这因云提供商而异,但在 AWS 中可以通过编程方式完成,例如通过 aws ec2 deregister-image 命令行
虽然 Packer 可以自动将镜像复制到不同的区域(请参见 AWS 的 ami_regions)和不同的账户(使用 ami_users 将创建的 AMI 与其他账户共享或使用 post processor 在不同的账户中创建单独的副本),但它无法轻松地按条件执行操作,除非您针对每种共享方式都有不同的 Packer 配置文件,并且无法将发布分离出来,使您在发布到生产账户之前先发布到非生产账户等。
如果您想在某些账户和区域中滚动出 AMI,而不是全部滚动,则需要将该逻辑放在更高的位置,例如 CI/CD 系统这样的编排机制中。

谢谢,这个答案确实很有用。我还缺少的一件事是Packer如何知道是否需要构建镜像。Docker具有缓存层,仅在更改时重新构建,但似乎Packer每次都会从头开始构建新的镜像,而不管是否有任何更改。另一件事是如何将图像从暂存推广到生产环境。如果您有一个自动扩展的K8s集群,您不希望它启动未经管道测试的图像节点。您是否必须在Terraform/Packer之外重命名图像来处理此问题? - Erik B
它并不知道是否发生了任何更改,因此如果您关心这一点,您必须在使用Packer之前处理它。正如我在答案中提到的,您还需要单独处理推广图像。Packer非常专注于构建图像。由您告诉它何时构建图像以及图像应发布在哪里。 - ydaetskcoR
我个人会同时向我们所有的账户发布 AMIs,然后我们的 CI/CD 系统会调用 Terraform 先将更改应用于我们的非生产系统,最终再在后续步骤中推出到生产环境(目前需要手动批准此步骤,但如果您对测试流程有信心,也可以自动化)。 - ydaetskcoR
这就是它的工作原理。您还可以使用aws_ami资源在Terraform中直接创建该AMI,但这不如在Packer中创建图像并将其解耦好。根据您提到的要求,您无论如何都需要处理编排逻辑,以逐步在不同帐户和区域中推出该映像,因此应在Terraform之外的某个地方对其进行编码,并且在那一点上,您可以以任何方式处理链接Packer和Terraform。 - ydaetskcoR
@ErikB,请不要在配置后进行配置。Packer可以在构建过程中启动配置工具,例如类型为salt的provisioner。大多数人生成AMIs的方式是使用DevOps团队管理的基本AMI,并在此AMI之上使用packer进行构建。例如,您可以使用预安装常用软件包的Centos7 AMI,并将其用作依赖团队的web_applications配置的source_ami。请还要考虑图像管理,以避免出现25,000个未使用的AMI的情况。 - Daniel Hajduk
显示剩余4条评论

1
我写了一篇关于这个主题的博客文章,保持 Packer 和 Terraform 镜像版本化、同步和 DRY

总结如下:

我们的目标

  • Packer 构建的镜像必须有版本号。
  • 镜像版本控制必须是 DRY 的(只保存在一个地方),并在 Packer 和 Terraform 之间共享,以避免错误地失去同步。
  • Packer、Terraform 和镜像版本信息的配置文件必须存储在 git 中,这样检出特定提交并执行 terraform apply 应该足以执行回滚。
  • Terraform 必须仅基于本地信息自动检测到一个或多个镜像的更新版本,或者应该构建一个更新版本。
  • 必须可以拥有 N 个独立的开发/预发布环境,其中镜像版本控制与生产环境无关。
  • 方法必须是 IaaS 不可知的(必须适用于任何云提供商)。

方法概述

使用命名约定,例如

<IMAGE-NAME> ::= <ROLE>__<BRANCH>-<REVISION>

在单独的文件packer/versions.pkvars.hcl中定义变量的值:
service-a-img-name = "service-a__main-3"

使用以下命令构建图像:

$ packer build -var-file=versions.pkrvars.hcl minimal.pkr.hcl

在 Terraform 方面,由于文件 packer/versions.pkvars.hcl 是 HCL 格式的,我们可以从 Terraform 中读取它:
$ terraform apply -var-file=../../packer/versions.pkrvars.hcl

所有细节都在上述博客文章中。

0

就算只是一点点价值,图像版本控制也很有用,因为你可以保存一些默认设置,例如Kubernetes主机节点(预下载的Docker镜像等),这样在通过AWS检查之前,它已经加入了集群。

我已经为许多应用程序做过这个操作,并发现最好的做法通常是像下面这样:

vendor-app-appversion-epoch

这种方法可以让您对Ami和应用程序进行版本控制,然后您可以像对待牲畜(将被屠杀)而不是宠物(需终身照料)一样处理您的实例。

data "aws_ami" "amazon_linux2" {
  most_recent = true
  filter {
    name = "name"
    values = ["amzn2-ami-*-x86_64-gp2"]
  }

  filter {
    name = "virtualization-type"
    values = ["hvm"]
  }

  owners = ["amazon"]
}

当您应用terraform时,它将拉取linux2的最新镜像。


很抱歉,我不认为这回答了我的问题。你似乎在建议一种命名约定而非版本控制策略。你也没有提到 Terraform 和 Packer 之间的交互。如果你是在建议我手动将 Packer 输出的镜像名称复制到 Terraform 的变量中,那么这个答案对我没有任何帮助,因为这正是我想避免的。 - Erik B
好的,它不允许我在这里放置格式化文本,所以我进行了详细说明。是的,这是一种命名结构,但它允许您始终获取最新的内容。 - hikerspath

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接