首页 > 解决方案 > 检测 Terraform 中的 Ansible 更改并执行它们

问题描述

我想将 Ansible 与 Terraform 结合起来,以便 Terraform 创建机器,而 Ansible 将提供它们。使用terraform-provisioner-ansible可以将它们无缝地结合在一起。但是我看到了缺乏变更检测,当 Ansible 独立运行时不会发生这种情况。

TL;DR:如何将 Ansible 中所做的更改应用到 Terraform Ansible 插件?或者至少在每次更新时执行 ansible 插件,以便 Ansible 可以自己处理?

示例用例

考虑安装一些软件包的这个剧本

- name: Ansible install package test
  hosts: all
  tasks: 
  - name: Install cli tools
    become: yes
    apt:
      name: "{{ tools }}"
      update_cache: yes
    vars:
      tools:
        - nnn
        - htop

使用插件集成到 Terraform

resource "libvirt_domain" "ubuntu18" {
  # ...
  connection {
    type = "ssh"
    host = "192.168.2.2"
    user = "ubuntu"
    private_key = "${file("~/.ssh/id_rsa")}"
  }
  provisioner "ansible" {
    plays {
      enabled = true
      become_method = "sudo"

      playbook = {
        file_path = "ansible-test.yml" 
      }
    }
  }
}

第一次运行会很好。但后来我注意到一些包裹丢失了

- name: Ansible install package test
  hosts: all
  tasks: 
  - name: Install cli tools
    become: yes
    apt:
      name: "{{ tools }}"
      update_cache: yes
    vars:
      tools:
        - nnn
        - htop
        - vim # This is a new package

运行时terraform plan我会得到No changes. Infrastructure is up-to-date.我的新包vim永远不会安装!所以 Ansible 没有运行,因为如果 Ansible 运行,它会安装新包。

问题似乎是供应商本身

创建时供应商仅在创建期间运行,而不是在更新或任何其他生命周期期间运行。它们旨在作为执行系统引导的一种手段。

但是应用更新的正确方法是什么?我尝试了指向我的 vm 资源的null_ressourcewithdepends_on链接,但 Terraform 也没有检测到 Ansible 部分的更改。似乎缺少Terraform 插件的更改检测。

在文档中,我只发现破坏时间供应商。但没有更新。我可以摧毁并重新创造这台机器。这会使事情变慢很多。我喜欢 Ansible 检查什么是存在的方法,并且只应用尚未存在的更改,这似乎是一种很好的配置方式。

不能用 Terraform 做类似的事情吗?

以我目前的经验(比 Terraform 更 Ansible),我认为没有其他方法可以放弃漂亮的插件并自己执行 Ansible。但这也会放弃良好的集成。所以我需要自己甚至手动生成库存文件(在我看来这错过了自动化方法)。

source_code_hash可能是一个选项,但不灵活:当有多个播放/角色时,我需要为每个容易出错的单个文件手动执行此操作。

标签: ansibleterraformprovisioning

解决方案


使用null_ressource带有伪触发器的

tedsmitt的想法使用时间戳作为触发器,这似乎是强制提供者的唯一方法。ansible-playbook但是,从 CLI直接运行会产生手动维护库存的开销。您不能从这里调用python 动态库存脚本,因为 terraform apply需要先完成

在我看来,更好的方法是在这里运行ansible 配置器

resource "null_resource" "ansible-provisioner" {
  triggers {
      build_number = "${timestamp()}"
  }
  depends_on = ["libvirt_domain.ubuntu18"]

  connection {
    type = "ssh"
    host = "192.168.2.2"
    user = "ubuntu"
    private_key = "${file("~/.ssh/id_rsa")}"
  }
  provisioner "ansible" {
    plays {
      enabled = true
      become_method = "sudo"

      playbook = {
        file_path = "ansible-test.yml" 
      }
    }
  }
}

这里唯一的亮点是:Terraform 每次都会识别一个伪变化

Terraform will perform the following actions:

-/+ null_resource.ansible-provisioner (new resource required)
      id:                    "3365240528326363062" => <computed> (forces new resource)
      triggers.%:            "1" => "1"
      triggers.build_number: "2019-06-04T09:32:27Z" => "2019-06-04T09:34:17Z" (forces new resource)


Plan: 1 to add, 0 to change, 1 to destroy.

根据其他可用的解决方法,这对我来说似乎是最好的妥协。

使用动态清单手动运行 Ansible

我发现的另一种方法是动态库存插件,详细说明可以在此博客条目中找到。它集成到 Terraform 中,让您将资源指定为清单主机,例如:

resource "ansible_host" "k8s" {
  inventory_hostname = "192.168.2.2"
  groups             = ["test"]
  vars = {
    ansible_user = "ubuntu"
    ansible_ssh_private_key_file = "~/.ssh/id_rsa"
  }
}

Python 脚本使用此信息生成动态清单,可以这样使用:

ansible-playbook -i /etc/ansible/terraform.py ansible-test.yml

一个很大的好处是:它使您的配置保持干燥。Terraform 具有领先的配置文件,无需另外维护单独的 Ansible 文件。还有变量使用的能力(例如,库存主机名不应该像我的示例中那样为生产使用硬编码)。

在我的用例(Provision Rancher testcluster)中,这种null_ressource方法似乎更好,因为一切都是用一个 Terraform 命令构建的。无需额外执行 Ansible。但是根据要求,最好将 Ansible 保留一个单独的步骤,因此我将其发布为替代方案。

安装插件

尝试此解决方案时,请记住您需要从此处安装相应的 Terraform 插件:

version=0.0.4
wget https://github.com/nbering/terraform-provider-ansible/releases/download/v${version}/terraform-provider-ansible-linux_amd64.zip -O terraform-provisioner-ansible.zip
unzip terraform-provisioner-ansible.zip
chmod +x linux_amd64/*
mv linux_amd64 ~/.terraform.d/plugins

还要注意,需要首先删除上述解决方案中的自动配置器,因为它具有相同的名称(可能冲突)。


推荐阅读