Поиск по сайту:

Управляйте OpenStack с помощью Terraform и GitLab


Следуйте этому руководству, чтобы узнать, как использование GitLab может еще больше улучшить совместную работу в вашем кластере OpenStack.

Одним из достоинств GitOps является инфраструктура как код. Это поощряет сотрудничество за счет использования общего хранилища конфигурации и политик. Использование GitLab может еще больше улучшить совместную работу в вашем кластере OpenStack. GitLab CI может служить центром управления исходным кодом и оркестрации для CI/CD и даже управлять состоянием Terraform.

Чтобы добиться этого, вам необходимо следующее:

  1. Учетная запись или экземпляр GitLab.
  2. Частный кластер OpenStack. Если у вас его нет, прочитайте мою статью «Настройка OpenStack в кластере Raspberry Pi».
  3. Компьютер (желательно хост-контейнер).

Состояние GitLab и Terraform

Цель состоит в том, чтобы добиться совместной работы с помощью Terraform, поэтому вам необходимо иметь централизованный файл состояния. В GitLab есть управляемое состояние для Terraform. С помощью этой функции вы можете предоставить отдельным пользователям возможность совместно управлять OpenStack.

Создайте группу и проект GitLab.

Войдите в GitLab, щелкните меню гамбургера и выберите ГруппыПросмотреть все группы.

(Эй Джей Канлас, CC BY-SA 4.0)

Создайте группу, нажав Новая группа, а затем Создать группу.

(Эй Джей Канлас, CC BY-SA 4.0)

Назовите группу, чтобы создать уникальный URL-адрес группы, и пригласите свою команду работать с вами.

(Эй Джей Канлас, CC BY-SA 4.0)

После создания группы создайте проект, нажав Создать новый проект, а затем Создать пустой проект:

(Эй Джей Канлас, CC BY-SA 4.0)

Назовите свой проект. GitLab генерирует для вас уникальный URL-адрес проекта. Этот проект содержит репозиторий для ваших скриптов Terraform и состояния Terraform.

Создайте токен личного доступа

Репозиторию необходим личный токен доступа для управления этим состоянием Terraform. В своем профиле выберите Редактировать профиль:.

(Эй Джей Канлас, CC BY-SA 4.0)

Нажмите Токен доступа на боковой панели, чтобы открыть меню для создания токена доступа. Сохраните свой токен, потому что вы не сможете просмотреть его снова.

(Эй Джей Канлас, CC BY-SA 4.0)

Клонировать пустой репозиторий

На компьютере с прямым доступом к вашей установке OpenStack клонируйте репозиторий, а затем перейдите в полученный каталог:

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

$ cd testproject

Создайте серверный файл .tf и файл поставщика.

Создайте бэкэнд-файл, чтобы настроить GitLab в качестве бэкэнда вашего состояния:

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

Этот файл провайдера извлекает провайдера для 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

Поскольку вы объявили переменную в поставщике, вы должны объявить ее в файле переменных:

$ 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

Эти сведения доступны в вашем файле rc на OpenStack.

Инициализируйте проект в 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, просмотрите сведения о проекте чуть выше вкладки Информация о проекте на боковой панели. Обычно это название вашего проекта.

(Эй Джей Канлас, CC BY-SA 4.0)

Для меня это 42580143.

Используйте свое имя пользователя для gitlab-username. Мой — ajohnsc.

gitlab-personal-access-token — это токен, который вы создали ранее в этом упражнении. В этом примере я использую wwwwwwwwwwwwwwwwww. Вы можете назвать ваше-уникальное-имя-состояния как угодно. Я использовал 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

Вывод аналогичен этому:

(Эй Джей Канлас, CC BY-SA 4.0)

Проверьте скрипт Terraform

Это устанавливает размер виртуальных машин для моих версий 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

Настройки моей внешней сети следующие:

$ 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 и перейдите к своему проекту. Откройте ИнфраструктураTerraform, чтобы убедиться, что состояние homelab создано.

(Эй Джей Канлас, 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

Ваш кластер OpenStack не является общедоступным, и API OpenStack не доступен. Для запуска конвейеров GitLab у вас должен быть исполнитель GitLab. Исполнители GitLab — это службы или агенты, которые запускают и выполняют задачи на удаленном сервере GitLab.

На компьютере в другой сети создайте контейнер для запуска 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

Теперь зарегистрируйте его в своем проекте на панели НастройкиCI/CD вашего проекта GitLab:

(Эй Джей Канлас, CC BY-SA 4.0)

Прокрутите вниз до пункта БегуныСвернуть:

(Эй Джей Канлас, CC BY-SA 4.0)

Требуется регистрационный токен и URL-адрес участника GitLab. Отключите общий бегун справа, чтобы убедиться, что он работает только на бегуне. Запустите контейнер gitlab-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 отобразит ваш бегун как действительный. Это выглядит так:

(Эй Джей Канлас, CC BY-SA 4.0)

Теперь вы можете использовать этот бегун для автоматизации подготовки с помощью конвейера CI/CD в GitLab.

Настройте конвейер GitLab

Теперь вы можете настроить конвейер. Добавьте файл с именем .gitlab-ci.yaml в свой репозиторий, чтобы определить шаги CI/CD. Игнорируйте ненужные файлы, например каталоги .terraform, и конфиденциальные данные, такие как файлы переменных.

Вот мой файл .gitignore:

$ cat .gitignore
*.tfvars
.terraform*

Вот мои записи конвейера CI в .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"

Процесс начинается с объявления того, что каждый шаг и этап находятся под тегом homelab, что позволяет вашему исполнителю GitLab запустить его.

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, получает поставщика и устанавливает для его серверной части 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, и временно сохраняет его, используя артефакты. тег .

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"

Существуют переменные, поэтому их необходимо объявить в НастройкиCI/CDПеременныеРазвернуть.

(Эй Джей Канлас, 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.

(Эй Джей Канлас, 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

Посмотреть результаты

Просмотрите свои новые конвейеры в разделе CI/CD на GitLab.

(Эй Джей Канлас, CC BY-SA 4.0)

На стороне OpenStack вы можете увидеть ресурсы, созданные Terraform.

Сети:

(Эй Джей Канлас, CC BY-SA 4.0)

Вкусы:

(Эй Джей Канлас, CC BY-SA 4.0)

Изображения:

(Эй Джей Канлас, CC BY-SA 4.0)

Проект:

(Эй Джей Канлас, CC BY-SA 4.0)

Пользователь:

(Эй Джей Канлас, CC BY-SA 4.0)

Следующие шаги

У Terraform огромный потенциал. Terraform и Ansible отлично сочетаются друг с другом. В моей следующей статье я продемонстрирую, как Ansible может работать с OpenStack.

Статьи по данной тематике: