前言

Terraform 和 Ansible 是两个独立的东西,有各自的用处,但它们能够集成以解决典型用例以及它们彼此弥补的方法,这使得它们愈加受欢迎。

一 东西简介

1.1 Terraform

它是作为代码(IaC)软件东西的基础设备,用于构建、更改和版别操控基础设备。它与500多个供货商协作,这些供货商的资源可用于供给基础设备。

1.2 Ansible

它是一种装备办理东西,可在已设置的基础架构上装备和布置应用程序时派上用场。在本文中,咱们运用 Ansible 装备了 terraform 布置的基础设备。

本文将运用Terraform对腾讯云基础设备资源进行编列办理,运用Ansible对主机实例进行装备办理。

二 架构及流程

Terraform 集成Ansible 实战(remote)

总体方案运用terraform来进行云上基础设备编列,运用ansible进行主机或其他设备的装备办理。

Terraform 集成Ansible 实战(remote)

编列出一台独立的长途ansible manager 主机作为ansible 装备办理的办理服务器,需求后续在其上进行ansible 装备、密钥下发,及生成管控方针服务器的invertory主机清单,对需求管控的node节点需求在该服务器进行操作。

三 实战

3.1 编列Ansible manager节点

运用本地密钥编列ansible节点

  • 装置ansible
  • 将ansible的装备文件传输到ansible manger的特定目录
