For AI agents: the complete documentation index is available at https://docs.ovhcloud.com/en/llms.txt, the full documentation bundle is available at https://docs.ovhcloud.com/en/llms-full.txt, and this page is available as Markdown at https://docs.ovhcloud.com/en/guides/hosted-private-cloud/opcp/how-to-deploy-scality-ring.md.

How to deploy Scality RING on OPCP

View as Markdown

Find out how to deploy a Scality RING storage cluster on OPCP bare metal with Terraform, from node preparation to verification

Objective

Scality RING is a software-defined storage platform that turns a set of servers into a single object (S31) and/or file (NFS/CIFS) storage system, with data protection handled either by erasure coding (ARC) or replication (COS) instead of a hardware RAID controller.

Info

Scality RING is a certified OPCP third-party solution. This deployment procedure is written and maintained by Scality, not OVHcloud. For support with RING itself, contact Scality support.

OVHcloud's On-Prem Cloud Platform (OPCP) Bare Metal Pool exposes physical servers through OpenStack: Nova hands each instance to Ironic, which PXE-boots a whole physical machine with the image you provide, rather than running a virtual machine on a hypervisor. There is no console access and, on the target production platform, no direct SSH path onto the nodes before they are deployed.

This guide walks through deploying a RING cluster on OPCP bare metal, end to end:

  1. Preparing the bare-metal nodes (network bonding and, optionally, software RAID for the OS disk).
  2. Provisioning the network ports and bare-metal instances with Terraform.
  3. Letting the RING installer run itself, driven by cloud-init, since the nodes cannot be reached over SSH before they boot.
  4. Verifying the deployment and reaching the RING supervisor UI.
Info

This guide does not cover building the Scality RING supervisor and storage disk images, or obtaining a RING licence. It assumes you already have both. See Requirements below for where to get them.

Requirements

  • An OVHcloud OPCP project with enough Bare Metal Pool nodes in the Ironic available state for your target topology. RING's minimum supported cluster on this platform is 1 supervisor node and 3 storage nodes. The 3 storage nodes must be either High Capacity Storage catalogue models (write cache not available yet) or High Performance Storage models.

  • An OpenStack application credential for that project, usable by both the OpenStack CLI and Terraform. See the Creating an Application Credential from Horizon step of OVHcloud's How to use Terraform guide for OPCP.

  • The following tools installed on the workstation you will run the deployment from:

    • the OpenStack CLI (openstack), to upload images (step 2), set up LACP/RAID (step 3) and run the verification commands (step 8);
    • Terraform (or OpenTofu) with the terraform-provider-openstack provider, to provision the network ports and bare-metal instances;
    • jq, to parse the JSON output of the port group check in step 8;
    • an SSH client, useful if you need to reach the supervisor directly rather than only through its web UI.
  • A Scality RING supervisor qcow2 image and one or more storage qcow2 images, based on Rocky Linux or RHEL — the only operating systems RING supports on OPCP — ready to upload to Glance. The supervisor image is expected to already carry the RING installer bundle, since the supervisor has no guaranteed way to fetch it after boot on this platform.

    Info

    You do not build these images yourself: request the supervisor and storage qcow2 images (and the RING installer bundle, if not already embedded in the supervisor image) from Scality, through your account team or Scality support. Building or customising RING images is not covered by this guide — see the Scality documentation portal for that procedure, or OVHcloud's guide on building a custom CentOS Stream image for background on the underlying Ironic/Nova image requirements.

  • A valid Scality RING licence. RING will install and run without one only in a limited, time-boxed evaluation state; request a licence through Scality support before going into production.

  • A RING sizing-tool export (a cluster.csv describing your node roles, disk layout and RING sizing parameters), typically provided by your Scality account team or support contact.

  • The backend private network that the Bare Metal Pool already provisions for your project (used for the RING internal plane); optionally, a second network for client-facing S3/NFS traffic, which Terraform can create for you.

Info

This guide assumes familiarity with core RING concepts (rings, connectors, COS/ARC data protection, the supervisor role). For background on any of these, see the Scality documentation portal.

Instructions

1. Authenticating to OPCP

Export your application credential so both the OpenStack CLI and Terraform can use it:

export OS_AUTH_TYPE=v3applicationcredential
export OS_AUTH_URL=<opcp-keystone-endpoint>
export OS_IDENTITY_API_VERSION=3
export OS_REGION_NAME=<opcp-region>
export OS_INTERFACE=public
export OS_APPLICATION_CREDENTIAL_ID="<application-credential-id>"
export OS_APPLICATION_CREDENTIAL_SECRET="<application-credential-secret>"

