Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(openstack): add OVHcloud example #27

Merged
merged 1 commit into from
Sep 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 79 additions & 0 deletions ovhcloud/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# Flatcar Provisioning Automation for OVHcloud

This repository provides tools to automate Flatcar provisioning on [OVHcloud][ovhcloud] using [Terraform][terraform].

## Features

- Minimal configuration required (demo deployment works with default settings w/o any customisation, just run `terraform apply`!).
- Deploy one or multiple servers.
- Per-server custom configuration via separate [container linux config][container-linux-config] files.

## Prerequisites

1. OVHcloud credentials: username, tenant name, password, authentication URL, region.

## Disclaimer

This Terraform code is tested against a [DevStack][devstack] instance, for actual OVHcloud deployment the "network" attribute might need to be modified.

## HowTo

This will create a server in 'RegionOne' using a medium instance size in the 'public' network with 'default' security group.
See "Customisation" below for advanced settings.

1. Clone the repo.
2. Add credentials in a `terraform.tfvars` file, expected credentials name can be found in `provider.tf`
3. Run
```shell
terraform init
```
4. Edit [`server1.yaml`][server-1] and provide your own custom provisioning configuration in [container linux config][container-linux-config] syntax.
5. Plan and apply.
This will also auto-generate an SSH key pair to log in to the server after provisioning.
The public key of that key pair will be registered with OpenStack. <br />
Invoke Terraform:
```shell
terraform plan
terraform apply
```

Terraform will print server information (name, ipv4 and v6) after deployment concluded.
The deployment will create an SSH key pair in `.ssh/`.

After provisioning concluded the private key of the key pair generated during provisioning can be used to connect to node. As mentioned in the disclaimer, the tested setup relies on DevStack so we need to proxy jump on the devstack instance before reaching the deployed instance:
```shell
ssh -J user@[DEVSTACK-IP] -i ./.ssh/provisioning_private_key.pem -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null core@[SERVER-IP]
```