locals {
  cidr_block_vpc_name    = "10.0.0.0/16"
  cidr_block_subnet_name = "10.0.1.0/24"
  count                  = 1
  username = "root"
  local_key_file = "id_rsa"
  local_ansible_config = "ansible.cfg"
  ansible_dir = "/opt/ansibleworkspace/"
  key_file = "/Users/xuel/.ssh/id_rsa"
}
data "tencentcloud_images" "image" {
  image_type = ["PUBLIC_IMAGE"]
  os_name    = "centos 7.5"
}
data "tencentcloud_instance_types" "instanceType" {
  cpu_core_count = 1
  memory_size    = 1
  filter {
    name   = "instance-family"
    values = ["S3"]
  }
}
data "tencentcloud_availability_zones" "zone" {}
resource "tencentcloud_vpc" "vpc" {
  cidr_block = local.cidr_block_vpc_name
  name       = "xuel_tf_vpc"
}
resource "tencentcloud_subnet" "subnet" {
  availability_zone = data.tencentcloud_availability_zones.zone.zones.0.name
  cidr_block        = local.cidr_block_subnet_name
  name              = "xuel_tf_subnet"
  vpc_id            = tencentcloud_vpc.vpc.id
}
resource "tencentcloud_key_pair" "xuel-tf-key" {
  key_name   = "xuel_tf_ansible_manager"
  public_key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDCcJxzfjqQDikRRShExu22kvNOfMKeWrn++s5nQMTm+9SNsQISEk+P15JhFVachgjumupMcOIrfJQAAQcnzNmxoRCTCJQfmegGpDZVpE1cyHUhGMA5kwu67PmK1Snm8hkg2Xzlduhr1xysL2mRn3+6o5tsFXhGrYOcSSXnf5SpTPgMjqo339ksH0iv8kvu3NaZRueygLYaVEMjixJvsnUisL3uY8LQ+4cm2Zu5mdQamhWhN0kkSdlfbjPgzxexL4AglD9YDy4I9Q80vKzy33Ubwo17a2aNCF3uPpYvCKiV0H9z2XtMxisKDfsQQA01Q1vpccUIK6L48xSbersxxxxxxxxxxxxxxxxxi1SGabjYLsv23ki6EMGjM/AK+fq+vj3pIPUMpscX3xVDGmz/zusq6v1KfOtQw7B/Dg8c2cxKUlEWZqqC3A7rt3JO/RVEbeqSe5mlRm2yngINVemmhkcfZNs= xuel@kaliarchmacbookpro"
}
resource "tencentcloud_instance" "xuel_tf_ansible" {
  availability_zone = data.tencentcloud_availability_zones.zone.zones[0].name
  image_id          = data.tencentcloud_images.image.images[0].image_id
  instance_type     = data.tencentcloud_instance_types.instanceType.instance_types[0].instance_type
  vpc_id            = tencentcloud_vpc.vpc.id
  subnet_id         = tencentcloud_subnet.subnet.id
  //  allocate_public_ip = true
  system_disk_size           = 50
  system_disk_type           = "CLOUD_PREMIUM"
  key_name                   = tencentcloud_key_pair.xuel-tf-key.id
  allocate_public_ip         = true
  internet_max_bandwidth_out = 2
  data_disks {
    data_disk_size = 50
    data_disk_type = "CLOUD_PREMIUM"
  }
  hostname             = format("xuel-tf-ansible-manager-%d", count.index)
  instance_charge_type = "POSTPAID_BY_HOUR"
  count                = local.count
  instance_name        = format("xuel-tf-server-%d", count.index)
  tags = {
    tagkey = "xuel-ansible-manager"
  }
}
// ansible 服务器布置ansible
resource "null_resource" "shell" {
  depends_on = [tencentcloud_instance.xuel_tf_ansible]
  count = local.count
  triggers = {
    instance_ids = element(tencentcloud_instance.xuel_tf_ansible.*.id, count.index)
  }
  provisioner "remote-exec" {
    connection {
      host        = element(tencentcloud_instance.xuel_tf_ansible.*.public_ip, count.index)
      type        = "ssh"
      user        = "root"
      private_key = file("${local.key_file}")
    }
    inline = [
      // 装置nginx
      "echo index.html > /xueltf.txt",
      "yum -y install nginx",
      "echo tf-nginx > /usr/share/nginx/html/index.html",
      "systemctl start nginx",
      "systemctl status nginx",
      // 装置ansible
      "yum install epel-release -y",
      "yum install ansible -y",
      // 装备ansible目录
      format("[ ! -d %s ] && mkdir -pv %s || echo 'dir create ok'", local.ansible_dir,local.ansible_dir)
    ]
  }
}
// 复制ansible文件至ansible 办理节点
resource "null_resource" "file_copy" {
  depends_on = [null_resource.shell]
  connection {
    host = tencentcloud_instance.xuel_tf_ansible.0.public_ip
    user = local.username
    type = "ssh"
    private_key = file("${local.key_file}")
  }
  // ansible 装备文件
  provisioner "file" {
    source = local.local_ansible_config
    destination = format("${local.ansible_dir}/%s", local.local_ansible_config)
  }
}
output "summary" {
  value = {
    //    image = {for k,v in data.tencentcloud_images.image:k=>v}
    //    instanceType = {for k,v in data.tencentcloud_instance_types
    //    .instanceType:k=>v}
    //    zone = {for k,v in data.tencentcloud_availability_zones.zone: k=>v}
    instance = { for k, v in tencentcloud_instance.xuel_tf_ansible : k => v }
    ip = tencentcloud_instance.xuel_tf_ansible.0.public_ip
  }
}

3.2 编列node节点

  • 运用本地密钥编列多台node节点
  • 从ansible_manager 的 terraform_remote_state 获取ansible manger的ip地址信息,用于传输node节点的IP.txt 列表文件至ansible
  • 将本地编列node节点的ansible playbook脚本上次至方针服务器。
  • 将创建node节点的私钥传输到ansible,供后续ansible免密登录node节点履行ansible playbook
