Gerencie OpenStack usando Terraform e GitLab
Siga este tutorial para ver como o uso do GitLab pode aprimorar ainda mais a colaboração em seu cluster OpenStack.
Uma virtude do GitOps é a infraestrutura como código. Ele incentiva a colaboração usando um repositório compartilhado de configurações e políticas. Usar o GitLab pode aprimorar ainda mais a colaboração em seu cluster OpenStack. O GitLab CI pode servir como seu centro de controle de origem e orquestração para CI/CD e pode até mesmo gerenciar o estado do Terraform.
Para conseguir isso, você precisa do seguinte:
- Conta ou instância do GitLab.
- Cluster OpenStack privado. Se você não tiver um, leia meu artigo Configure o OpenStack em um cluster Raspberry Pi.
- Um computador (de preferência um host contêiner).
Estado do GitLab e do Terraform
O objetivo é conseguir colaboração através do Terraform, então você precisa ter um arquivo de estado centralizado. GitLab possui um estado gerenciado para Terraform. Com esse recurso, você pode permitir que indivíduos gerenciem o OpenStack de forma colaborativa.
Crie um grupo e projeto no GitLab
Faça login no GitLab, clique no menu hambúrguer e clique em Grupos→Ver todos os grupos.
(AJ Canlas, CC BY-SA 4.0)
Crie um grupo clicando em Novo grupo e depois em Criar grupo.
(AJ Canlas, CC BY-SA 4.0)
Dê um nome ao grupo para gerar um URL de grupo exclusivo e convide sua equipe para trabalhar com você.
(AJ Canlas, CC BY-SA 4.0)
Após criar um grupo, crie um projeto clicando em Criar novo projeto e depois em Criar projeto em branco:
(AJ Canlas, CC BY-SA 4.0)
Dê um nome ao seu projeto. GitLab gera um URL de projeto exclusivo para você. Este projeto contém o repositório para seus scripts e estado do Terraform.
Crie um token de acesso pessoal
O repositório precisa de um token de acesso pessoal para gerenciar este estado do Terraform. No seu perfil, selecione Editar perfil:
(AJ Canlas, CC BY-SA 4.0)
Clique em Token de acesso no painel lateral para acessar um menu para criar um token de acesso. Salve seu token porque você não poderá visualizá-lo novamente.
(AJ Canlas, CC BY-SA 4.0)
Clone o repositório vazio
Em um computador com acesso direto à instalação do OpenStack, clone o repositório e depois mude para o diretório resultante:
$ git clone git@gitlab.com:testgroup2170/testproject.git
$ cd testproject
Crie o arquivo .tf de back-end e o arquivo do provedor
Crie um arquivo de back-end para configurar o GitLab como seu back-end de estado:
$ cat >> backend.tf << EOF
terraform {
backend "http" {
}
}
EOF
Este arquivo de provedor extrai o provedor do OpenStack:
$ 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
Como você declarou uma variável no provedor, você deve declará-la em um arquivo de variáveis:
$ 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
Como inicialmente você está trabalhando localmente, você deve definir essas variáveis para que funcione:
$ 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
Esses detalhes estão disponíveis em seu arquivo rc
no OpenStack.
Inicialize o projeto no Terraform
Inicializar o projeto é bem diferente porque você precisa dizer ao Terraform para usar o GitLab como back-end de estado:
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
Para visualizar o gitlab-project-id
, veja os detalhes do projeto logo acima da guia Informações do Projeto no painel lateral. Geralmente é o nome do seu projeto.
(AJ Canlas, CC BY-SA 4.0)
Para mim, é 42580143
.
Use seu nome de usuário para gitlab-username
. O meu é ajohnsc
.
O gitlab-personal-access-token
é o token que você criou anteriormente neste exercício. Neste exemplo, eu uso wwwwwwwwwwwwwwwwwwwww
. Você pode nomear nome-do-seu-estado-único
qualquer coisa. Usei homelab
.
Aqui está meu script de inicialização:
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}"
Para usar o arquivo:
$ 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
A saída é semelhante a esta:
(AJ Canlas, CC BY-SA 4.0)
Teste o script Terraform
Isso define o tamanho das VMs para meus tipos de OpenStack:
$ 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
As configurações da minha rede externa são as seguintes:
$ 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
As configurações do roteador são assim:
$ 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
Insira o seguinte para imagens:
$ 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
Aqui está um inquilino de demonstração:
$ 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
Quando concluído, você terá esta estrutura de arquivos:
.
├── backend.tf
├── demo-project-user.tf
├── external-network.tf
├── flavors.tf
├── images.tf
├── provider.tf
├── routers.tf
├── terraform.tfvars
└── variables.tf
Plano de emissão
Após a conclusão dos arquivos, você pode criar os arquivos de plano com o comando 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...
Após todos os arquivos de plano terem sido criados, aplique-os com o comando 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.
Após aplicar a infraestrutura, retorne ao GitLab e navegue até o seu projeto. Procure em Infraestrutura → Terraform para confirmar se o estado homelab
foi criado.
(AJ Canlas, CC BY-SA 4.0)
Destrua o estado para testar o CI
Agora que você criou um estado, tente destruir a infraestrutura para poder aplicar o pipeline de CI mais tarde. Claro, isso é apenas para migrar da CLI do Terraform para um Pipeline. Se você tiver uma infraestrutura existente, poderá pular esta etapa.
$ 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.
Agora você tem um estado que todos podem usar. Você pode provisionar usando um estado centralizado. Com o pipeline adequado, você pode automatizar tarefas comuns.
Configure um executor GitLab
Seu cluster OpenStack não é voltado ao público e a API OpenStack não está exposta. Você deve ter um executor GitLab para executar pipelines GitLab. Os executores GitLab são serviços ou agentes que executam e executam tarefas no servidor GitLab remoto.
Em um computador em uma rede diferente, crie um contêiner para um executor do GitLab:
$ 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
Agora registre-o com seu projeto no painel Configurações → CI/CD do seu projeto GitLab:
(AJ Canlas, CC BY-SA 4.0)
Role para baixo até Executores → Recolher:
(AJ Canlas, CC BY-SA 4.0)
O token de registro e o URL do executor do GitLab são obrigatórios. Desative o executor compartilhado no lado direito para garantir que ele funcione apenas no executor. Execute o contêiner gitlab-runner
para registrar o executor:
$ 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"
Após o sucesso, a interface do GitLab exibe seu executor como válido. Se parece com isso:
(AJ Canlas, CC BY-SA 4.0)
Agora você pode usar esse executor para automatizar o provisionamento com um pipeline de CI/CD no GitLab.
Configure o pipeline do GitLab
Agora você pode configurar um pipeline. Adicione um arquivo chamado .gitlab-ci.yaml
em seu repositório para definir suas etapas de CI/CD. Ignore os arquivos desnecessários, como diretórios .terraform
e dados confidenciais, como arquivos variáveis.
Aqui está meu arquivo .gitignore
:
$ cat .gitignore
*.tfvars
.terraform*
Aqui estão minhas entradas de pipeline de CI em .gitlab-ci.yaml
:
$ 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"
O processo começa declarando que cada etapa e estágio estão sob a tag homelab
, permitindo que seu executor do GitLab o execute.
default:
tags:
- homelab
Em seguida, as variáveis são definidas no pipeline. As variáveis só estão presentes quando o pipeline está em execução:
variables:
TF_ROOT: ${CI_PROJECT_DIR}
TF_ADDRESS: ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/terraform/state/homelab
Há um cache que salva arquivos e diretórios específicos durante a execução de um estágio para outro:
cache:
key: homelab
paths:
- ${TF_ROOT}/.terraform*
Estas são as etapas que o pipeline segue:
stages:
- prepare
- validate
- build
- deploy
Isto declara o que fazer antes de qualquer estágio ser executado:
before_script:
- cd ${TF_ROOT}
No estágio prepare
, o tf-init
inicializa os scripts do Terraform, obtém o provedor e define seu back-end para o GitLab. Variáveis que ainda não foram declaradas serão adicionadas posteriormente como variáveis de ambiente.
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
Nesta parte, o trabalho de CI tf-validate
e o estágio validate
executam o Terraform para validar se os scripts do Terraform estão livres de erros de sintaxe. Variáveis ainda não declaradas são adicionadas como variáveis de ambiente posteriormente.
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
Em seguida, a tarefa de CI tf-build
com o estágio build
cria o arquivo de plano usando terraform plan
e o salva temporariamente usando os artifacts etiqueta
.
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
Na próxima seção, o trabalho de CI tf-deploy
com o estágio deploy
aplica o arquivo de plano.
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"
Existem variáveis, então você deve declará-las em Configurações → CI/CD → Variáveis → Expandir.
(AJ Canlas, CC BY-SA 4.0)
Adicione todas as variáveis necessárias:
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
Então, para este exemplo, usei o seguinte:
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"
E é mascarado pelo GitLab para sua proteção.
(AJ Canlas, CC BY-SA 4.0)
A última etapa é enviar os novos arquivos para o repositório:
$ 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
Veja os resultados
Veja seus novos pipelines na seção CI/CD do GitLab.
(AJ Canlas, CC BY-SA 4.0)
Do lado do OpenStack, você pode ver os recursos criados pelo Terraform.
As redes:
(AJ Canlas, CC BY-SA 4.0)
Os sabores:
(AJ Canlas, CC BY-SA 4.0)
As imagens:
(AJ Canlas, CC BY-SA 4.0)
O projeto:
(AJ Canlas, CC BY-SA 4.0)
O usuário:
(AJ Canlas, CC BY-SA 4.0)
Próximos passos
Terraform tem muito potencial. Terraform e Ansible são ótimos juntos. No meu próximo artigo, demonstrarei como o Ansible pode trabalhar com OpenStack