使用 Terraform 和 GitLab 管理 OpenStack

按照本教程了解如何使用 GitLab 进一步增强 OpenStack 集群中的协作。
目前还没有读者喜欢这个。
diagram of planning a cloud

Opensource.com

GitOps 的一个优点是基础设施即代码。它通过使用共享的配置和策略存储库来鼓励协作。在您的 OpenStack 集群中使用 GitLab 可以进一步增强协作。GitLab CI 可以作为您 CI/CD 的源代码控制和编排中心,甚至可以管理 Terraform 的状态。

为了实现这一点,您需要以下内容

  1. GitLab 帐户或实例。
  2. 私有 OpenStack 集群。如果您没有,请阅读我的文章 在 Raspberry Pi 集群上设置 OpenStack
  3. 一台计算机(最好是容器主机)。

GitLab 和 Terraform 状态

目标是通过 Terraform 实现协作,因此您需要有一个中心化的状态文件。GitLab 具有 Terraform 的托管状态。借助此功能,您可以使个人能够协作管理 OpenStack。

创建一个 GitLab 组和项目

登录 GitLab,点击汉堡菜单,然后点击 GroupsView all groups

view all groups

(AJ Canlas, CC BY-SA 4.0)

点击 New group ,然后点击 Create group 创建一个组。

Create Group

(AJ Canlas, CC BY-SA 4.0)

命名该组以生成唯一的组 URL,并邀请您的团队与您一起工作。

Name Group

(AJ Canlas, CC BY-SA 4.0)

创建组后,点击 Create new project,然后点击 Create blank project 创建一个项目

Create from blank project

(AJ Canlas, CC BY-SA 4.0)

命名您的项目。GitLab 为您生成唯一的项目 URL。此项目包含您的 Terraform 脚本和 Terraform 状态的存储库。

创建个人访问令牌

存储库需要个人访问令牌来管理此 Terraform 状态。在您的个人资料中,选择 Edit Profile:

Edit profile

(AJ Canlas, CC BY-SA 4.0)

点击侧面板中的 Access Token 以访问创建访问令牌的菜单。保存您的令牌,因为您无法再次查看它。

Access Token

(AJ Canlas, CC BY-SA 4.0)

克隆空存储库

在可以直接访问您的 OpenStack 安装的计算机上,克隆存储库,然后更改为结果目录

$ git clone git@gitlab.com:testgroup2170/testproject.git

$ cd testproject

创建后端 .tf 和 provider 文件

创建一个后端文件以将 GitLab 配置为您的状态后端

$ cat >> backend.tf << EOF
terraform {
  backend "http" {
  }
}
EOF

此 provider 文件拉取 OpenStack 的 provider

$ cat >> provider.tf << EOF
terraform {
  required_version = ">= 0.14.0"
  required_providers {
    openstack = {
      source  = "terraform-provider-openstack/openstack"
      version = "1.49.0"
    }
  }
}

provider "openstack" {
  user_name   = var.OS_USERNAME
  tenant_name = var.OS_TENANT
  password    = var.OS_PASSWORD
  auth_url    = var.OS_AUTH_URL
  region      = var.OS_REGION
}
EOF

由于您已在 provider 中声明了一个变量,因此您必须在变量文件中声明它

$ cat >> variables.tf << EOF
variable "OS_USERNAME" {
  type        = string
  description = "OpenStack Username"
}

variable "OS_TENANT" {
  type        = string
  description = "OpenStack Tenant/Project Name"
}

variable "OS_PASSWORD" {
  type        = string
  description = "OpenStack Password"
}

variable "OS_AUTH_URL" {
  type        = string
  description = "OpenStack Identitiy/Keystone API for authentication"
}

variable "OS_REGION" {
  type        = string
  description = "OpenStack Region"
}

EOF

由于您最初是在本地工作,因此您必须设置这些变量才能使其工作

$ cat >> terraform.tfvars << EOF
OS_USERNAME = "admin"
OS_TENANT   = "admin"
OS_PASSWORD = "YYYYYYYYYYYYYYYYYYYYYY"
OS_AUTH_URL = "http://X.X.X.X:35357/v3"
OS_REGION   = "RegionOne"
EOF

这些详细信息可在 OpenStack 的 rc 文件中找到。