_NOTE_:
* Server IP address can be found at any moment after deployment by running `terraform output`
* If you update server configuration(s) in `server-configs` and re-run `terraform apply`, the instance will be **replaced**.
Consider adding [`create_before_destroy`](https://www.terraform.io/docs/configuration/meta-arguments/lifecycle.html#syntax-and-arguments) to the `openstack_compute_instance_v2` resource in [`compute.tf`](compute.tf) to avoid services becoming unavailable during reprovisioning.
* SSH key is passed twice: in the Butane configuration and in the `key_pair` argument of the instance - while the SSH keys in the Butane configuration are added by Ignition during the first boot of the instance, `key_pair` are processed by `[email protected]` (a.k.a [`afterburn`][afterburn]) from the OpenStack [metadata service][metadata-service]. It means that you can install SSH keys without Ignition and change them as wanted.

### Customisation

The provisioning automation can be customised via settings in [`terraform.tfvars`][terraform.tfvars]:
- `cluster_name`: Descriptive name of your cluster, will be used to generate server names.
`flatcar-terraform` by default.
- `machines`: Add more machines to your deployment.
Each machine name must be unique and requires a respective `[NAME].yaml` server configuration in [`server-configs`](server-configs).
An example / default configuration for machine `server1` is provided with [`server1.yaml`](server-configs/server1.yaml).
During provisioning, server names are generated by concatenating the cluster name and the machine name - the defaults above will create a single server named `flatcar-terraform-server1`.
- `ssh_keys`: Additional SSH public keys to add to core user's `authorized_keys`.
Note that during provisioning, a provisioning RSA key pair will be generated and stored in the local directory `.ssh/provisioning_private_key.pem` and `.ssh/provisioning_key.pub`, respectively.
The private key can be used for ssh connections (`core` user) to all provisioned servers.
- `release_channel`: Select one of "lts", "stable", "beta", or "alpha".
Read more about channels [here](https://www.flatcar.org/releases).
- `flatcar_version`: Select the desired Flatcar version for the given channel (default to "current", which is the latest).
- `flavor_name`: The spec of the machine, it default to ds1G (1vCPU, 10GB of disk and 1GB of memory)
- `ssh`: A boolean to create and attach a security group to the instances to allow SSH connections. (_NOTE_: At this moment, when creating an instance with this variable enabled then turning it off does not work as the security group is not firstly detached from the instance so the security group can't be deleted)

[afterburn]: https://coreos.github.io/afterburn/
[container-linux-config]: https://www.flatcar.org/docs/latest/provisioning/config-transpiler/configuration/
[devstack]: https://opendev.org/openstack/devstack
[metadata-service]: https://docs.openstack.org/nova/zed/user/metadata.html
[ovhcloud]: https://www.ovhcloud.com/
[server-1]: server-configs/server1.yaml
[terraform]: https://www.terraform.io/
[terraform-tfvars]: terraform.tfvars
103 changes: 103 additions & 0 deletions ovhcloud/compute.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# we let 'Nova' generate a new keypair.
resource "openstack_compute_keypair_v2" "provisioning_keypair" {
name = "Provisioning key for Flatcar cluster ${var.cluster_name}"
}

# keypair is saved locally for later SSH connections.
resource "local_file" "provisioning_key" {
filename = "${path.module}/.ssh/provisioning_private_key.pem"
content = openstack_compute_keypair_v2.provisioning_keypair.private_key
directory_permission = "0700"
file_permission = "0400"
}

resource "local_file" "provisioning_key_pub" {
filename = "${path.module}/.ssh/provisioning_key.pub"
content = openstack_compute_keypair_v2.provisioning_keypair.public_key
directory_permission = "0700"
file_permission = "0440"
}

# Get the flavor ID
data "openstack_compute_flavor_v2" "flatcar" {
name = var.flavor_name
}

# We create the OVHcloud image
#
# XXX not supporting web_download + specify properties (notably
# for block-storage to avoid using virtio-blk instead of SCSI)
resource "openstack_images_image_v2" "flatcar" {
name = "${var.cluster_name}-${var.release_channel}.${var.flatcar_version}"
image_source_url = "https://${var.release_channel}.release.flatcar-linux.net/amd64-usr/${var.flatcar_version}/flatcar_production_openstack_image.img.gz"
# XXX do not use it, OVH openstack seems to not handle this well :(
# web_download = false
verify_checksum = true
decompress = true
container_format = "bare"
disk_format = "qcow2"
protected = false
hidden = false
visibility = "private"

# See: https://docs.openstack.org/glance/stein/admin/useful-image-properties.html
# See: https://wiki.openstack.org/wiki/VirtDriverImageProperties
properties = {
architecture = "x86_64"
image_original_user = "core"
distro_family = "gentoo"
os_distro = "gentoo"
os_version = var.flatcar_version
os_release_channel = var.release_channel
os_arch = "amd64"
os_type = "linux"
# XXX OVHcloud supports 256 volumes by VM, use SCSI to be able to use this
# feature. If you do not specify this it will fallback to virtio-blk and
# you'll only be able to use 26 volumes (including the root one).
hw_disk_bus = "scsi"
hw_scsi_model = "virtio-scsi"
hypervisor_type = "qemu"
hw_qemu_guest_agent = true
hw_vif_model = "virtio"
hw_vif_multiqueue_enabled = true
hw_time_hpet = true
}

timeouts {
create = "5m"
}
}

# 'instance' are the OpenStack instances created from the 'flatcar' image
# using user data.
resource "openstack_compute_instance_v2" "instance" {
for_each = toset(var.machines)
name = "${var.cluster_name}-${each.key}"
image_id = openstack_images_image_v2.flatcar.id
flavor_id = data.openstack_compute_flavor_v2.flatcar.id
key_pair = openstack_compute_keypair_v2.provisioning_keypair.name

network {
name = "public"
}

user_data = data.ct_config.machine-ignitions[each.key].rendered

security_groups = flatten([["default"], var.ssh ? [openstack_networking_secgroup_v2.ssh[0].name] : []])
}

data "ct_config" "machine-ignitions" {
for_each = toset(var.machines)
strict = true
content = file("${path.module}/server-configs/${each.key}.yaml")
snippets = [
data.template_file.core_user.rendered
]
}

data "template_file" "core_user" {
template = file("${path.module}/core-user.yaml.tmpl")
vars = {
ssh_keys = jsonencode(concat(var.ssh_keys, [openstack_compute_keypair_v2.provisioning_keypair.public_key]))
}
}
7 changes: 7 additions & 0 deletions ovhcloud/core-user.yaml.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
variant: flatcar
version: 1.0.0

passwd:
users:
- name: core
ssh_authorized_keys: ${ssh_keys}
27 changes: 27 additions & 0 deletions ovhcloud/network.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
resource "openstack_networking_secgroup_v2" "ssh" {
name = "ssh-terraform"
description = "Allow SSH from the outside - Managed by Terraform"
count = var.ssh ? 1 : 0
}

resource "openstack_networking_secgroup_rule_v2" "ssh-ipv4" {
direction = "ingress"
ethertype = "IPv4"
protocol = "tcp"
port_range_min = 22
port_range_max = 22
remote_ip_prefix = "0.0.0.0/0"
security_group_id = openstack_networking_secgroup_v2.ssh[0].id
count = var.ssh ? 1 : 0
}

resource "openstack_networking_secgroup_rule_v2" "ssh-ipv6" {
direction = "ingress"
ethertype = "IPv6"
protocol = "tcp"
port_range_min = 22
port_range_max = 22
remote_ip_prefix = "::/0"
security_group_id = openstack_networking_secgroup_v2.ssh[0].id
count = var.ssh ? 1 : 0
}
28 changes: 28 additions & 0 deletions ovhcloud/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
output "provisioning_public_key_file" {
value = local_file.provisioning_key_pub.filename
}

output "provisioning_private_key_file" {
value = local_file.provisioning_key.filename
}

output "ipv4" {
value = {
for key in var.machines :
"${var.cluster_name}-${key}" => openstack_compute_instance_v2.instance[key].access_ip_v4
}
}

output "ipv6" {
value = {
for key in var.machines :
"${var.cluster_name}-${key}" => openstack_compute_instance_v2.instance[key].access_ip_v6
}
}

output "name" {
value = {
for key in var.machines :
"${var.cluster_name}-${key}" => openstack_compute_instance_v2.instance[key].name
}
}
28 changes: 28 additions & 0 deletions ovhcloud/provider.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
terraform {
required_version = ">= 0.14.0"
required_providers {
openstack = {
source = "terraform-provider-openstack/openstack"
version = "~> 1.48.0"
}
ct = {
source = "poseidon/ct"
version = "0.11.0"
}
template = {
source = "hashicorp/template"
version = "~> 2.2.0"
}
}
}

# Configure the OpenStack Provider
# See: https://registry.terraform.io/providers/terraform-provider-openstack/openstack/latest/docs
# See: https://help.ovhcloud.com/csm/en-public-cloud-compute-terraform?id=kb_article_view&sysparm_article=KB0050797
provider "openstack" {
user_name = var.user_name
tenant_name = var.tenant_name
password = var.password
auth_url = var.auth_url
region = var.region
Comment on lines +23 to +27
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we can just add a note to the OVH documentation to find those information?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added some links to documentation but did not find a lot of useful things on OVHcloud :|

}
25 changes: 25 additions & 0 deletions ovhcloud/server-configs/server1.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
variant: flatcar
version: 1.0.0

# This is a simple NGINX example.
# Replace the below with your own config.
# Refer to https://www.flatcar.org/docs/latest/provisioning/config-transpiler/configuration/ for more information.

systemd:
units:
- name: nginx.service
enabled: true
contents: |
[Unit]
Description=NGINX example
After=docker.service
Requires=docker.service
[Service]
TimeoutStartSec=0
ExecStartPre=-/usr/bin/docker rm --force nginx1
ExecStart=/usr/bin/docker run --name nginx1 --pull always --net host docker.io/nginx:1
ExecStop=/usr/bin/docker stop nginx1
Restart=always
RestartSec=5s
[Install]
WantedBy=multi-user.target
72 changes: 72 additions & 0 deletions ovhcloud/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
variable "machines" {
type = list(string)
description = "Machine names, corresponding to machine-NAME.yaml.tmpl files"
default = []
}

variable "cluster_name" {
type = string
description = "Cluster name used as prefix for the machine names"
default = "terraform-flatcar"
}

variable "ssh_keys" {
type = list(string)
default = []
description = "Additional SSH public keys for user 'core'."
}

variable "release_channel" {
type = string
description = "Release channel"
default = "stable"

validation {
condition = contains(["lts", "stable", "beta", "alpha"], var.release_channel)
error_message = "release_channel must be lts, stable, beta, or alpha."
}
}

variable "flavor_name" {
type = string
description = "The OpenStack flavor to use (a.k.a the spec of the instance)"
default = "b2-7"
}

variable "flatcar_version" {
type = string
description = "The Flatcar version associated to the release channel"
default = "current"
}

variable "user_name" {
type = string
description = "OpenStack username"
}

variable "tenant_name" {
type = string
description = "OpenStack tenant name"
}

variable "password" {
type = string
description = "OpenStack password"
}

variable "auth_url" {
type = string
description = "OpenStack authentication URL"
}

variable "region" {
type = string
description = "OpenStack region"
default = "RegionOne"
}

variable "ssh" {
type = bool
description = "Allow SSH connection from the outside"
default = false
}