Verify it works:

openstack token issue -f value -c id
Tip

In some OPCP regions, the service catalog advertises endpoints that are not reachable from your network. If openstack commands fail to connect after a successful openstack token issue, add explicit per-service endpoint overrides (OS_COMPUTE_ENDPOINT_OVERRIDE, OS_IMAGE_ENDPOINT_OVERRIDE, OS_NETWORK_ENDPOINT_OVERRIDE, OS_BAREMETAL_ENDPOINT_OVERRIDE, OS_VOLUME_ENDPOINT_OVERRIDE) pointing at the addresses your project was given. Terraform's OpenStack provider accepts the same overrides in its provider block.

2. Uploading the RING images to Glance

Upload the supervisor and storage images provided by Scality, giving each a distinct name:

openstack image create --disk-format qcow2 --container-format bare \
  --file <path-to-supervisor-image>.qcow2 "<supervisor-image-name>"

openstack image create --disk-format qcow2 --container-format bare \
  --file <path-to-storage-image>.qcow2 "<storage-image-name>"
Warning

Image names must be unique in the project. Uploading under a name that already exists in Glance will make your Terraform plan fail rather than silently reuse the old image, so pick a new name whenever you upload a rebuilt image (e.g. append a version suffix).

Building or customising these images is outside the scope of this guide — see the note under Requirements for how to obtain them.

3. Preparing the bare-metal nodes

Two node-level settings live outside Terraform, because the standard OpenStack Ironic driver has no Terraform resource for them. Configure these settings before the nodes are deployed — they can only be changed on nodes that carry no instance, and redoing them later requires tearing the instance down first.

Network bonding (LACP). RING expects each node to present its network(s) as a bonded interface (bond0 for the backend plane and, if you use a frontend network, bond1), so that losing a single NIC or top-of-rack switch does not take the node down. Configure an 802.3ad (LACP) port group per plane directly in OpenStack Ironic, following OVHcloud's How to setup LACP on a Node guide. Use the layer3+4 transmit-hash policy so traffic spreads across both members of the bond (the kernel default, layer2, pins every flow to a single link).

Warning

LACP port groups can only be created on nodes with no active instance. If a node already has an instance, tear it down first — the OVHcloud guide does not cover reconfiguring bonding on a node in production.

Software RAID for the OS disk (optional). If you want the OS disk mirrored rather than relying on a hardware RAID controller, configure a RAID-1 target_raid_config on each node following OVHcloud's How to configure a software RAID on a node guide, before the node's next deploy.

Warning

Creating the RAID array erases the disks it is built from. Only enable it on nodes that carry no instance and no data you need, and make sure your storage/supervisor image is mdadm-capable (the root filesystem itself does not need to be pre-built on an md device, but the image must be able to assemble one at boot — mdadm installed, the mdraid module in the initramfs).

4. Defining your Terraform configuration

RING does not ship a public Terraform module for OPCP, so this configuration is built directly from the OpenStack, tls and random providers. It needs, in order: a fixed backend IP per node, your sizing-tool CSV completed with those addresses, a dedicated deployment SSH keypair, the bare-metal instances themselves, and enough cloud-init configuration for the supervisor to drive the install once every node is up.

Providers and node addresses

terraform {
  required_providers {
    openstack = {
      source  = "terraform-provider-openstack/openstack"
      version = "~> 3.0"
    }
    tls    = { source = "hashicorp/tls", version = "~> 4.0" }
    random = { source = "hashicorp/random", version = "~> 3.0" }
  }
}

provider "openstack" {
  # Reads OS_AUTH_URL, OS_APPLICATION_CREDENTIAL_ID, etc. from step 1's environment.
}

variable "backend_network_name" { type = string }
variable "backend_subnet_name"  { type = string }
variable "store_image_name"      { type = string }
variable "supervisor_image_name" { type = string }

variable "lacp_enabled" {
  type    = bool
  default = true
}

# The supervisor's private backend IP address (see step 9).
variable "supervisor_published_address" { type = string }

# One fixed backend IP per node, keyed by minion_id — must match cluster.csv exactly.
# RING's minimum topology on OPCP is one supervisor and three storage nodes.
variable "node_ips" {
  type = map(string)
  default = {
    "<supervisor-minion-id>" = "<supervisor-backend-ip>"
    "<storage-minion-id-1>"  = "<storage-1-backend-ip>"
    "<storage-minion-id-2>"  = "<storage-2-backend-ip>"
    "<storage-minion-id-3>"  = "<storage-3-backend-ip>"
  }
}

Complete the sizing-tool CSV

Your sizing-tool export may bundle more than one section (for example, ring-level parameters followed by a servers table). Extract only the servers table — one row per node, with its header — into its own file (for example, servers.csv), keeping minion_id, role and enclosure as provided and the address columns (data_ip, data_iface, s3_ip, s3_iface) blank. Terraform then fills in exactly those 4 columns:

locals {
  csv_rows = csvdecode(file("${path.module}/servers.csv"))
  iface    = var.lacp_enabled ? "bond0" : "eth0"

  completed_rows = [
    for row in local.csv_rows : merge(row, {
      data_ip    = var.node_ips[row.minion_id]
      data_iface = local.iface
      s3_ip      = row.role == "supervisor" ? "" : var.node_ips[row.minion_id]
      s3_iface   = local.iface
    })
  ]

  completed_csv = csvencode(local.completed_rows)
  store_ips     = [for r in local.completed_rows : r.data_ip if r.role != "supervisor"]
  nodes         = { for r in local.completed_rows : r.minion_id => r }
}

Network ports

data "openstack_networking_network_v2" "backend" {
  name = var.backend_network_name
}

data "openstack_networking_subnet_v2" "backend" {
  name = var.backend_subnet_name
}

resource "openstack_networking_port_v2" "backend" {
  for_each   = var.node_ips
  name       = "${each.key}-backend"
  network_id = data.openstack_networking_network_v2.backend.id

  fixed_ip {
    subnet_id  = data.openstack_networking_subnet_v2.backend.id
    ip_address = each.value
  }
}

Deployment keypair and GUI password

resource "tls_private_key" "deploy" {
  algorithm = "RSA"
  rsa_bits  = 4096
}

resource "random_password" "gui" {
  length  = 20
  special = false
}

Bare-metal instances and cloud-init

resource "openstack_compute_instance_v2" "node" {
  for_each = local.nodes

  name         = each.key
  image_name   = each.value.role == "supervisor" ? var.supervisor_image_name : var.store_image_name
  flavor_name  = each.value.enclosure
  config_drive = true

  network {
    port = openstack_networking_port_v2.backend[each.key].id
  }

  user_data = each.value.role == "supervisor" ? templatefile("${path.module}/cloudinit-supervisor.yaml.tftpl", {
    deploy_private_key = tls_private_key.deploy.private_key_openssh
    deploy_public_key  = tls_private_key.deploy.public_key_openssh
    store_ips          = local.store_ips
    cluster_csv        = local.completed_csv
    gui_password       = random_password.gui.result
    published_address  = var.supervisor_published_address
  }) : templatefile("${path.module}/cloudinit-store.yaml.tftpl", {
    deploy_public_key = tls_private_key.deploy.public_key_openssh
  })
}

output "port_ids" {
  value = { for k, p in openstack_networking_port_v2.backend : k => p.id }
}

output "gui_password" {
  value     = random_password.gui.result
  sensitive = true
}

cloudinit-store.yaml.tftpl only needs to authorise the deployment key as root, so the supervisor can reach the node:

#cloud-config
users:
  - name: root
    ssh_authorized_keys:
      - ${deploy_public_key}

cloudinit-supervisor.yaml.tftpl authorises the same key, stages the completed CSV and the private half of the deployment key, then waits for every storage node to answer over SSH before launching the RING installer non-interactively and detached (so it survives the end of cloud-init):

#cloud-config
users:
  - name: root
    ssh_authorized_keys:
      - ${deploy_public_key}

write_files:
  - path: /root/.ssh/id_rsa
    permissions: '0600'
    content: |
      ${indent(6, deploy_private_key)}
  - path: /var/tmp/scality/cluster.csv
    content: |
      ${indent(6, cluster_csv)}

runcmd:
  - |
    for ip in ${join(" ", store_ips)}; do
      timeout 3600 bash -c "until ssh -o StrictHostKeyChecking=no -i /root/.ssh/id_rsa root@$ip true; do sleep 10; done"
    done
    RING_GUI_PASSWORD=${gui_password} RING_SUPERVISOR_FQDN=${published_address} \
      setsid /root/<ring-installer-bundle>.run --yes --conf /var/tmp/scality/conf \
      > /var/tmp/scality/install.log 2>&1 &
Tip

If your deployment also serves S3/NFS clients on a separate network from the RING backend plane, repeat the network-ports block for a second, frontend network (with no gateway and no DNS, so nodes keep their default route on the backend plane), attach a second network { port = ... } block to each openstack_compute_instance_v2.node, and point the CSV's s3_ip / s3_iface at that plane's address and bond (bond1) for the storage nodes instead of the backend one.

5. Running a preflight check

Before running terraform plan, run the following read-only checks:

  • The application credential and Terraform provider can both reach the project.
  • Both the supervisor and storage images exist in Glance under the names your configuration references.
  • At least as many nodes are in the available Ironic state as your cluster.csv requires.
  • If lacp_enabled is set, every node you expect Terraform to pick already carries its LACP port groups (step 3) — Nova can select any available node, and the installer CSV bakes in bond names at plan time, so a node without bonding will boot with a mismatched network configuration.
  • If you configured software RAID, its state matches what you intend for this deploy.

Fixing a gap now is far cheaper than fixing it after a bare-metal instance is provisioned.

6. Reviewing and applying the Terraform plan

terraform init
terraform plan -out=tfplan
terraform show tfplan > tfplan.txt

Read tfplan.txt carefully before applying:

Warning

On bare metal, a Terraform replace of an instance is a full Ironic redeploy of the physical machine — the node is wiped and reprovisioned from scratch. Look specifically for must be replaced or will be destroyed on any bare-metal instance, network port, or the deployment keypair, and, if you see one you did not expect, find out why before applying.

terraform apply tfplan

7. Letting the automated install run

Because the nodes cannot be reached over SSH before they boot, the RING installer is launched by cloud-init itself rather than by an operator running a script:

  • Terraform generates a dedicated deployment keypair. The public half is added to every node's root authorized_keys; the private half rides only the supervisor's cloud-init, so the supervisor — and only the supervisor — can reach every storage node as root.
  • At first boot, the supervisor writes the completed install CSV and installer configuration to local disk, then polls root SSH to every storage node (boot order across bare-metal nodes is not guaranteed, so this typically runs with a generous timeout, of the order of an hour).
  • Once every node answers, the supervisor launches the RING installer non-interactively and detached, so it survives the end of cloud-init and is not affected by a dropped connection to the supervisor.

There is no operator action in this step — it is the point of the cloud-init-driven design.

8. Verifying the deployment

Before assuming the install is progressing normally, confirm the network layer matches what the CSV assumed at plan time:

openstack baremetal port group list --node <node> --long -f json | jq '.[] | {name, internal_info}'

Each backend port group should carry the node's port ID from your Terraform port_ids output as internal_info.tenant_vif_port_id (and, if you configured a frontend network, each frontend port group should carry the matching frontend_port_ids entry). This mapping is not contractually guaranteed by Ironic, so it is worth checking after every deploy, not only the first.

From the supervisor, confirm in the guest OS that each bond came up with the expected address and that the default route points at the backend gateway, then follow the install log:

tail -f /var/tmp/scality/install.log
Info

Each time it is launched, the installer exits immediately if it finds a done marker or a still-running process, so rebooting a node mid-install will not restart a finished or in-progress install.

9. Accessing the RING supervisor UI

Retrieve the GUI/admin password generated by Terraform:

terraform output -raw gui_password

Then, from a host with network access to the backend plane, open https://<supervisor_published_address>/gui/ in a browser, where <supervisor_published_address> is the supervisor's private backend IP address. Accept the self-signed certificate on first access and log in as an administrator.

Info

If you have not yet activated a RING licence, do so now through the supervisor UI or as guided by Scality support. RING enforces licence limits once the evaluation period ends.

Conclusion

At this point, Terraform owns the network ports, the bare-metal instances and the generated credentials, and cloud-init has driven an unattended RING install across every node. You have a running Scality RING cluster on OPCP, ready for licence activation (if not already done) and for configuring S3/IAM and, if applicable, file connectors. For everything past this point — sizing, ring topology changes, connector configuration, upgrades — consult the Scality documentation portal, and reach out to Scality support for anything licence- or issue-related.

Go further

For training or technical assistance implementing our solutions, contact your sales representative or visit our Professional Services page to request a quote and have your project analysed by our experts.

Join our community of users.

1: S3 is a trademark of Amazon Technologies, Inc. OVHcloud's service is not sponsored by, endorsed by, or otherwise affiliated with Amazon Technologies, Inc.

Was this page helpful?