在 Terraform 中初始化项目

初始化项目非常不同,因为您需要告诉 Terraform 使用 GitLab 作为您的状态后端

PROJECT_ID="<gitlab-project-id>"
TF_USERNAME="<gitlab-username>"
TF_PASSWORD="<gitlab-personal-access-token>"
TF_STATE_NAME="<your-unique-state-name>"
TF_ADDRESS="https://gitlab.com/api/v4/projects/${PROJECT_ID}/terraform/state/${TF_STATE_NAME}"

$ terraform init \
  -backend-config=address=${TF_ADDRESS} \
  -backend-config=lock_address=${TF_ADDRESS}/lock \
  -backend-config=unlock_address=${TF_ADDRESS}/lock \
  -backend-config=username=${TF_USERNAME} \
  -backend-config=password=${TF_PASSWORD} \
  -backend-config=lock_method=POST \
  -backend-config=unlock_method=DELETE \
  -backend-config=retry_wait_min=5

要查看 gitlab-project-id,请查看侧面板中 Project Information 选项卡正上方的项目详细信息。它通常是您的项目名称。

Project ID

(AJ Canlas, CC BY-SA 4.0)

对我来说,它是 42580143

使用您的用户名作为 gitlab-username。我的用户名是 ajohnsc

gitlab-personal-access-token 是您在本练习前面创建的令牌。在本示例中,我使用 wwwwwwwwwwwwwwwwwwwww。您可以将 your-unique-state-name 命名为任何名称。我使用了 homelab

这是我的初始化脚本

PROJECT_ID="42580143"
TF_USERNAME="ajohnsc"
TF_PASSWORD="wwwwwwwwwwwwwwwwwwwww"
TF_STATE_NAME="homelab"
TF_ADDRESS="https://gitlab.com/api/v4/projects/${PROJECT_ID}/terraform/state/${TF_STATE_NAME}"

要使用该文件

$ terraform init \
  -backend-config=address=${TF_ADDRESS} \
  -backend-config=lock_address=${TF_ADDRESS}/lock \
  -backend-config=unlock_address=${TF_ADDRESS}/lock \
  -backend-config=username=${TF_USERNAME} \
  -backend-config=password=${TF_PASSWORD} \
  -backend-config=lock_method=POST \
  -backend-config=unlock_method=DELETE \
  -backend-config=retry_wait_min=5

输出类似于这样

terraform init

(AJ Canlas, CC BY-SA 4.0)

测试 Terraform 脚本

这为我的 OpenStack flavor 设置了 VM 的大小

$ cat >> flavors.tf << EOF
resource "openstack_compute_flavor_v2" "small-flavor" {
  name      = "small"
  ram       = "4096"
  vcpus     = "1"
  disk      = "0"
  flavor_id = "1"
  is_public = "true"
}

resource "openstack_compute_flavor_v2" "medium-flavor" {
  name      = "medium"
  ram       = "8192"
  vcpus     = "2"
  disk      = "0"
  flavor_id = "2"
  is_public = "true"
}

resource "openstack_compute_flavor_v2" "large-flavor" {
  name      = "large"
  ram       = "16384"
  vcpus     = "4"
  disk      = "0"
  flavor_id = "3"
  is_public = "true"
}

resource "openstack_compute_flavor_v2" "xlarge-flavor" {
  name      = "xlarge"
  ram       = "32768"
  vcpus     = "8"
  disk      = "0"
  flavor_id = "4"
  is_public = "true"
}
EOF

我的外部网络的设置如下

$ cat >> external-network.tf << EOF
resource "openstack_networking_network_v2" "external-network" {
  name           = "external-network"
  admin_state_up = "true"
  external       = "true"
  segments {
    network_type     = "flat"
    physical_network = "physnet1"
  }
}

resource "openstack_networking_subnet_v2" "external-subnet" {
  name            = "external-subnet"
  network_id      = openstack_networking_network_v2.external-network.id
  cidr            = "10.0.0.0/8"
  gateway_ip      = "10.0.0.1"
  dns_nameservers = ["10.0.0.254", "10.0.0.253"]
  allocation_pool {
    start = "10.0.0.2"
    end   = "10.0.254.254"
  }
}
EOF

