Terraform+Ansible+Gitlab 实现基础设施+配置管理GitOPS方案二

举报
kaliarch 发表于 2022/06/11 12:22:18 2022/06/11
【摘要】 一 前言围绕代码基础结构(IaC)更改进行协作,需要检查和批准代码更改和预期的基础结构更改。GitLab 提供了一个解决方案,使用合并请求页面来帮助围绕 Terraform 代码更改及其预期效果进行协作。这样用户就不必构建自定义工具或依赖第三方解决方案来简化他们的 IaC 工作流。使用 GitLab 管理基础设施,您可以使用与 Terraform 的集成来定义可以版本化、重用和共享的资源:...

一 前言

围绕代码基础结构(IaC)更改进行协作,需要检查和批准代码更改和预期的基础结构更改。GitLab 提供了一个解决方案,使用合并请求页面来帮助围绕 Terraform 代码更改及其预期效果进行协作。这样用户就不必构建自定义工具或依赖第三方解决方案来简化他们的 IaC 工作流。

使用 GitLab 管理基础设施,您可以使用与 Terraform 的集成来定义可以版本化、重用和共享的资源:

  • 管理低级组件,如计算、存储和网络资源。管理高级组件,如 DNS 条目和 SaaS 特性。
  • 合并 GitOps 部署和基础设施即代码(Infrastructure-as-Code,IaC)工作流。
  • 使用 GitLab 作为 Terraform 状态存储器。
  • 存储和使用 Terraform 模块简化通用和复杂的基础结构模式。

二 流程架构

2.1 架构图

2.2 流程

运维研发编写目标云的基于Terraform的资源清单文件,同事项目内管理Gitlab CI流程,在K8s不同NS下注册有对应的runner,当在不同分支下可以触发不同ns下的CI流程。
1.开发或运维人员提交代码。
2.部署在对应名称空间下的runner执行流程,创建运行单个Stage的POD来运行Terraform对应命令,例如init/fmt/play/apply等,同时在云上资源编排完成后,会进行利用Ansible对编排出来的主机进行SSH登录并进行内部配置管理。
3.如果要对云上资源进行变更,修改代码,再次提交pr,出发更新流水线。
4.如果需要销毁,根据CI文件配置提交BUILD为destroy,触发云上销毁动作。

三 预置条件

  • Gitlab 服务器
  • 注册有项目的gitlab-runner
  • K8s集群
  • 腾讯云AK账号,用于编排云上资源及state文件存储在OSS中。
  • 执行容器需要同时具备terraform+ansible 二进制命令。

四 配置

4.1 Tf+Ansible镜像制作

在资源编排结束后,利用Ansible针对编排出来的资源进行ssh登录进行配置管理,因此需要制作包含TF+Ansbile的镜像作为runner执行器,注意在制作镜像时候需关注TF和Ansible的版本。

制作镜像的Dockerfile

FROM debian:sid
MAINTAINER kaliarch
ENV TERRAFORM_VERSION=0.14.0
RUN apt-get update \
    && apt-get install -y ansible unzip \
    && mkdir /terraform \
    && cd /terraform \
    && wget -q https://releases.hashicorp.com/terraform/${TERRAFORM_VERSION}/terraform_${TERRAFORM_VERSION}_linux_amd64.zip \
    && unzip terraform_${TERRAFORM_VERSION}_linux_amd64.zip -d /bin/ \
    && rm terraform_${TERRAFORM_VERSION}_linux_amd64.zip

执行编译并上传至镜像仓库:

docker build -t 1832990/terraform-ansible:latest .
docke push 1832990/terraform-ansible:latest

4.2 Gitlab CI配置

variables:
  # PHASE: BUILD|DESTROY
  PHASE: BUILD
#  PROXY: http://squiduser:xxzx789@43.134.199.162:3128
#  PROXY: http://squiduser:xxzx789@43.154.197.46:3128
#  TENCENTCLOUD_SECRET_ID: AKIDsa3HGzThgRxDE0Wnpb8QSM1RbWCdokQr
#  TENCENTCLOUD_SECRET_KEY: DBtMaYHlzyiDSRBfutd8NN8dtufAQEn6
  REGION: "ap-guangzhou"
  PLAN_JSON: plan.json
  BACKEND_CONF: "backend_oss.conf"
  IMAGE: 1832990/terraform-ansible:latest


before_script:
#  - apk add --no-cache curl git jq
#  - apk add --no-cache ansible
  - export http_proxy=${SQUID_PROXY}
  - export https_proxy=${SQUID_PROXY}
  - export TENCENTCLOUD_SECRET_KEY=${TENCENTCLOUD_SECRET_KEY}
  - export TENCENTCLOUD_SECRET_ID=${TENCENTCLOUD_SECRET_ID}
  - export TF_REGISTRY_CLIENT_TIMEOUT=120000
  - export CHECKPOINT_TIMEOUT=500000
  - export TF_REGISTRY_DISCOVERY_RETRY=5
  - alias convert_report="jq -r '([.resource_changes[]?.change.actions?]|flatten)|{\"create\":(map(select(.==\"create\"))|length),\"update\":(map(select(.==\"update\"))|length),\"delete\":(map(select(.==\"delete\"))|length)}'"

