GitOps 的一个优点是基础设施即代码。它通过使用共享的配置和策略存储库来鼓励协作。在您的 OpenStack 集群中使用 GitLab 可以进一步增强协作。GitLab CI 可以作为您 CI/CD 的源代码控制和编排中心,甚至可以管理 Terraform 的状态。
为了实现这一点,您需要以下内容
- GitLab 帐户或实例。
- 私有 OpenStack 集群。如果您没有,请阅读我的文章 在 Raspberry Pi 集群上设置 OpenStack。
- 一台计算机(最好是容器主机)。
GitLab 和 Terraform 状态
目标是通过 Terraform 实现协作,因此您需要有一个中心化的状态文件。GitLab 具有 Terraform 的托管状态。借助此功能,您可以使个人能够协作管理 OpenStack。
创建一个 GitLab 组和项目
登录 GitLab,点击汉堡菜单,然后点击 Groups→View all groups。

(AJ Canlas, CC BY-SA 4.0)
点击 New group ,然后点击 Create group 创建一个组。

(AJ Canlas, CC BY-SA 4.0)
命名该组以生成唯一的组 URL,并邀请您的团队与您一起工作。

(AJ Canlas, CC BY-SA 4.0)
创建组后,点击 Create new project,然后点击 Create blank project 创建一个项目

(AJ Canlas, CC BY-SA 4.0)
命名您的项目。GitLab 为您生成唯一的项目 URL。此项目包含您的 Terraform 脚本和 Terraform 状态的存储库。
创建个人访问令牌
存储库需要个人访问令牌来管理此 Terraform 状态。在您的个人资料中,选择 Edit Profile:

(AJ Canlas, CC BY-SA 4.0)
点击侧面板中的 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 选项卡正上方的项目详细信息。它通常是您的项目名称。

(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
输出类似于这样

(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 并导航到您的项目。查看 Infrastructure → Terraform 以确认状态 homelab
已创建。

(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 项目的 Settings → CI/CD 面板中将其注册到您的项目

(AJ Canlas, CC BY-SA 4.0)
向下滚动到 Runners → Collapse

(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 显示为有效。它看起来像这样

(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"
有变量,因此您必须在 Settings → CI/CD → Variables → Expand 中声明它们。

(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 中屏蔽以进行保护。

(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 部分中查看您的新管道。

(AJ Canlas, CC BY-SA 4.0)
在 OpenStack 端,您可以看到 Terraform 创建的资源。
网络

(AJ Canlas, CC BY-SA 4.0)
Flavor

(AJ Canlas, CC BY-SA 4.0)
镜像

(AJ Canlas, CC BY-SA 4.0)
项目

(AJ Canlas, CC BY-SA 4.0)
用户

(AJ Canlas, CC BY-SA 4.0)
后续步骤
Terraform 具有巨大的潜力。Terraform 和 Ansible 非常适合一起使用。在我的下一篇文章中,我将演示 Ansible 如何与 OpenStack 协同工作
评论已关闭。