路由器设置如下所示

$ cat >> routers.tf << EOF
resource "openstack_networking_router_v2" "external-router" {
  name                = "external-router"
  admin_state_up      = true
  external_network_id = openstack_networking_network_v2.external-network.id
}
EOF

输入以下图像

$ cat >> images.tf << EOF
resource "openstack_images_image_v2" "cirros" {
  name             = "cirros"
  image_source_url = "https://download.cirros-cloud.net/0.6.1/cirros-0.6.1-x86_64-disk.img"
  container_format = "bare"
  disk_format      = "qcow2"
}
EOF

这是一个演示租户

$ cat >> demo-project-user.tf << EOF
resource "openstack_identity_project_v3" "demo-project" {
  name = "Demo"
}

resource "openstack_identity_user_v3" "demo-user" {
  name               = "demo-user"
  default_project_id = openstack_identity_project_v3.demo-project.id
  password = "demo"
}
EOF

完成后,您将拥有此文件结构

.
├── backend.tf
├── demo-project-user.tf
├── external-network.tf
├── flavors.tf
├── images.tf
├── provider.tf
├── routers.tf
├── terraform.tfvars
└── variables.tf

发布计划

文件完成后,您可以使用 terraform plan 命令创建计划文件

$ terraform plan
Acquiring state lock. This may take a few moments...

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # openstack_compute_flavor_v2.large-flavor will be created
  + resource "openstack_compute_flavor_v2" "large-flavor" {
      + disk         = 0
      + extra_specs  = (known after apply)
      + flavor_id    = "3"
      + id           = (known after apply)
      + is_public    = true
      + name         = "large"
      + ram          = 16384
      + region       = (known after apply)
      + rx_tx_factor = 1
      + vcpus        = 4
    }

[...]

Plan: 10 to add,
Releasing state lock. This may take a few moments...

创建所有计划文件后,使用 terraform apply 命令应用它们

$ terraform apply -auto-approve
Acquiring state lock. This may take a few moments...
[...]
Plan: 10 to add, 0 to change, 0 to destroy.
openstack_compute_flavor_v2.large-flavor: Creating...
openstack_compute_flavor_v2.small-flavor: Creating...
openstack_identity_project_v3.demo-project: Creating...
openstack_networking_network_v2.external-network: Creating...
openstack_compute_flavor_v2.xlarge-flavor: Creating...
openstack_compute_flavor_v2.medium-flavor: Creating...
openstack_images_image_v2.cirros: Creating...
[...]
Releasing state lock. This may take a few moments...

Apply complete! Resources: 10 added, 0 changed, 0 destroyed.

应用基础设施后,返回 GitLab 并导航到您的项目。查看 InfrastructureTerraform 以确认状态 homelab 已创建。

Gitlab State file

(AJ Canlas, CC BY-SA 4.0)

销毁状态以测试 CI

现在您已经创建了一个状态,请尝试销毁基础设施,以便稍后可以应用 CI 管道。当然,这纯粹是为了从 Terraform CLI 迁移到管道。如果您有现有的基础设施,则可以跳过此步骤。

$ terraform destroy -auto-approve
Acquiring state lock. This may take a few moments...
openstack_identity_project_v3.demo-project: Refreshing state... [id=5f86d4229003404998dfddc5b9f4aeb0]
openstack_networking_network_v2.external-network: Refreshing state... [id=012c10f3-8a51-4892-a688-aa9b7b43f03d]
[...]
Plan: 0 to add, 0 to change, 10 to destroy.
openstack_compute_flavor_v2.small-flavor: Destroying... [id=1]
openstack_compute_flavor_v2.xlarge-flavor: Destroying... [id=4]
openstack_networking_router_v2.external-router: Destroying... [id=73ece9e7-87d7-431d-ad6f-09736a02844d]
openstack_compute_flavor_v2.large-flavor: Destroying... [id=3]
openstack_identity_user_v3.demo-user: Destroying... [id=96b48752e999424e95bc690f577402ce]
[...]
Destroy complete! Resources: 10 destroyed.

您现在拥有每个人都可以使用的状态。您可以使用集中式状态进行配置。通过适当的管道,您可以自动化常见任务。

设置 GitLab Runner

