{"id":71235,"date":"2023-02-15T09:03:06","date_gmt":"2023-02-15T09:03:06","guid":{"rendered":"https:\/\/www.cryptocabaret.com\/?p=71235"},"modified":"2023-02-15T09:03:06","modified_gmt":"2023-02-15T09:03:06","slug":"manage-openstack-using-terraform-and-gitlab","status":"publish","type":"post","link":"https:\/\/www.cryptocabaret.com\/?p=71235","title":{"rendered":"Manage OpenStack using Terraform and GitLab"},"content":{"rendered":"<p><span class=\"field field--name-title field--type-string field--label-hidden\">Manage OpenStack using Terraform and GitLab<\/span><br \/>\n<span class=\"field field--name-uid field--type-entity-reference field--label-hidden\"><a title=\"View user profile.\" href=\"https:\/\/opensource.com\/users\/ajscanlas\" class=\"username\">ajscanlas<\/a><\/span><br \/>\n<span class=\"field field--name-created field--type-created field--label-hidden\">Wed, 02\/15\/2023 &#8211; 03:00<\/span><\/p>\n<div class=\"clearfix text-formatted field field--name-body field--type-text-with-summary field--label-hidden field__item\">\n<p>One virtue of <a href=\"https:\/\/enterprisersproject.com\/article\/2021\/6\/gitops-explained-plain-english?intcmp=7013a000002qLH8AAM\" target=\"_blank\" rel=\"noopener\">GitOps<\/a> is Infrastructure as Code. It encourages collaboration by using a shared configuration and policy repository. Using GitLab can further enhance collaboration in your OpenStack cluster. GitLab CI can serve as your source control and orchestration hub for CI\/CD, and it can even manage the state of Terraform.<\/p>\n<p>To achieve this, you need the following:<\/p>\n<ol>\n<li><a href=\"http:\/\/gitlab.com\/\" target=\"_blank\" rel=\"noopener\">GitLab<\/a> account or instance.<\/li>\n<li>Private OpenStack cluster. If you don&#8217;t have one, read my article <a href=\"https:\/\/opensource.com\/article\/20\/12\/openstack-raspberry-pi\" target=\"_blank\" rel=\"noopener\">Set up OpenStack on a Raspberry Pi cluster<\/a>.<\/li>\n<li>A computer (preferably a container host).<\/li>\n<\/ol>\n<h2>GitLab and Terraform state<\/h2>\n<p>The goal is to achieve collaboration through Terraform, so you need to have a centralized state file. GitLab has a managed state for Terraform. With this feature, you can enable individuals to manage OpenStack collaboratively.<\/p>\n<h2>Create a GitLab group and project<\/h2>\n<p>Log in to GitLab, click on the hamburger menu, and click <strong>Groups<\/strong>\u2192<strong>View all groups<\/strong>.<\/p>\n<article class=\"align-center media media--type-image media--view-mode-default\">\n<div class=\"field field--name-field-media-image field--type-image field--label-hidden field__item\">  <img decoding=\"async\" loading=\"lazy\" src=\"https:\/\/www.cryptocabaret.com\/wp-content\/uploads\/2023\/02\/1.png\" width=\"3348\" height=\"900\" alt=\"view all groups\"><\/div>\n<div class=\"field field--name-field-caption field--type-text-long field--label-hidden caption field__item\"><span class=\"caption__byline\">Image by: <\/span><\/p>\n<p>(AJ Canlas, CC BY-SA 4.0)<\/p>\n<\/div>\n<\/article>\n<p>Create a group by clicking on <strong>New group <\/strong>and then on <strong>Create group<\/strong>.<\/p>\n<article class=\"align-center media media--type-image media--view-mode-default\">\n<div class=\"field field--name-field-media-image field--type-image field--label-hidden field__item\">  <img decoding=\"async\" loading=\"lazy\" src=\"https:\/\/www.cryptocabaret.com\/wp-content\/uploads\/2023\/02\/3.png\" width=\"3358\" height=\"874\" alt=\"Create Group\"><\/div>\n<div class=\"field field--name-field-caption field--type-text-long field--label-hidden caption field__item\"><span class=\"caption__byline\">Image by: <\/span><\/p>\n<p>(AJ Canlas, CC BY-SA 4.0)<\/p>\n<\/div>\n<\/article>\n<p>Name the group to generate a unique group URL, and invite your team to work with you.<\/p>\n<article class=\"align-center media media--type-image media--view-mode-default\">\n<div class=\"field field--name-field-media-image field--type-image field--label-hidden field__item\">  <img decoding=\"async\" loading=\"lazy\" src=\"https:\/\/www.cryptocabaret.com\/wp-content\/uploads\/2023\/02\/4.png\" width=\"3360\" height=\"1864\" alt=\"Name Group\"><\/div>\n<div class=\"field field--name-field-caption field--type-text-long field--label-hidden caption field__item\"><span class=\"caption__byline\">Image by: <\/span><\/p>\n<p>(AJ Canlas, CC BY-SA 4.0)<\/p>\n<\/div>\n<\/article>\n<p>After creating a group, create a project by clicking <strong>Create new project<\/strong>, and then <strong>Create blank project<\/strong>:<\/p>\n<article class=\"align-center media media--type-image media--view-mode-default\">\n<div class=\"field field--name-field-media-image field--type-image field--label-hidden field__item\">  <img decoding=\"async\" loading=\"lazy\" src=\"https:\/\/www.cryptocabaret.com\/wp-content\/uploads\/2023\/02\/6.png\" width=\"3360\" height=\"1484\" alt=\"Create from blank project\"><\/div>\n<div class=\"field field--name-field-caption field--type-text-long field--label-hidden caption field__item\"><span class=\"caption__byline\">Image by: <\/span><\/p>\n<p>(AJ Canlas, CC BY-SA 4.0)<\/p>\n<\/div>\n<\/article>\n<p>Name your project. GitLab generates a unique project URL for you. This project contains the repository for your Terraform scripts and Terraform state.<\/p>\n<h2>Create a personal access token<\/h2>\n<p>The repository needs a personal access token to manage this Terraform state. In your profile, select <strong>Edit Profile:<\/strong><\/p>\n<article class=\"align-center media media--type-image media--view-mode-default\">\n<div class=\"field field--name-field-media-image field--type-image field--label-hidden field__item\">  <img decoding=\"async\" loading=\"lazy\" src=\"https:\/\/www.cryptocabaret.com\/wp-content\/uploads\/2023\/02\/8.png\" width=\"3354\" height=\"1868\" alt=\"Edit profile\"><\/div>\n<div class=\"field field--name-field-caption field--type-text-long field--label-hidden caption field__item\"><span class=\"caption__byline\">Image by: <\/span><\/p>\n<p>(AJ Canlas, CC BY-SA 4.0)<\/p>\n<\/div>\n<\/article>\n<p>Click <strong>Access Token<\/strong> in the side panel to access a menu for creating an access token. Save your token because you can&#8217;t view it again.<\/p>\n<article class=\"align-center media media--type-image media--view-mode-default\">\n<div class=\"field field--name-field-media-image field--type-image field--label-hidden field__item\">  <img decoding=\"async\" loading=\"lazy\" src=\"https:\/\/www.cryptocabaret.com\/wp-content\/uploads\/2023\/02\/9.png\" width=\"3354\" height=\"1820\" alt=\"Access Token\"><\/div>\n<div class=\"field field--name-field-caption field--type-text-long field--label-hidden caption field__item\"><span class=\"caption__byline\">Image by: <\/span><\/p>\n<p>(AJ Canlas, CC BY-SA 4.0)<\/p>\n<\/div>\n<\/article>\n<h2>Clone the empty repository<\/h2>\n<p>On a computer with direct access to your OpenStack installation, clone the repository and then change to the resulting directory:<\/p>\n<pre>\n<code class=\"language-shell\">$ git clone git@gitlab.com:testgroup2170\/testproject.git\n\n$ cd testproject<\/code><\/pre>\n<h2>Create the backend .tf and provider file<\/h2>\n<p>Create a backend file to configure GitLab as your state backend:<\/p>\n<pre>\n<code class=\"language-shell\">$ cat &gt;&gt; backend.tf <\/code><\/pre>\n<p>This provider file pulls the provider for OpenStack:<\/p>\n<pre>\n<code class=\"language-shell\">$ cat &gt;&gt; provider.tf = 0.14.0\"\n  required_providers {\n    openstack = {\n      source  = \"terraform-provider-openstack\/openstack\"\n      version = \"1.49.0\"\n    }\n  }\n}\n\nprovider \"openstack\" {\n  user_name   = var.OS_USERNAME\n  tenant_name = var.OS_TENANT\n  password    = var.OS_PASSWORD\n  auth_url    = var.OS_AUTH_URL\n  region      = var.OS_REGION\n}\nEOF<\/code><\/pre>\n<p>Because you&#8217;ve declared a variable in the provider, you must declare it in a variable file:<\/p>\n<pre>\n<code class=\"language-shell\">$ cat &gt;&gt; variables.tf <\/code><\/pre>\n<p>Because you&#8217;re initially working locally, you must set those variables to make it work:<\/p>\n<pre>\n<code class=\"language-shell\">$ cat &gt;&gt; terraform.tfvars <\/code><\/pre>\n<p>These details are available on your <code>rc<\/code> file on OpenStack.<\/p>\n<h2>Initialize the project in Terraform<\/h2>\n<p>Initializing the project is quite different because you need to tell Terraform to use GitLab as your state backend:<\/p>\n<pre>\n<code class=\"language-plaintext\">PROJECT_ID=\"<gitlab-project-id>\"\nTF_USERNAME=\"<gitlab-username>\"\nTF_PASSWORD=\"<gitlab-personal-access-token>\"\nTF_STATE_NAME=\"<your-unique-state-name>\"\nTF_ADDRESS=\"https:\/\/gitlab.com\/api\/v4\/projects\/${PROJECT_ID}\/terraform\/state\/${TF_STATE_NAME}\"\n\n$ terraform init \n  -backend-config=address=${TF_ADDRESS} \n  -backend-config=lock_address=${TF_ADDRESS}\/lock \n  -backend-config=unlock_address=${TF_ADDRESS}\/lock \n  -backend-config=username=${TF_USERNAME} \n  -backend-config=password=${TF_PASSWORD} \n  -backend-config=lock_method=POST \n  -backend-config=unlock_method=DELETE \n  -backend-config=retry_wait_min=5<\/your-unique-state-name><\/gitlab-personal-access-token><\/gitlab-username><\/gitlab-project-id><\/code><\/pre>\n<p>To view the <code>gitlab-project-id<\/code>, look in the project details just above the <strong>Project Information<\/strong> tab in the side panel. It&#8217;s usually your project name.<\/p>\n<article class=\"align-center media media--type-image media--view-mode-default\">\n<div class=\"field field--name-field-media-image field--type-image field--label-hidden field__item\">  <img decoding=\"async\" loading=\"lazy\" src=\"https:\/\/www.cryptocabaret.com\/wp-content\/uploads\/2023\/02\/10.png\" width=\"3356\" height=\"1192\" alt=\"Project ID\"><\/div>\n<div class=\"field field--name-field-caption field--type-text-long field--label-hidden caption field__item\"><span class=\"caption__byline\">Image by: <\/span><\/p>\n<p>(AJ Canlas, CC BY-SA 4.0)<\/p>\n<\/div>\n<\/article>\n<p>For me, it&#8217;s <code>42580143<\/code>.<\/p>\n<p>Use your username for <code>gitlab-username<\/code>. Mine is <code>ajohnsc<\/code>.<\/p>\n<p>The <code>gitlab-personal-access-token<\/code> is the token you created earlier in this exercise. In this example, I use <code>wwwwwwwwwwwwwwwwwwwww<\/code>. You can name <code>your-unique-state-name<\/code> anything. I used <code>homelab<\/code>.<\/p>\n<p>Here is my initialization script:<\/p>\n<pre>\n<code class=\"language-plaintext\">PROJECT_ID=\"42580143\"\nTF_USERNAME=\"ajohnsc\"\nTF_PASSWORD=\"wwwwwwwwwwwwwwwwwwwww\"\nTF_STATE_NAME=\"homelab\"\nTF_ADDRESS=\"https:\/\/gitlab.com\/api\/v4\/projects\/${PROJECT_ID}\/terraform\/state\/${TF_STATE_NAME}\"<\/code><\/pre>\n<p>To use the file:<\/p>\n<pre>\n<code class=\"language-shell\">$ terraform init \n  -backend-config=address=${TF_ADDRESS} \n  -backend-config=lock_address=${TF_ADDRESS}\/lock \n  -backend-config=unlock_address=${TF_ADDRESS}\/lock \n  -backend-config=username=${TF_USERNAME} \n  -backend-config=password=${TF_PASSWORD} \n  -backend-config=lock_method=POST \n  -backend-config=unlock_method=DELETE \n  -backend-config=retry_wait_min=5<\/code><\/pre>\n<p>The output is similar to this:<\/p>\n<article class=\"align-center media media--type-image media--view-mode-default\">\n<div class=\"field field--name-field-media-image field--type-image field--label-hidden field__item\">  <img decoding=\"async\" loading=\"lazy\" src=\"https:\/\/www.cryptocabaret.com\/wp-content\/uploads\/2023\/02\/11.png\" width=\"1762\" height=\"1048\" alt=\"terraform init\"><\/div>\n<div class=\"field field--name-field-caption field--type-text-long field--label-hidden caption field__item\"><span class=\"caption__byline\">Image by: <\/span><\/p>\n<p>(AJ Canlas, CC BY-SA 4.0)<\/p>\n<\/div>\n<\/article>\n<h2>Test the Terraform script<\/h2>\n<p>This sets the size of the VMs for my OpenStack flavors:<\/p>\n<pre>\n<code class=\"language-shell\">$ cat &gt;&gt; flavors.tf <\/code><\/pre>\n<p>The settings for my external network are as follows:<\/p>\n<pre>\n<code class=\"language-shell\">$ cat &gt;&gt; external-network.tf <\/code><\/pre>\n<p>Router settings look like this:<\/p>\n<pre>\n<code class=\"language-shell\">$ cat &gt;&gt; routers.tf <\/code><\/pre>\n<p>Enter the following for images:<\/p>\n<pre>\n<code class=\"language-shell\">$ cat &gt;&gt; images.tf <\/code><\/pre>\n<p>Here is a Demo tenant:<\/p>\n<pre>\n<code class=\"language-shell\">$ cat &gt;&gt; demo-project-user.tf <\/code><\/pre>\n<p>When complete, you will have this file structure:<\/p>\n<pre>\n<code class=\"language-plaintext\">.\n\u251c\u2500\u2500 backend.tf\n\u251c\u2500\u2500 demo-project-user.tf\n\u251c\u2500\u2500 external-network.tf\n\u251c\u2500\u2500 flavors.tf\n\u251c\u2500\u2500 images.tf\n\u251c\u2500\u2500 provider.tf\n\u251c\u2500\u2500 routers.tf\n\u251c\u2500\u2500 terraform.tfvars\n\u2514\u2500\u2500 variables.tf<\/code><\/pre>\n<h2>Issue plan<\/h2>\n<p>After the files are complete, you can create the plan files with the <code>terraform plan<\/code> command:<\/p>\n<pre>\n<code class=\"language-shell\">$ terraform plan\nAcquiring state lock. This may take a few moments...\n\nTerraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:\n  + create\n\nTerraform will perform the following actions:\n\n  # openstack_compute_flavor_v2.large-flavor will be created\n  + resource \"openstack_compute_flavor_v2\" \"large-flavor\" {\n      + disk         = 0\n      + extra_specs  = (known after apply)\n      + flavor_id    = \"3\"\n      + id           = (known after apply)\n      + is_public    = true\n      + name         = \"large\"\n      + ram          = 16384\n      + region       = (known after apply)\n      + rx_tx_factor = 1\n      + vcpus        = 4\n    }\n\n[...]\n\nPlan: 10 to add,\nReleasing state lock. This may take a few moments...<\/code><\/pre>\n<p>After all plan files have been created, apply them with the <code>terraform apply<\/code> command:<\/p>\n<pre>\n<code class=\"language-shell\">$ terraform apply -auto-approve\nAcquiring state lock. This may take a few moments...\n[...]\nPlan: 10 to add, 0 to change, 0 to destroy.\nopenstack_compute_flavor_v2.large-flavor: Creating...\nopenstack_compute_flavor_v2.small-flavor: Creating...\nopenstack_identity_project_v3.demo-project: Creating...\nopenstack_networking_network_v2.external-network: Creating...\nopenstack_compute_flavor_v2.xlarge-flavor: Creating...\nopenstack_compute_flavor_v2.medium-flavor: Creating...\nopenstack_images_image_v2.cirros: Creating...\n[...]\nReleasing state lock. This may take a few moments...\n\nApply complete! Resources: 10 added, 0 changed, 0 destroyed.<\/code><\/pre>\n<p>After applying the infrastructure, return to GitLab and navigate to your project. Look in <strong>Infrastructure<\/strong> \u2192 <strong>Terraform<\/strong> to confirm that the state <code>homelab<\/code> has been created.<\/p>\n<article class=\"align-center media media--type-image media--view-mode-default\">\n<div class=\"field field--name-field-media-image field--type-image field--label-hidden field__item\">  <img decoding=\"async\" loading=\"lazy\" src=\"https:\/\/www.cryptocabaret.com\/wp-content\/uploads\/2023\/02\/12.png\" width=\"3360\" height=\"1380\" alt=\"Gitlab State file\"><\/div>\n<div class=\"field field--name-field-caption field--type-text-long field--label-hidden caption field__item\"><span class=\"caption__byline\">Image by: <\/span><\/p>\n<p>(AJ Canlas, CC BY-SA 4.0)<\/p>\n<\/div>\n<\/article>\n<h2>Destroy the state to test CI<\/h2>\n<p>Now that you&#8217;ve created a state, try destroying the infrastructure so you can apply the CI pipeline later. Of course, this is purely for moving from Terraform CLI to a Pipeline. If you have an existing infrastructure, you can skip this step.<\/p>\n<pre>\n<code class=\"language-shell\">$ terraform destroy -auto-approve\nAcquiring state lock. This may take a few moments...\nopenstack_identity_project_v3.demo-project: Refreshing state... [id=5f86d4229003404998dfddc5b9f4aeb0]\nopenstack_networking_network_v2.external-network: Refreshing state... [id=012c10f3-8a51-4892-a688-aa9b7b43f03d]\n[...]\nPlan: 0 to add, 0 to change, 10 to destroy.\nopenstack_compute_flavor_v2.small-flavor: Destroying... [id=1]\nopenstack_compute_flavor_v2.xlarge-flavor: Destroying... [id=4]\nopenstack_networking_router_v2.external-router: Destroying... [id=73ece9e7-87d7-431d-ad6f-09736a02844d]\nopenstack_compute_flavor_v2.large-flavor: Destroying... [id=3]\nopenstack_identity_user_v3.demo-user: Destroying... [id=96b48752e999424e95bc690f577402ce]\n[...]\nDestroy complete! Resources: 10 destroyed.<\/code><\/pre>\n<p>You now have a state everyone can use. You can provision using a centralized state. With the proper pipeline, you can automate common tasks.<\/p>\n<h2>Set up a GitLab runner<\/h2>\n<p>Your OpenStack cluster isn&#8217;t public-facing, and the OpenStack API isn&#8217;t exposed. You must have a GitLab runner to run GitLab pipelines. GitLab runners are services or agents that run and perform tasks on the remote GitLab server.<\/p>\n<p>On a computer on a different network, create a container for a GitLab runner:<\/p>\n<pre>\n<code class=\"language-shell\">$ docker volume create gitlab-runner-config\n\n$ docker run -d --name gitlab-runner --restart always \n  -v \/var\/run\/docker.sock:\/var\/run\/docker.sock \n  -v gitlab-runner-config:\/etc\/gitlab-runner \n  gitlab\/gitlab-runner:latest\n\n$ docker ps\nCONTAINER ID   IMAGE                           COMMAND                  CREATED         STATUS         PORTS                                       NAMES\n880e2ed289d3   gitlab\/gitlab-runner:latest     \"\/usr\/bin\/dumb-init \u2026\"   3 seconds ago   Up 2 seconds                                               gitlab-runner-test<\/code><\/pre>\n<p>Now register it with your project in your GitLab project&#8217;s <strong>Settings<\/strong> \u2192 <strong>CI\/CD<\/strong> panel:<\/p>\n<article class=\"align-center media media--type-image media--view-mode-default\">\n<div class=\"field field--name-field-media-image field--type-image field--label-hidden field__item\">  <img decoding=\"async\" loading=\"lazy\" src=\"https:\/\/www.cryptocabaret.com\/wp-content\/uploads\/2023\/02\/13.png\" width=\"1060\" height=\"1344\" alt=\"Gitlab runner register\"><\/div>\n<div class=\"field field--name-field-caption field--type-text-long field--label-hidden caption field__item\"><span class=\"caption__byline\">Image by: <\/span><\/p>\n<p>(AJ Canlas, CC BY-SA 4.0)<\/p>\n<\/div>\n<\/article>\n<p>Scroll down to <strong>Runners<\/strong> \u2192 <strong>Collapse<\/strong>:<\/p>\n<article class=\"align-center media media--type-image media--view-mode-default\">\n<div class=\"field field--name-field-media-image field--type-image field--label-hidden field__item\">  <img decoding=\"async\" loading=\"lazy\" src=\"https:\/\/www.cryptocabaret.com\/wp-content\/uploads\/2023\/02\/14.png\" width=\"3360\" height=\"1742\" alt=\"Gitlab runner registration\"><\/div>\n<div class=\"field field--name-field-caption field--type-text-long field--label-hidden caption field__item\"><span class=\"caption__byline\">Image by: <\/span><\/p>\n<p>(AJ Canlas, CC BY-SA 4.0)<\/p>\n<\/div>\n<\/article>\n<p>The GitLab runner registration token and URL are required. Disable the shared runner on the right side to ensure it works on the runner only. Run the <code>gitlab-runner<\/code> container to register the runner:<\/p>\n<pre>\n<code class=\"language-shell\">$ docker exec -ti gitlab-runner \/usr\/bin\/gitlab-runner register\nRuntime platform                                    arch=amd64 os=linux pid=18 revision=6d480948 version=15.7.1\nRunning in system-mode.                            \n                                                   \nEnter the GitLab instance URL (for example, https:\/\/gitlab.com\/):\nhttps:\/\/gitlab.com\/\nEnter the registration token:\nGR1348941S1bVeb1os44ycqsdupRK\nEnter a description for the runner:\n[880e2ed289d3]: dockerhost\nEnter tags for the runner (comma-separated):\nhomelab\nEnter optional maintenance note for the runner:\n\nWARNING: 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 \nRegistering runner... succeeded                     runner=GR1348941S1bVeb1o\nEnter an executor: docker-ssh, shell, virtualbox, instance, kubernetes, custom, docker, parallels, ssh, docker+machine, docker-ssh+machine:\ndocker\nEnter the default Docker image (for example, ruby:2.7):\najscanlas\/homelab-runner:3.17\nRunner registered successfully. Feel free to start it, but if it's running already the config should be automatically reloaded!\n \nConfiguration (with the authentication token) was saved in \"\/etc\/gitlab-runner\/config.toml\" <\/code><\/pre>\n<p>Upon success, your GitLab interface displays your runner as valid. It looks like this:<\/p>\n<article class=\"align-center media media--type-image media--view-mode-default\">\n<div class=\"field field--name-field-media-image field--type-image field--label-hidden field__item\">  <img decoding=\"async\" loading=\"lazy\" src=\"https:\/\/www.cryptocabaret.com\/wp-content\/uploads\/2023\/02\/15.png\" width=\"3348\" height=\"1834\" alt=\"Specific Runner\"><\/div>\n<div class=\"field field--name-field-caption field--type-text-long field--label-hidden caption field__item\"><span class=\"caption__byline\">Image by: <\/span><\/p>\n<p>(AJ Canlas, CC BY-SA 4.0)<\/p>\n<\/div>\n<\/article>\n<div class=\"embedded-resource-list callout-float-right\">\n<div class=\"field field--name-title field--type-string field--label-hidden field__item\">Explore the open source cloud<\/div>\n<div class=\"field field--name-links field--type-link field--label-hidden field__items\">\n<div class=\"field__item\"><a href=\"https:\/\/www.redhat.com\/en\/services\/training\/do092-developing-cloud-native-applications-microservices-architectures?intcmp=7013a000002gp8aAAA\">Free online course: Developing cloud-native applications with microservices<\/a><\/div>\n<div class=\"field__item\"><a href=\"https:\/\/www.redhat.com\/en\/engage\/managed-cloud-services-s-202202210207?intcmp=7013a000002gp8aAAA\">eBook: Modernize your IT with managed cloud services<\/a><\/div>\n<div class=\"field__item\"><a href=\"https:\/\/cloud.redhat.com\/products\/dedicated\/?intcmp=7013a000002gp8aAAA\">Try for 60 days: Red Hat OpenShift Dedicated<\/a><\/div>\n<div class=\"field__item\"><a href=\"https:\/\/www.redhat.com\/en\/services\/training\/do080-deploying-containerized-applications-technical-overview?intcmp=7013a000002gp8aAAA\">Free online course: Containers, Kubernetes and Red Hat OpenShift<\/a><\/div>\n<div class=\"field__item\"><a href=\"https:\/\/www.redhat.com\/en\/topics\/containers\/what-is-kubernetes?intcmp=7013a000002gp8aAAA\">What is Kubernetes?<\/a><\/div>\n<div class=\"field__item\"><a href=\"https:\/\/www.redhat.com\/en\/topics\/edge-computing?intcmp=7013a000002gp8aAAA\">Understanding edge computing<\/a><\/div>\n<div class=\"field__item\"><a href=\"https:\/\/www.redhat.com\/architect\/?intcmp=7013a000002gp8aAAA\">Latest articles for IT architects<\/a><\/div>\n<\/p><\/div>\n<\/p><\/div>\n<p>You can now use that runner to automate provisioning with a CI\/CD pipeline in GitLab.<\/p>\n<h2>Set up the GitLab pipeline<\/h2>\n<p>Now you can set up a pipeline. Add a file named <code>.gitlab-ci.yaml<\/code> in your repository to define your CI\/CD steps. Ignore the files you don&#8217;t need, like <code>.terraform<\/code> directories and sensitive data like variable files.<\/p>\n<p>Here&#8217;s my <code>.gitignore<\/code> file:<\/p>\n<pre>\n<code class=\"language-shell\">$ cat .gitignore\n*.tfvars\n.terraform*<\/code><\/pre>\n<p>Here are my CI pipeline entries in <code>.gitlab-ci.yaml<\/code>:<\/p>\n<pre>\n<code class=\"language-shell\">$ cat .gitlab-ci.yaml\ndefault:\n  tags:\n    - homelab\n\nvariables:\n  TF_ROOT: ${CI_PROJECT_DIR}\n  TF_ADDRESS: ${CI_API_V4_URL}\/projects\/${CI_PROJECT_ID}\/terraform\/state\/homelab\n\ncache:\n  key: homelab\n  paths:\n    - ${TF_ROOT}\/.terraform*\n\nstages:\n  - prepare\n  - validate\n  - build\n  - deploy\n\nbefore_script:\n  - cd ${TF_ROOT}\n\ntf-init:\n  stage: prepare\n  script:\n    - terraform --version\n    - 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\n\ntf-validate:\n  stage: validate\n  dependencies:\n    - tf-init\n  variables:\n    TF_VAR_OS_AUTH_URL: ${OS_AUTH_URL}\n    TF_VAR_OS_PASSWORD: ${OS_PASSWORD}\n    TF_VAR_OS_REGION: ${OS_REGION}\n    TF_VAR_OS_TENANT: ${OS_TENANT}\n    TF_VAR_OS_USERNAME: ${OS_USERNAME}\n  script:\n    - terraform validate\n\ntf-build:\n  stage: build\n  dependencies:\n    - tf-validate\n  variables:\n    TF_VAR_OS_AUTH_URL: ${OS_AUTH_URL}\n    TF_VAR_OS_PASSWORD: ${OS_PASSWORD}\n    TF_VAR_OS_REGION: ${OS_REGION}\n    TF_VAR_OS_TENANT: ${OS_TENANT}\n    TF_VAR_OS_USERNAME: ${OS_USERNAME}\n  script:\n    - terraform plan -out \"planfile\"\n  artifacts:\n    paths:\n      - ${TF_ROOT}\/planfile\n\ntf-deploy:\n  stage: deploy\n  dependencies:\n    - tf-build\n  variables:\n    TF_VAR_OS_AUTH_URL: ${OS_AUTH_URL}\n    TF_VAR_OS_PASSWORD: ${OS_PASSWORD}\n    TF_VAR_OS_REGION: ${OS_REGION}\n    TF_VAR_OS_TENANT: ${OS_TENANT}\n    TF_VAR_OS_USERNAME: ${OS_USERNAME}\n  script:\n    - terraform apply -auto-approve \"planfile\"<\/code><\/pre>\n<p>The process starts by declaring that every step and stage is under the <code>homelab<\/code> tag, allowing your GitLab runner to run it.<\/p>\n<pre>\n<code class=\"language-plaintext\">default:\n  tags:\n    - homelab<\/code><\/pre>\n<p>Next, the variables are set on the pipeline. The variables are only present when the pipeline is running:<\/p>\n<pre>\n<code class=\"language-shell\">variables:\n  TF_ROOT: ${CI_PROJECT_DIR}\n  TF_ADDRESS: ${CI_API_V4_URL}\/projects\/${CI_PROJECT_ID}\/terraform\/state\/homelab<\/code><\/pre>\n<p>There&#8217;s a cache that saves specific files and directories upon running from stage to stage:<\/p>\n<pre>\n<code class=\"language-shell\">cache:\n  key: homelab\n  paths:\n    - ${TF_ROOT}\/.terraform*<\/code><\/pre>\n<p>These are the stages that the pipeline follows:<\/p>\n<pre>\n<code class=\"language-plaintext\">stages:\n  - prepare\n  - validate\n  - build\n  - deploy<\/code><\/pre>\n<p>This declares what to do before any stages are run:<\/p>\n<pre>\n<code class=\"language-plaintext\">before_script:\n  - cd ${TF_ROOT}<\/code><\/pre>\n<p>In the <code>prepare<\/code> stage, the <code>tf-init<\/code> initializes the Terraform scripts, gets the provider, and sets its backend to GitLab. Variables that aren&#8217;t declared yet are added as environment variables later.<\/p>\n<pre>\n<code class=\"language-plaintext\">tf-init:\n  stage: prepare\n  script:\n    - terraform --version\n    - 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<\/code><\/pre>\n<p>In this part, the CI job <code>tf-validate<\/code> and the stage <code>validate<\/code> run Terraform to validate that the Terraform scripts are free of syntax errors. Variables not yet declared are added as environment variables later.<\/p>\n<pre>\n<code class=\"language-plaintext\">tf-validate:\n  stage: validate\n  dependencies:\n    - tf-init\n  variables:\n    TF_VAR_OS_AUTH_URL: ${OS_AUTH_URL}\n    TF_VAR_OS_PASSWORD: ${OS_PASSWORD}\n    TF_VAR_OS_REGION: ${OS_REGION}\n    TF_VAR_OS_TENANT: ${OS_TENANT}\n    TF_VAR_OS_USERNAME: ${OS_USERNAME}\n  script:\n    - terraform validate<\/code><\/pre>\n<p>Next, the CI job <code>tf-build<\/code> with the stage <code>build<\/code> creates the plan file using <code>terraform plan<\/code> and temporarily saves it using the <code>artifacts<\/code> tag.<\/p>\n<pre>\n<code class=\"language-plaintext\">tf-build:\n  stage: build\n  dependencies:\n    - tf-validate\n  variables:\n    TF_VAR_OS_AUTH_URL: ${OS_AUTH_URL}\n    TF_VAR_OS_PASSWORD: ${OS_PASSWORD}\n    TF_VAR_OS_REGION: ${OS_REGION}\n    TF_VAR_OS_TENANT: ${OS_TENANT}\n    TF_VAR_OS_USERNAME: ${OS_USERNAME}\n  script:\n    - terraform plan -out \"planfile\"\n  artifacts:\n    paths:\n      - ${TF_ROOT}\/planfile<\/code><\/pre>\n<p>In the next section, the CI job <code>tf-deploy<\/code> with the stage <code>deploy<\/code> applies the plan file.<\/p>\n<pre>\n<code class=\"language-plaintext\">tf-deploy:\n  stage: deploy\n  dependencies:\n    - tf-build\n  variables:\n    TF_VAR_OS_AUTH_URL: ${OS_AUTH_URL}\n    TF_VAR_OS_PASSWORD: ${OS_PASSWORD}\n    TF_VAR_OS_REGION: ${OS_REGION}\n    TF_VAR_OS_TENANT: ${OS_TENANT}\n    TF_VAR_OS_USERNAME: ${OS_USERNAME}\n  script:\n    - terraform apply -auto-approve \"planfile\"<\/code><\/pre>\n<p>There are variables, so you must declare them in <strong>Settings<\/strong> \u2192 <strong>CI\/CD<\/strong> \u2192 <strong>Variables<\/strong> \u2192 <strong>Expand<\/strong>.<\/p>\n<article class=\"align-center media media--type-image media--view-mode-default\">\n<div class=\"field field--name-field-media-image field--type-image field--label-hidden field__item\">  <img decoding=\"async\" loading=\"lazy\" src=\"https:\/\/www.cryptocabaret.com\/wp-content\/uploads\/2023\/02\/16.png\" width=\"3360\" height=\"1802\" alt=\"Gitlab Environment Variables\"><\/div>\n<div class=\"field field--name-field-caption field--type-text-long field--label-hidden caption field__item\"><span class=\"caption__byline\">Image by: <\/span><\/p>\n<p>(AJ Canlas, CC BY-SA 4.0)<\/p>\n<\/div>\n<\/article>\n<p>Add all the variables required:<\/p>\n<pre>\n<code class=\"language-plaintext\">BE_ACCESS_TOKEN =&gt; GitLab Access Token\nBE_REMOTE_STATE_ADDRESS =&gt; This was the rendered TF_ADDRESS variable\nBE_USERNAME =&gt; GitLab username\nOS_USERNAME =&gt; OpenStack Username\nOS_TENANT   =&gt; OpenStack tenant\nOS_PASSWORD =&gt; OpenStack User Password\nOS_AUTH_URL =&gt; Auth URL\nOS_REGION   =&gt; OpenStack Region<\/code><\/pre>\n<p>So for this example, I used the following:<\/p>\n<pre>\n<code class=\"language-plaintext\">BE_ACCESS_TOKEN = \"wwwwwwwwwwwwwwwwwwwww\"\nBE_REMOTE_STATE_ADDRESS = https:\/\/gitlab.com\/api\/v4\/projects\/42580143\/terraform\/state\/homelab\nBE_USERNAME = \"ajohnsc\"\nOS_USERNAME = \"admin\"\nOS_TENANT   = \"admin\"\nOS_PASSWORD = \"YYYYYYYYYYYYYYYYYYYYYY\"\nOS_AUTH_URL = \"http:\/\/X.X.X.X:35357\/v3\"\nOS_REGION   = \"RegionOne\"<\/code><\/pre>\n<p>And it is masked GitLab for its protection.<\/p>\n<article class=\"align-center media media--type-image media--view-mode-default\">\n<div class=\"field field--name-field-media-image field--type-image field--label-hidden field__item\">  <img decoding=\"async\" loading=\"lazy\" src=\"https:\/\/www.cryptocabaret.com\/wp-content\/uploads\/2023\/02\/17.png\" width=\"3342\" height=\"1668\" alt=\"Gitlab variable view\"><\/div>\n<div class=\"field field--name-field-caption field--type-text-long field--label-hidden caption field__item\"><span class=\"caption__byline\">Image by: <\/span><\/p>\n<p>(AJ Canlas, CC BY-SA 4.0)<\/p>\n<\/div>\n<\/article>\n<p>The last step is to push the new files to the repository:<\/p>\n<pre>\n<code class=\"language-shell\">$ git add .\n\n$ git commit -m \"First commit\"\n[main (root-commit) e78f701] First commit\n 10 files changed, 194 insertions(+)\n create mode 100644 .gitignore\n create mode 100644 .gitlab-ci.yml\n create mode 100644 backend.tf\n create mode 100644 demo-project-user.tf\n create mode 100644 external-network.tf\n create mode 100644 flavors.tf\n create mode 100644 images.tf\n create mode 100644 provider.tf\n create mode 100644 routers.tf\n create mode 100644 variables.tf\n\n$ git push\nEnumerating objects: 12, done.\nCounting objects: 100% (12\/12), done.\nDelta compression using up to 4 threads\nCompressing objects: 100% (10\/10), done.\nWriting objects: 100% (12\/12), 2.34 KiB | 479.00 KiB\/s, done.\nTotal 12 (delta 0), reused 0 (delta 0), pack-reused 0\nTo gitlab.com:testgroup2170\/testproject.git\n * [new branch]      main -&gt; main<\/code><\/pre>\n<h2>View the results<\/h2>\n<p>View your new pipelines in the <strong>CI\/CD<\/strong> section of GitLab.<\/p>\n<article class=\"align-center media media--type-image media--view-mode-default\">\n<div class=\"field field--name-field-media-image field--type-image field--label-hidden field__item\">  <img decoding=\"async\" loading=\"lazy\" src=\"https:\/\/www.cryptocabaret.com\/wp-content\/uploads\/2023\/02\/18.png\" width=\"3360\" height=\"1464\" alt=\"Pipelines\"><\/div>\n<div class=\"field field--name-field-caption field--type-text-long field--label-hidden caption field__item\"><span class=\"caption__byline\">Image by: <\/span><\/p>\n<p>(AJ Canlas, CC BY-SA 4.0)<\/p>\n<\/div>\n<\/article>\n<p>On the OpenStack side, you can see the resources created by Terraform.<\/p>\n<p>The networks:<\/p>\n<article class=\"align-center media media--type-image media--view-mode-default\">\n<div class=\"field field--name-field-media-image field--type-image field--label-hidden field__item\">  <img decoding=\"async\" loading=\"lazy\" src=\"https:\/\/www.cryptocabaret.com\/wp-content\/uploads\/2023\/02\/19.png\" width=\"3358\" height=\"1102\" alt=\"Networks\"><\/div>\n<div class=\"field field--name-field-caption field--type-text-long field--label-hidden caption field__item\"><span class=\"caption__byline\">Image by: <\/span><\/p>\n<p>(AJ Canlas, CC BY-SA 4.0)<\/p>\n<\/div>\n<\/article>\n<p>The flavors:<\/p>\n<article class=\"align-center media media--type-image media--view-mode-default\">\n<div class=\"field field--name-field-media-image field--type-image field--label-hidden field__item\">  <img decoding=\"async\" loading=\"lazy\" src=\"https:\/\/www.cryptocabaret.com\/wp-content\/uploads\/2023\/02\/20.png\" width=\"3360\" height=\"1232\" alt=\"Flavors\"><\/div>\n<div class=\"field field--name-field-caption field--type-text-long field--label-hidden caption field__item\"><span class=\"caption__byline\">Image by: <\/span><\/p>\n<p>(AJ Canlas, CC BY-SA 4.0)<\/p>\n<\/div>\n<\/article>\n<p>The images:<\/p>\n<article class=\"align-center media media--type-image media--view-mode-default\">\n<div class=\"field field--name-field-media-image field--type-image field--label-hidden field__item\">  <img decoding=\"async\" loading=\"lazy\" src=\"https:\/\/www.cryptocabaret.com\/wp-content\/uploads\/2023\/02\/21.png\" width=\"3360\" height=\"1192\" alt=\"Images\"><\/div>\n<div class=\"field field--name-field-caption field--type-text-long field--label-hidden caption field__item\"><span class=\"caption__byline\">Image by: <\/span><\/p>\n<p>(AJ Canlas, CC BY-SA 4.0)<\/p>\n<\/div>\n<\/article>\n<p>The project:<\/p>\n<article class=\"align-center media media--type-image media--view-mode-default\">\n<div class=\"field field--name-field-media-image field--type-image field--label-hidden field__item\">  <img decoding=\"async\" loading=\"lazy\" src=\"https:\/\/www.cryptocabaret.com\/wp-content\/uploads\/2023\/02\/22.png\" width=\"3360\" height=\"1024\" alt=\"Project\"><\/div>\n<div class=\"field field--name-field-caption field--type-text-long field--label-hidden caption field__item\"><span class=\"caption__byline\">Image by: <\/span><\/p>\n<p>(AJ Canlas, CC BY-SA 4.0)<\/p>\n<\/div>\n<\/article>\n<p>The user:<\/p>\n<article class=\"align-center media media--type-image media--view-mode-default\">\n<div class=\"field field--name-field-media-image field--type-image field--label-hidden field__item\">  <img decoding=\"async\" loading=\"lazy\" src=\"https:\/\/www.cryptocabaret.com\/wp-content\/uploads\/2023\/02\/23.png\" width=\"3360\" height=\"1412\" alt=\"user\"><\/div>\n<div class=\"field field--name-field-caption field--type-text-long field--label-hidden caption field__item\"><span class=\"caption__byline\">Image by: <\/span><\/p>\n<p>(AJ Canlas, CC BY-SA 4.0)<\/p>\n<\/div>\n<\/article>\n<h2>Next steps<\/h2>\n<p>Terraform has so much potential. Terraform and Ansible are great together. In my next article, I&#8217;ll demonstrate how Ansible can work with OpenStack<\/p>\n<\/div>\n<div class=\"clearfix text-formatted field field--name-field-article-subhead field--type-text-long field--label-hidden field__item\">\n<p>Follow this tutorial to see how using GitLab can further enhance collaboration in your OpenStack cluster.<\/p>\n<\/div>\n<div class=\"field field--name-field-lead-image field--type-entity-reference field--label-hidden field__item\">\n<article class=\"media media--type-image media--view-mode-caption\">\n<div class=\"field field--name-field-media-image field--type-image field--label-hidden field__item\">  <img decoding=\"async\" loading=\"lazy\" src=\"https:\/\/www.cryptocabaret.com\/wp-content\/uploads\/2023\/02\/BIZ_darwincloud_520x292_0311LL.png\" width=\"520\" height=\"292\" alt=\"diagram of planning a cloud\" title=\"diagram of planning a cloud\"><\/div>\n<div class=\"field field--name-field-caption field--type-text-long field--label-hidden caption field__item\"><span class=\"caption__byline\">Image by: <\/span><\/p>\n<p>Opensource.com<\/p>\n<\/div>\n<\/article>\n<\/div>\n<div class=\"field field--name-field-tags field--type-entity-reference field--label-hidden field__items\">\n<div class=\"field__item\"><a href=\"https:\/\/opensource.com\/tags\/containers\" hreflang=\"en\">Containers<\/a><\/div>\n<div class=\"field__item\"><a href=\"https:\/\/opensource.com\/tags\/openstack\" hreflang=\"en\">OpenStack<\/a><\/div>\n<div class=\"field__item\"><a href=\"https:\/\/opensource.com\/tags\/git\" hreflang=\"en\">Git<\/a><\/div>\n<div class=\"field__item\"><a href=\"https:\/\/opensource.com\/tags\/gitlab\" hreflang=\"en\">GitLab<\/a><\/div>\n<div class=\"field__item\"><a href=\"https:\/\/opensource.com\/tags\/cloud\" hreflang=\"en\">Cloud<\/a><\/div>\n<div class=\"field__item\"><a href=\"https:\/\/opensource.com\/tags\/cicd\" hreflang=\"en\">CI\/CD<\/a><\/div>\n<div class=\"field__item\"><a href=\"https:\/\/opensource.com\/tags\/automation\" hreflang=\"en\">Automation<\/a><\/div>\n<\/p><\/div>\n<div class=\"hidden field field--name-field-listicle-title field--type-string field--label-hidden field__item\">What to read next<\/div>\n<div class=\"field field--name-field-default-license field--type-list-string field--label-hidden field__item\"><a rel=\"license\" href=\"http:\/\/creativecommons.org\/licenses\/by-sa\/4.0\/\"><br \/>\n        <img decoding=\"async\" alt=\"Creative Commons License\" src=\"https:\/\/www.cryptocabaret.com\/wp-content\/uploads\/2023\/02\/cc-by-sa--23.png\" title=\"This work is licensed under a Creative Commons Attribution-Share Alike 4.0 International License.\"><\/a>This work is licensed under a Creative Commons Attribution-Share Alike 4.0 International License.<\/div>\n<section class=\"field field--name-field-comments field--type-comment field--label-hidden comment-wrapper\">\n<div class=\"comments__count\">\n<div class=\"login\"><a href=\"https:\/\/opensource.com\/user\/register?absolute=1\">Register<\/a> or <a href=\"https:\/\/opensource.com\/user\/login?destination=\/feed&amp;absolute=1\">Login<\/a> to post a comment.<\/div>\n<\/p><\/div>\n<\/section>\n<p class=\"wpematico_credit\"><small>Powered by <a href=\"http:\/\/www.wpematico.com\" target=\"_blank\" rel=\"noopener\">WPeMatico<\/a><\/small><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Manage OpenStack using Terraform and GitLab ajscanlas Wed, 02\/15\/2023 &#8211; 03:00 One virtue of GitOps is Infrastructure as Code. It encourages collaboration by using a shared configuration and policy repository. Using GitLab can further enhance collaboration in your OpenStack cluster. GitLab CI can serve as your source control and orchestration hub for CI\/CD, and it [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":71236,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[307],"tags":[],"class_list":["post-71235","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-open-source"],"_links":{"self":[{"href":"https:\/\/www.cryptocabaret.com\/index.php?rest_route=\/wp\/v2\/posts\/71235","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.cryptocabaret.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.cryptocabaret.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.cryptocabaret.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.cryptocabaret.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=71235"}],"version-history":[{"count":0,"href":"https:\/\/www.cryptocabaret.com\/index.php?rest_route=\/wp\/v2\/posts\/71235\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.cryptocabaret.com\/index.php?rest_route=\/wp\/v2\/media\/71236"}],"wp:attachment":[{"href":"https:\/\/www.cryptocabaret.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=71235"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.cryptocabaret.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=71235"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.cryptocabaret.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=71235"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}