# 配置缓存
cache:
  paths:
    - ${CI_PROJECT_DIR}/.terraform/*

stages:
  - init
  - validate
  - plan
  - show
  - deploy

Init:
  image:
    name: ${IMAGE}
    entrypoint: [""]
  stage: init
  retry:
    max: 2
    when:
      - script_failure
  tags:
    - gitlab-runner-k8s-new
  script:
    - terraform version
    - terraform init -backend-config=${BACKEND_CONF}
  only:
    - dev

Validate:
  image:
    name: ${IMAGE}
    entrypoint: [""]
  stage: validate
  tags:
    - gitlab-runner-k8s-new
  retry: 2
  script:
    - terraform init -backend-config=${BACKEND_CONF}
    - terraform validate
    - terraform fmt -check -recursive || echo 0
  cache:
    paths:
      - ${CI_PROJECT_DIR}/.terraform/*
    policy: pull
  allow_failure: true

Plan:
  image:
    name: ${IMAGE}
    entrypoint: [""]
  stage: plan
  retry: 2
  tags:
    - gitlab-runner-k8s-new
  artifacts:
    paths:
      - plan.bin
      - app_config.zip
    expire_in: 2 week
  script:
    - apt-get update && apt-get install -y jq
    - terraform init -backend-config=${BACKEND_CONF}
    - unset http_proxy && unset https_proxy
    - terraform plan -input=false -out=plan.bin -var region=${REGION}
#    - terraform show --json "plan.bin" | jq -r '([.resource_changes[]?.change.actions?]|flatten)|{\"create\":(map(select(.==\"create\"))|length),\"update\":(map(select(.==\"update\"))|length),\"delete\":(map(select(.==\"delete\"))|length)}'> ${PLAN_JSON}
#    - cat ${PLAN_JSON}
  only:
    variables:
      - $PHASE == "BUILD"

Apply:
  image:
    name: ${IMAGE}
    entrypoint: [""]
  when: manual
  stage: deploy
  retry: 2
  tags:
    - gitlab-runner-k8s-new
  script:
    - apt-get update && apt-get install -y sshpass
    - terraform init -backend-config=${BACKEND_CONF}
    - unset http_proxy && unset https_proxy
    - terraform apply -auto-approve -input=false plan.bin
  only:
    variables:
      - $PHASE == "BUILD"

Show:
  image:
    name: ${IMAGE}
    entrypoint: [""]
  stage: show
  retry: 2
  tags:
    - gitlab-runner-k8s-new
  script:
    - terraform init -backend-config=${BACKEND_CONF}
    - unset http_proxy && unset https_proxy
    - terraform show
  only:
    variables:
      - $PHASE == "DESTROY"
  allow_failure: true


Destroy:
  image:
    name: ${IMAGE}
    entrypoint: [""]
  stage: deploy
  retry: 2
  tags:
    - gitlab-runner-k8s-new
  script:
    - terraform init -backend-config=${BACKEND_CONF}
    - unset http_proxy && unset https_proxy
    - terraform destroy -auto-approve -var region=${REGION}
  only:
    variables:
      - $PHASE == "DESTROY"

4.1.2 环境配置

利用Gitlab CI/CD的Environment进行环境管理。

4.3 Terraform资源

  • tf文件
variable "region" {
  type = string
}


variable "ansible_user" {
  type = string
  description = "Ansible manage user"
  default = "root"
}
provider "tencentcloud" {
  region = var.region
}

terraform {
  required_providers {
    tencentcloud = {
      source  = "registry.terraform.io/tencentcloudstack/tencentcloud"
      version = ">=1.61.5"
    }
  }
  backend "cos" {}
}

locals {
  cidr_block_vpc_name    = "10.0.0.0/16"
  cidr_block_subnet_name = "10.0.1.0/24"
  count                  = 1
  local_ip_file = "ip.txt"
  ansible_dir = "./ansible/"

  // 本地ansible
  play_bookfile = "instance.yaml"
  //  key_file = "id_rsa"
//  key_file = "/Users/xuel/.ssh/id_rsa"
  server_pwd = "xxzx@789"
  server_user = "root"
}

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-charge-type"
    values = ["POSTPAID_BY_HOUR"]
  }
}


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
  password = local.server_pwd

  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地址文件,用于本地ansible play的inventory
resource "local_file" "ip_local_file" {
  filename = "${local.ansible_dir}/${local.local_ip_file}"
  content = join("\n", tencentcloud_instance.xuel_tf_ansible_node.*.public_ip)
}

// 在本地执行ansible-playbook
resource "null_resource" "exec-ansible-playbook" {
  depends_on = [local_file.ip_local_file]
  provisioner "local-exec" {
    command = "sleep 15 && ls -l && cd ${local.ansible_dir} && ls -l && pwd && ansible-playbook --version && ANSIBLE_HOST_KEY_CHECKING=False ansible-playbook ${local.play_bookfile} -i ./${local.local_ip_file} --extra-var ansible_ssh_pass=${local.server_pwd} --extra-vars ansible_ssh_user=${local.server_user}"
  }
}


output "result" {
  value = {
//    cvm_result = { for k, v in data.tencentcloud_instances.cvm : k => v },
//    count      = data.tencentcloud_instances.cvm.instance_list[*]
    vpc = {for k,v in tencentcloud_instance.xuel_tf_ansible_node :k=>v}
  }
}
  • 配置管理ansible-playbook
- name: integration of terraform and ansible
  hosts: all
  tasks:
    - name: installing httpd
      package:
        name: httpd
        state: present
      retries: 5
      delay: 3
    - 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: cloning git repo
#      ansible.builtin.git:
#        repo: "https://github.com/ishajain140/index_files.git/"
#        dest: /var/www/html/web

    - name: create html file
      ansible.builtin.shell:
        cmd: echo "xuel tf ansible page" > index.html
        chdir: /var/www/html

五 测试

  • init

  • validate

  • plan

  • apply

apply 为手动运行

查看服务器内部配置

  • destroy

修改PUSHE为:DESTROY,提交代码,对资源进行销毁

六 注意事项

  • 需要K8s集群配置pv存储卷来实现跨stage的任务cache。
  • 使用gitlab ci 环境管理来对执行ci/cd的人员隐藏密钥信息。
  • 后期可以使用gitlab 来进行变量管理。
  • ansible通过参数传递配置文件中的信息。

本文仅实现简单的Terraform + Gitlab CI 基础设施编排集成,未将Gitlab CI的配置文件进行抽离模版化。

目前tf和ansible未解耦,使得对配置管理的变更使得基础设施也同样变更,应在配置管理的时候,使用state查询到的output来进行参数输入。

参考链接

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。