您的 OpenStack 集群不是面向公众的,并且 OpenStack API 未公开。您必须有一个 GitLab Runner 来运行 GitLab 管道。GitLab Runner 是在远程 GitLab 服务器上运行和执行任务的服务或代理。

在不同网络上的计算机上,为 GitLab Runner 创建一个容器

$ docker volume create gitlab-runner-config

$ docker run -d --name gitlab-runner --restart always \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -v gitlab-runner-config:/etc/gitlab-runner \
  gitlab/gitlab-runner:latest

$ docker ps
CONTAINER ID   IMAGE                           COMMAND                  CREATED         STATUS         PORTS                                       NAMES
880e2ed289d3   gitlab/gitlab-runner:latest     "/usr/bin/dumb-init …"   3 seconds ago   Up 2 seconds                                               gitlab-runner-test

现在在您的 GitLab 项目的 SettingsCI/CD 面板中将其注册到您的项目

Gitlab runner register

(AJ Canlas, CC BY-SA 4.0)

向下滚动到 RunnersCollapse

Gitlab runner registration

(AJ Canlas, CC BY-SA 4.0)

需要 GitLab Runner 注册令牌和 URL。禁用右侧的共享 Runner 以确保它仅在 Runner 上工作。运行 gitlab-runner 容器以注册 Runner

$ docker exec -ti gitlab-runner /usr/bin/gitlab-runner register
Runtime platform                                    arch=amd64 os=linux pid=18 revision=6d480948 version=15.7.1
Running in system-mode.                            
                                                   