locals {
  cidr_block_vpc_name    = "10.0.0.0/16"
  cidr_block_subnet_name = "10.0.1.0/24"
  count                  = 3
  local_ip_file = "ip.txt"
  ansible_dir = "/opt/ansibleworkspace/"
  // 本地ansible
  play_bookfile = "instance.yaml"
  local_ansible_playbook_dir = "ansible_playbook"
  key_file = "id_rsa"
}
// 获取ansible_manager 的公网IP
data "terraform_remote_state" "ansible_manager_ip" {
  backend = "cos"
  config = {
    region = "ap-beijing"
    bucket = "tfproject-1253329830"
    prefix = "ansible_manager/default"
  }
}
data "tencentcloud_images" "image" {
  image_type = ["PUBLIC_IMAGE"]
  os_name    = "centos 7.5"
}
data "tencentcloud_instance_types" "instanceType" {
  cpu_core_count = 1
  memory_size    = 1
  filter {
    name   = "instance-family"
    values = ["S3"]
  }
}
data "tencentcloud_availability_zones" "zone" {}
resource "tencentcloud_vpc" "vpc" {
  cidr_block = local.cidr_block_vpc_name
  name       = "xuel_tf_vpc"
}
resource "tencentcloud_subnet" "subnet" {
  availability_zone = data.tencentcloud_availability_zones.zone.zones.0.name
  cidr_block        = local.cidr_block_subnet_name
  name              = "xuel_tf_subnet"
  vpc_id            = tencentcloud_vpc.vpc.id
}
// 界说公钥
resource "tencentcloud_key_pair" "xuel-tf-key" {
  key_name   = "xuel_node_key"
  public_key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDCcJxzfjqQDikRRShExu22kvNOfMKeWrn++s5nQMTm+9SNsQISEk+P15JhFVachgjumupMcOIrfJQAAQcnzNmxoRCTCJQfmegGpDZVpE1cyHUhGMA5kwu67PmK1Snm8hkg2Xzlduhr1xysL2mRn3+6o5tsFXhGrYOcSSXnf5SpTPgMjqo339ksH0iv8kvu3NaZRueygLYaVEMjixJvsnUisL3uY8LQ+4cm2Zu5mdQamhWhN0kkSdlfbjPgzxexL4AglD9YDy4I9Q80vKzy33Ubwo17a2aNCF3uPpYvCKiV0H9z2XtMxisKDfsQQA01Q1vpccUIK6L48xSbers8hV2xxpSEWzEuoZg18eG2ikAencA6mhGjFWcp9A1dllY2rUhcEdrjcjXji1SGabjYLsv23ki6EMGjM/AK+fq+vj3pIPUMpscX3xVDGmz/zusq6v1KfOtQw7B/Dg8c2cxKUlEWZqqC3A7rt3JO/RVEbeqSe5mlRm2yngINVemmhkcfZNs= xuel@kaliarchmacbookpro"
}
resource "tencentcloud_instance" "xuel_tf_ansible_node" {
  availability_zone = data.tencentcloud_availability_zones.zone.zones[0].name
  image_id          = data.tencentcloud_images.image.images[0].image_id
  instance_type     = data.tencentcloud_instance_types.instanceType.instance_types[0].instance_type
  vpc_id            = tencentcloud_vpc.vpc.id
  subnet_id         = tencentcloud_subnet.subnet.id
  //  allocate_public_ip = true
  system_disk_size = 50
  system_disk_type = "CLOUD_PREMIUM"
  key_name         = tencentcloud_key_pair.xuel-tf-key.id
  allocate_public_ip = true
  internet_max_bandwidth_out = 2
  data_disks {
    data_disk_size = 50
    data_disk_type = "CLOUD_PREMIUM"
  }
  hostname             = format("xuel-tf-server-%d", count.index)
  instance_charge_type = "POSTPAID_BY_HOUR"
  count                = local.count
  instance_name        = format("xuel-tf-server-%d", count.index)
  tags = {
    name = "xuel_tf_ansibles"
  }
}
// 生成IP地址文件
resource "local_file" "ip_local_file" {
  filename = local.local_ip_file
  content = join("\n", tencentcloud_instance.xuel_tf_ansible_node.*.public_ip)
}
// 第一步,将出产的公网IP地址发送给长途ansible服务器
resource "null_resource" "copy_file" {
  depends_on = [tencentcloud_instance.xuel_tf_ansible_node]
  connection {
    type = "ssh"
    host = data.terraform_remote_state.ansible_manager_ip.outputs.summary.ip
    user = var.ansible_user
    private_key = file("/Users/xuel/.ssh/id_rsa")
  }
  // 生成的方针服务器的ip地址
  provisioner "file" {
    source = local.local_ip_file
    destination = format("${local.ansible_dir}/%s", local.local_ip_file)
  }
  // 到方针服务器履行的ansible playbook
  provisioner "file" {
    source = format("%s/%s", local.local_ansible_playbook_dir,local.play_bookfile )
    destination = format("${local.ansible_dir}/%s", local.play_bookfile)
  }
  // 到方针服务器的密钥
  provisioner "file" {
    source = "/Users/xuel/.ssh/id_rsa"
    destination = "${local.ansible_dir}/${local.key_file}"
  }
  // 方针服务器进行授权
  provisioner "remote-exec" {
    inline = [
      "chmod 600 ${local.ansible_dir}/${local.key_file}"
    ]
  }
}
// 第二步,登录ansible 操控节点履行ansible playbook
resource "null_resource" "exec_ansible_playbook" {
  depends_on = [null_resource.copy_file]
  provisioner "remote-exec" {
    connection {
      type = "ssh"
      host = data.terraform_remote_state.ansible_manager_ip.outputs.summary.ip
      user = var.ansible_user
      private_key = file("/Users/xuel/.ssh/id_rsa")
    }
    inline = [
      "cd ${local.ansible_dir}",
      "ansible-playbook ${local.play_bookfile}"
    ]
  }
}

