Terraform+Ansible+Gitlab 实现基础设施+配置管理GitOPS方案二
一 前言
围绕代码基础结构(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来进行参数输入。
参考链接
- https://www.bitslovers.com/run-terraform-from-gitlab-ci/
- https://medium.com/geekculture/how-to-run-terraform-script-using-gitlab-ci-cd-b6f448ab0232
- https://webera.blog/gitlab-ci-and-terraform-workflow-using-the-feature-parent-child-pipelines-ab50cfe02c9e
- https://gitlab.com/-/ide/project/gitlab-org/gitlab/edit/master/-/lib/gitlab/ci/templates/Terraform.gitlab-ci.yml
- https://docs.gitlab.com/ee/user/application_security/iac_scanning/index.html#configure-iac-scanning-manually
- https://meigit.readthedocs.io/en/latest/gitlab_ci_.gitlab-ci.yml_detail.html
- https://zq99299.github.io/note-combat/gitlab/cicd/#缓存-gradle-的-jar-包依赖
- 点赞
- 收藏
- 关注作者
评论(0)