Enter the GitLab instance URL (for example, https://gitlab.com/):
https://gitlab.com/
Enter the registration token:
GR1348941S1bVeb1os44ycqsdupRK
Enter a description for the runner:
[880e2ed289d3]: dockerhost
Enter tags for the runner (comma-separated):
homelab
Enter optional maintenance note for the runner:

WARNING: Support for registration tokens and runner parameters in the 'register' command has been deprecated in GitLab Runner 15.6 and will be replaced with support for authentication tokens. For more information, see https://gitlab.com/gitlab-org/gitlab/-/issues/380872 
Registering runner... succeeded                     runner=GR1348941S1bVeb1o
Enter an executor: docker-ssh, shell, virtualbox, instance, kubernetes, custom, docker, parallels, ssh, docker+machine, docker-ssh+machine:
docker
Enter the default Docker image (for example, ruby:2.7):
ajscanlas/homelab-runner:3.17
Runner registered successfully. Feel free to start it, but if it's running already the config should be automatically reloaded!
 
Configuration (with the authentication token) was saved in "/etc/gitlab-runner/config.toml" 

成功后,您的 GitLab 界面会将您的 Runner 显示为有效。它看起来像这样

Specific Runner

(AJ Canlas, CC BY-SA 4.0)

您现在可以使用该 Runner 通过 GitLab 中的 CI/CD 管道自动化配置。

设置 GitLab 管道

现在您可以设置管道。在您的存储库中添加一个名为 .gitlab-ci.yaml 的文件,以定义您的 CI/CD 步骤。忽略您不需要的文件,例如 .terraform 目录和敏感数据(如变量文件)。

这是我的 .gitignore 文件

$ cat .gitignore
*.tfvars
.terraform*

这是我在 .gitlab-ci.yaml 中的 CI 管道条目

$ cat .gitlab-ci.yaml
default:
  tags:
    - homelab

variables:
  TF_ROOT: ${CI_PROJECT_DIR}
  TF_ADDRESS: ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/terraform/state/homelab

cache:
  key: homelab
  paths:
    - ${TF_ROOT}/.terraform*

stages:
  - prepare
  - validate
  - build
  - deploy

before_script:
  - cd ${TF_ROOT}

tf-init:
  stage: prepare
  script:
    - terraform --version
    - terraform init -backend-config=address=${BE_REMOTE_STATE_ADDRESS} -backend-config=lock_address=${BE_REMOTE_STATE_ADDRESS}/lock -backend-config=unlock_address=${BE_REMOTE_STATE_ADDRESS}/lock -backend-config=username=${BE_USERNAME} -backend-config=password=${BE_ACCESS_TOKEN} -backend-config=lock_method=POST -backend-config=unlock_method=DELETE -backend-config=retry_wait_min=5

tf-validate:
  stage: validate
  dependencies:
    - tf-init
  variables:
    TF_VAR_OS_AUTH_URL: ${OS_AUTH_URL}
    TF_VAR_OS_PASSWORD: ${OS_PASSWORD}
    TF_VAR_OS_REGION: ${OS_REGION}
    TF_VAR_OS_TENANT: ${OS_TENANT}
    TF_VAR_OS_USERNAME: ${OS_USERNAME}
  script:
    - terraform validate

tf-build:
  stage: build
  dependencies:
    - tf-validate
  variables:
    TF_VAR_OS_AUTH_URL: ${OS_AUTH_URL}
    TF_VAR_OS_PASSWORD: ${OS_PASSWORD}
    TF_VAR_OS_REGION: ${OS_REGION}
    TF_VAR_OS_TENANT: ${OS_TENANT}
    TF_VAR_OS_USERNAME: ${OS_USERNAME}
  script:
    - terraform plan -out "planfile"
  artifacts:
    paths:
      - ${TF_ROOT}/planfile

tf-deploy:
  stage: deploy
  dependencies:
    - tf-build
  variables:
    TF_VAR_OS_AUTH_URL: ${OS_AUTH_URL}
    TF_VAR_OS_PASSWORD: ${OS_PASSWORD}
    TF_VAR_OS_REGION: ${OS_REGION}
    TF_VAR_OS_TENANT: ${OS_TENANT}
    TF_VAR_OS_USERNAME: ${OS_USERNAME}
  script:
    - terraform apply -auto-approve "planfile"

该过程首先声明每个步骤和阶段都在 homelab 标签下,允许您的 GitLab Runner 运行它。

default:
  tags:
    - homelab

接下来,变量在管道上设置。变量仅在管道运行时存在

variables:
  TF_ROOT: ${CI_PROJECT_DIR}
  TF_ADDRESS: ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/terraform/state/homelab

有一个缓存,可以在从一个阶段运行到另一个阶段时保存特定文件和目录

cache:
  key: homelab
  paths:
    - ${TF_ROOT}/.terraform*

这些是管道遵循的阶段

stages:
  - prepare
  - validate
  - build
  - deploy

这声明了在任何阶段运行之前要执行的操作

before_script:
  - cd ${TF_ROOT}

prepare 阶段,tf-init 初始化 Terraform 脚本,获取 provider,并将其后端设置为 GitLab。尚未声明的变量稍后将添加为环境变量。

tf-init:
  stage: prepare
  script:
    - terraform --version
    - terraform init -backend-config=address=${BE_REMOTE_STATE_ADDRESS} -backend-config=lock_address=${BE_REMOTE_STATE_ADDRESS}/lock -backend-config=unlock_address=${BE_REMOTE_STATE_ADDRESS}/lock -backend-config=username=${BE_USERNAME} -backend-config=password=${BE_ACCESS_TOKEN} -backend-config=lock_method=POST -backend-config=unlock_method=DELETE -backend-config=retry_wait_min=5

在此部分中,CI 作业 tf-validate 和阶段 validate 运行 Terraform 以验证 Terraform 脚本是否没有语法错误。尚未声明的变量稍后将添加为环境变量。

tf-validate:
  stage: validate
  dependencies:
    - tf-init
  variables:
    TF_VAR_OS_AUTH_URL: ${OS_AUTH_URL}
    TF_VAR_OS_PASSWORD: ${OS_PASSWORD}
    TF_VAR_OS_REGION: ${OS_REGION}
    TF_VAR_OS_TENANT: ${OS_TENANT}
    TF_VAR_OS_USERNAME: ${OS_USERNAME}
  script:
    - terraform validate

接下来,CI 作业 tf-build 和阶段 build 使用 terraform plan 创建计划文件,并使用 artifacts 标签临时保存它。

tf-build:
  stage: build
  dependencies:
    - tf-validate
  variables:
    TF_VAR_OS_AUTH_URL: ${OS_AUTH_URL}
    TF_VAR_OS_PASSWORD: ${OS_PASSWORD}
    TF_VAR_OS_REGION: ${OS_REGION}
    TF_VAR_OS_TENANT: ${OS_TENANT}
    TF_VAR_OS_USERNAME: ${OS_USERNAME}
  script:
    - terraform plan -out "planfile"
  artifacts:
    paths:
      - ${TF_ROOT}/planfile

在下一节中,CI 作业 tf-deploy 和阶段 deploy 应用计划文件。

tf-deploy:
  stage: deploy
  dependencies:
    - tf-build
  variables:
    TF_VAR_OS_AUTH_URL: ${OS_AUTH_URL}
    TF_VAR_OS_PASSWORD: ${OS_PASSWORD}
    TF_VAR_OS_REGION: ${OS_REGION}
    TF_VAR_OS_TENANT: ${OS_TENANT}
    TF_VAR_OS_USERNAME: ${OS_USERNAME}
  script:
    - terraform apply -auto-approve "planfile"

有变量,因此您必须在 SettingsCI/CDVariablesExpand 中声明它们。

Gitlab Environment Variables

(AJ Canlas, CC BY-SA 4.0)

添加所有必需的变量

BE_ACCESS_TOKEN => GitLab Access Token
BE_REMOTE_STATE_ADDRESS => This was the rendered TF_ADDRESS variable
BE_USERNAME => GitLab username
OS_USERNAME => OpenStack Username
OS_TENANT   => OpenStack tenant
OS_PASSWORD => OpenStack User Password
OS_AUTH_URL => Auth URL
OS_REGION   => OpenStack Region

因此,对于此示例,我使用了以下内容

BE_ACCESS_TOKEN = "wwwwwwwwwwwwwwwwwwwww"
BE_REMOTE_STATE_ADDRESS = https://gitlab.com/api/v4/projects/42580143/terraform/state/homelab
BE_USERNAME = "ajohnsc"
OS_USERNAME = "admin"
OS_TENANT   = "admin"
OS_PASSWORD = "YYYYYYYYYYYYYYYYYYYYYY"
OS_AUTH_URL = "http://X.X.X.X:35357/v3"
OS_REGION   = "RegionOne"

并且它已在 GitLab 中屏蔽以进行保护。

Gitlab variable view

(AJ Canlas, CC BY-SA 4.0)

最后一步是将新文件推送到存储库

$ git add .

$ git commit -m "First commit"
[main (root-commit) e78f701] First commit
 10 files changed, 194 insertions(+)
 create mode 100644 .gitignore
 create mode 100644 .gitlab-ci.yml
 create mode 100644 backend.tf
 create mode 100644 demo-project-user.tf
 create mode 100644 external-network.tf
 create mode 100644 flavors.tf
 create mode 100644 images.tf
 create mode 100644 provider.tf
 create mode 100644 routers.tf
 create mode 100644 variables.tf

$ git push
Enumerating objects: 12, done.
Counting objects: 100% (12/12), done.
Delta compression using up to 4 threads
Compressing objects: 100% (10/10), done.
Writing objects: 100% (12/12), 2.34 KiB | 479.00 KiB/s, done.
Total 12 (delta 0), reused 0 (delta 0), pack-reused 0
To gitlab.com:testgroup2170/testproject.git
 * [new branch]      main -> main

查看结果

在 GitLab 的 CI/CD 部分中查看您的新管道。

Pipelines

(AJ Canlas, CC BY-SA 4.0)

在 OpenStack 端,您可以看到 Terraform 创建的资源。

网络

Networks

(AJ Canlas, CC BY-SA 4.0)

Flavor

Flavors

(AJ Canlas, CC BY-SA 4.0)

镜像

Images

(AJ Canlas, CC BY-SA 4.0)

项目

Project

(AJ Canlas, CC BY-SA 4.0)

用户

user

(AJ Canlas, CC BY-SA 4.0)

后续步骤

Terraform 具有巨大的潜力。Terraform 和 Ansible 非常适合一起使用。在我的下一篇文章中,我将演示 Ansible 如何与 OpenStack 协同工作

AJ Canlas
AJ Canlas 是 Micro-D International 在菲律宾的高级解决方案咨询经理。他负责公共云和私有云基础设施及项目。他的主要优势在于 Linux、OpenStack、Ansible 自动化和容器化。

评论已关闭。

© . All rights reserved.