在对node节点编列出来后,运用以下ansible playbook进行对服务器履行装备办理,主要内容为装置http,git并发动http服务,对数据盘/dev/vdb进行格式化挂在,并在其写入html文件

- name: integration of terraform and ansible
  hosts: all
  tasks:
    - name: installing httpd
      package:
        name: httpd
        state: present
    - name: installing php
      package:
        name: php
        state: present
    - name: starting httpd service
      service:
        name: httpd
        state: started
    - name: installing git
      package:
        name: git
        state: present
    - name: formatting storage
      filesystem:
        fstype: ext4
        dev : /dev/vdb
    - name: making folder
      file:
        path: /var/www/html/web
        state: directory
    - name: mounting storage
      mount:
        fstype: ext4
        src: /dev/vdb
        path: /var/www/html
        state: mounted
    - name: create html file
      ansible.builtin.shell:
        cmd: echo "xuel tf ansible page" > index.html
        chdir: /var/www/html

四 测试

本实战将backend存储在腾讯云cos中,workspace运用default

4.1 编列ansible manger节点

进行编列ansible manger节点

Terraform 集成Ansible 实战(remote)

查看存储在cos中的state文件

Terraform 集成Ansible 实战(remote)

登录编列出来的ansible manger查看信息

Terraform 集成Ansible 实战(remote)

4.2 编列node节点

再次编列三台node节点。

Terraform 集成Ansible 实战(remote)

查看存储在cos中的state状况文件

Terraform 集成Ansible 实战(remote)

登录任意服务器进行查看状况,http现已发动,并且磁盘现已格式化并挂载。

Terraform 集成Ansible 实战(remote)

查看web页面

Terraform 集成Ansible 实战(remote)

其他

  • 在写入多个ip列表时
content = join("\n", tencentcloud_instance.xuel_tf_ansible_node.*.public_ip)
  • ansible 的装备文件中界说了inventory 和 private_key_file 的途径
[defaults]
inventory = /opt/ansibleworkspace/ip.txt
host_key_checking= False
private_key_file=/opt/ansibleworkspace/id_rsa
remote_user=root
[privilege_escalation]
become=true
become_method=sudo
become_user=root
become_ask_pass=false

后期能够结合将编列出的主机进行分组,并配合git流程实现基于Gitlab 的DevOPS流程。

参考链接

  • registry.terraform.io/providers/h…

其他

  • 我正在参与技能社区创作者签约计划招募活动,点击链接报名投稿。