OpenStack All‑in‑One on NVMe + RAID5 SSDs (From Scratch)
This guide is a from-scratch, manual installation of an all‑in‑one OpenStack node on Ubuntu, using:
- NVMe for the host OS and control plane services.
- Three SSDs in RAID 5 (via
mdadm) for VM ephemeral storage (Nova), block volumes (Cinder via LVM), and optionally images (Glance).
Target audience: sysadmins and homelab builders who want a reproducible, transparent setup without DevStack/Packstack.
Scope: single host acting as controller + compute + (optional) cinder-volume. Production hardening topics are listed at the end.
✅ Summary (What you’ll get)
- OS: Ubuntu Server 22.04 LTS
- OpenStack Release: Caracal (2024.1) via Ubuntu Cloud Archive
- Services: Keystone (Id), Glance (Images), Nova (API/Conductor/Scheduler/NoVNC/Compute), Neutron (ML2 + Linuxbridge), Cinder (LVM backend), Horizon (Dashboard)
- Storage layout:
/on NVMe (host OS)/dev/md0= RAID 5 over 3x SSDs ->cinder-volumes(LVM VG) for Cinder block storage/openstack_storage/nova_instancesfor Nova ephemeral disks (symlink from/var/lib/nova/instances)/openstack_storage/glance_images(optional) for Glance filesystem backend
🧠 Assumptions & IP Plan
- Hostname:
controller - Management IP:
192.168.0.10/24(adjust to your LAN) - DNS/Hosts: you can resolve
controllerto192.168.0.10 - Disks:
- NVMe: OS already installed
- SSDs:
/dev/sda,/dev/sdb,/dev/sdc(adjust device names as needed)
- You have sudo access and outbound internet
Note: If your disk names differ (e.g.
nvme1n1,sdb,sdc), adapt commands accordingly.
0) Base System Prep
sudo apt update && sudo apt -y upgrade
sudo hostnamectl set-hostname controller
# Map controller to IP (edit your values)
echo "192.168.0.10 controller" | sudo tee -a /etc/hosts
Install the Cloud Archive for Caracal (2024.1) and the OpenStack client:
sudo apt install -y software-properties-common
sudo add-apt-repository -y cloud-archive:caracal
sudo apt update && sudo apt -y dist-upgrade
sudo apt install -y python3-openstackclient
Reboot if a kernel was upgraded:
sudo reboot
1) Build RAID 5 over the 3 SSDs
Install mdadm and create the array:
sudo apt install -y mdadm
# Adjust device names to your SSDs
sudo mdadm --create --verbose /dev/md0 --level=5 --raid-devices=3 /dev/sda /dev/sdb /dev/sdc
Confirm and monitor sync:
cat /proc/mdstat
sudo mdadm --detail /dev/md0
Persist the RAID configuration and initramfs:
sudo mdadm --detail --scan | sudo tee -a /etc/mdadm/mdadm.conf
sudo update-initramfs -u
Create a filesystem and mountpoint (XFS recommended for VM storage):
sudo mkfs.xfs /dev/md0
sudo mkdir -p /openstack_storage
sudo blkid /dev/md0
# Copy the UUID and add to /etc/fstab:
# UUID=<your-uuid> /openstack_storage xfs defaults 0 0
sudoedit /etc/fstab
sudo mount -a
df -h | grep openstack_storage
2) Core Infra: SQL, RabbitMQ, Memcached, NTP
# MariaDB (SQL)
sudo apt install -y mariadb-server python3-pymysql
# Harden and bind
sudo mysql_secure_installation
sudo tee /etc/mysql/mariadb.conf.d/99-openstack.cnf >/dev/null <<'EOF'
[mysqld]
bind-address = 0.0.0.0
default-storage-engine = innodb
innodb_file_per_table = on
max_connections = 4096
collation-server = utf8_general_ci
character-set-server = utf8
EOF
sudo systemctl restart mariadb && sudo systemctl enable mariadb
# RabbitMQ (Messaging)
sudo apt install -y rabbitmq-server
sudo rabbitmqctl add_user openstack StrongRabbitPass!
sudo rabbitmqctl set_permissions openstack ".*" ".*" ".*"
# Memcached (Tokens/Keystone)
sudo apt install -y memcached python3-memcache
sudo sed -i 's/^-l .*/-l 0.0.0.0/' /etc/memcached.conf
sudo systemctl restart memcached && sudo systemctl enable memcached
# NTP (time sync)
sudo apt install -y chrony
sudo systemctl enable --now chrony
3) Keystone (Identity)
Create DB and user:
sudo mysql -u root -p <<'SQL'
CREATE DATABASE keystone;
GRANT ALL PRIVILEGES ON keystone.* TO 'keystone'@'localhost' IDENTIFIED BY 'KEYSTONE_DB_PASS!';
GRANT ALL PRIVILEGES ON keystone.* TO 'keystone'@'%' IDENTIFIED BY 'KEYSTONE_DB_PASS!';
FLUSH PRIVILEGES;
SQL
Install & configure Keystone:
sudo apt install -y keystone apache2 libapache2-mod-wsgi-py3
sudo sed -i 's|^#* *connection *=.*|connection = mysql+pymysql://keystone:KEYSTONE_DB_PASS!@controller/keystone|' /etc/keystone/keystone.conf
sudo awk '/^\[token\]/{print;print "provider = fernet";next}1' /etc/keystone/keystone.conf | sudo tee /etc/keystone/keystone.conf.tmp >/dev/null && sudo mv /etc/keystone/keystone.conf.tmp /etc/keystone/keystone.conf
sudo keystone-manage db_sync
sudo keystone-manage fernet_setup --keystone-user keystone --keystone-group keystone
sudo keystone-manage credential_setup --keystone-user keystone --keystone-group keystone
sudo keystone-manage bootstrap \
--bootstrap-password ADMIN_PASS! \
--bootstrap-admin-url http://controller:5000/v3/ \
--bootstrap-internal-url http://controller:5000/v3/ \
--bootstrap-public-url http://controller:5000/v3/ \
--bootstrap-region-id RegionOne
sudo systemctl restart apache2 && sudo systemctl enable apache2
Admin environment file ~/admin-openrc:
cat > ~/admin-openrc <<'EOF'
export OS_USERNAME=admin
export OS_PASSWORD=ADMIN_PASS!
export OS_PROJECT_NAME=admin
export OS_USER_DOMAIN_NAME=Default
export OS_PROJECT_DOMAIN_NAME=Default
export OS_AUTH_URL=http://controller:5000/v3
export OS_IDENTITY_API_VERSION=3
EOF
source ~/admin-openrc
openstack token issue
Create service project and basic users (glance, nova, placement, neutron, cinder):
# Service project
openstack project create --domain Default --description "Service Project" service
(Drivers will add their own users in their sections.)
4) Glance (Image Service)
DB:
sudo mysql -u root -p <<'SQL'
CREATE DATABASE glance;
GRANT ALL PRIVILEGES ON glance.* TO 'glance'@'localhost' IDENTIFIED BY 'GLANCE_DB_PASS!';
GRANT ALL PRIVILEGES ON glance.* TO 'glance'@'%' IDENTIFIED BY 'GLANCE_DB_PASS!';
FLUSH PRIVILEGES;
SQL
Identity + install:
# Keystone user/role/endpoints
openstack user create --domain Default --password GLANCE_USER_PASS! glance
openstack role add --project service --user glance admin
openstack service create --name glance --description "OpenStack Image" image
openstack endpoint create --region RegionOne image public http://controller:9292
openstack endpoint create --region RegionOne image internal http://controller:9292
openstack endpoint create --region RegionOne image admin http://controller:9292
# Install
sudo apt install -y glance
Config /etc/glance/glance-api.conf (key stanzas):
[database]
connection = mysql+pymysql://glance:GLANCE_DB_PASS!@controller/glance
[keystone_authtoken]
www_authenticate_uri = http://controller:5000
auth_url = http://controller:5000
memcached_servers = controller:11211
auth_type = password
project_domain_name = Default
user_domain_name = Default
project_name = service
username = glance
password = GLANCE_USER_PASS!
[glance_store]
stores = file,http
default_store = file
filesystem_store_datadir = /openstack_storage/glance_images/
Prepare storage and sync DB:
sudo mkdir -p /openstack_storage/glance_images
sudo chown -R glance:glance /openstack_storage/glance_images
sudo glance-manage db_sync
sudo systemctl enable --now glance-api
Test image upload (Cirros example):
source ~/admin-openrc
wget -O /tmp/cirros.img https://download.cirros-cloud.net/0.6.2/cirros-0.6.2-x86_64-disk.img
openstack image create "cirros" --file /tmp/cirros.img --disk-format qcow2 --container-format bare --public
openstack image list
5) Nova (Compute)
DBs + Placement:
sudo mysql -u root -p <<'SQL'
CREATE DATABASE nova_api;
CREATE DATABASE nova;
CREATE DATABASE nova_cell0;
CREATE DATABASE placement;
GRANT ALL PRIVILEGES ON nova_api.* TO 'nova'@'localhost' IDENTIFIED BY 'NOVA_DB_PASS!';
GRANT ALL PRIVILEGES ON nova_api.* TO 'nova'@'%' IDENTIFIED BY 'NOVA_DB_PASS!';
GRANT ALL PRIVILEGES ON nova.* TO 'nova'@'localhost' IDENTIFIED BY 'NOVA_DB_PASS!';
GRANT ALL PRIVILEGES ON nova.* TO 'nova'@'%' IDENTIFIED BY 'NOVA_DB_PASS!';
GRANT ALL PRIVILEGES ON nova_cell0.* TO 'nova'@'localhost' IDENTIFIED BY 'NOVA_DB_PASS!';
GRANT ALL PRIVILEGES ON nova_cell0.* TO 'nova'@'%' IDENTIFIED BY 'NOVA_DB_PASS!';
GRANT ALL PRIVILEGES ON placement.* TO 'placement'@'localhost' IDENTIFIED BY 'PLACEMENT_DB_PASS!';
GRANT ALL PRIVILEGES ON placement.* TO 'placement'@'%' IDENTIFIED BY 'PLACEMENT_DB_PASS!';
FLUSH PRIVILEGES;
SQL
Keystone users and endpoints:
# Nova
openstack user create --domain Default --password NOVA_USER_PASS! nova
openstack role add --project service --user nova admin
openstack service create --name nova --description "OpenStack Compute" compute
openstack endpoint create --region RegionOne compute public http://controller:8774/v2.1
openstack endpoint create --region RegionOne compute internal http://controller:8774/v2.1
openstack endpoint create --region RegionOne compute admin http://controller:8774/v2.1
# Placement
openstack user create --domain Default --password PLACEMENT_USER_PASS! placement
openstack role add --project service --user placement admin
openstack service create --name placement --description "Placement API" placement
openstack endpoint create --region RegionOne placement public http://controller:8778
openstack endpoint create --region RegionOne placement internal http://controller:8778
openstack endpoint create --region RegionOne placement admin http://controller:8778
Install Nova services (API, Conductor, Scheduler, NoVNC) + Placement API:
sudo apt install -y nova-api nova-conductor nova-scheduler nova-novncproxy nova-compute \
placement-api
Config highlights:
/etc/nova/nova.conf(minimum):
[api_database]
connection = mysql+pymysql://nova:NOVA_DB_PASS!@controller/nova_api
[database]
connection = mysql+pymysql://nova:NOVA_DB_PASS!@controller/nova
[DEFAULT]
transport_url = rabbit://openstack:StrongRabbitPass!@controller
my_ip = 192.168.0.10
use_neutron = True
firewall_driver = nova.virt.firewall.NoopFirewallDriver
enabled_apis = osapi_compute,metadata
[keystone_authtoken]
www_authenticate_uri = http://controller:5000
auth_url = http://controller:5000
memcached_servers = controller:11211
auth_type = password
project_domain_name = Default
user_domain_name = Default
project_name = service
username = nova
password = NOVA_USER_PASS!
[glance]
api_servers = http://controller:9292
[oslo_concurrency]
lock_path = /var/lib/nova/tmp
[placement]
region_name = RegionOne
project_domain_name = Default
project_name = service
auth_type = password
user_domain_name = Default
auth_url = http://controller:5000/v3
username = placement
password = PLACEMENT_USER_PASS!
/etc/placement/placement.conf:
[placement_database]
connection = mysql+pymysql://placement:PLACEMENT_DB_PASS!@controller/placement
[api]
auth_strategy = keystone
[keystone_authtoken]
auth_url = http://controller:5000/v3
memcached_servers = controller:11211
auth_type = password
project_domain_name = Default
user_domain_name = Default
project_name = service
username = placement
password = PLACEMENT_USER_PASS!
DB sync + cell mapping:
sudo su -s /bin/bash nova -c "nova-manage api_db sync"
sudo su -s /bin/bash nova -c "nova-manage cell_v2 map_cell0"
sudo su -s /bin/bash nova -c "nova-manage cell_v2 create_cell --name=cell1"
sudo su -s /bin/bash nova -c "nova-manage db sync"
Restart services:
sudo systemctl restart placement-api
sudo systemctl restart nova-api nova-scheduler nova-conductor nova-novncproxy
sudo systemctl restart nova-compute
sudo systemctl enable placement-api nova-api nova-scheduler nova-conductor nova-novncproxy nova-compute
Move Nova instance storage to RAID
sudo systemctl stop nova-compute
sudo mkdir -p /openstack_storage/nova_instances
sudo rsync -aHAX /var/lib/nova/instances/ /openstack_storage/nova_instances/
sudo mv /var/lib/nova/instances /var/lib/nova/instances.bak
sudo ln -s /openstack_storage/nova_instances /var/lib/nova/instances
sudo systemctl start nova-compute
6) Neutron (Networking) — ML2 + Linuxbridge (simple flat/VLAN)
Install:
sudo apt install -y neutron-server neutron-plugin-ml2 neutron-linuxbridge-agent neutron-dhcp-agent neutron-metadata-agent neutron-l3-agent
Config /etc/neutron/neutron.conf (highlights):
[DEFAULT]
core_plugin = ml2
service_plugins = router
transport_url = rabbit://openstack:StrongRabbitPass!@controller
auth_strategy = keystone
notify_nova_on_port_status_changes = true
notify_nova_on_port_data_changes = true
[keystone_authtoken]
www_authenticate_uri = http://controller:5000
auth_url = http://controller:5000
memcached_servers = controller:11211
auth_type = password
project_domain_name = Default
user_domain_name = Default
project_name = service
username = neutron
password = NEUTRON_USER_PASS!
[nova]
auth_url = http://controller:5000
auth_type = password
project_domain_name = Default
user_domain_name = Default
region_name = RegionOne
project_name = service
username = nova
password = NOVA_USER_PASS!
Create Neutron user/endpoints:
openstack user create --domain Default --password NEUTRON_USER_PASS! neutron
openstack role add --project service --user neutron admin
openstack service create --name neutron --description "OpenStack Networking" network
openstack endpoint create --region RegionOne network public http://controller:9696
openstack endpoint create --region RegionOne network internal http://controller:9696
openstack endpoint create --region RegionOne network admin http://controller:9696
ML2 and Linuxbridge basics:
/etc/neutron/plugins/ml2/ml2_conf.ini(flat or VLAN example):
[ml2]
type_drivers = flat,vlan
tenant_network_types =
mechanism_drivers = linuxbridge
extension_drivers = port_security
[ml2_type_flat]
flat_networks = physnet1
[securitygroup]
enable_ipset = true
/etc/neutron/plugins/ml2/linuxbridge_agent.ini:
[linux_bridge]
physical_interface_mappings = physnet1:enp3s0
[vxlan]
enable_vxlan = false
[securitygroup]
enable_security_group = true
firewall_driver = neutron.agent.linux.iptables_firewall.IptablesFirewallDriver
- Metadata agent
/etc/neutron/metadata_agent.ini:
[DEFAULT]
nova_metadata_host = controller
metadata_proxy_shared_secret = METADATA_SECRET!
- In Nova
/etc/nova/nova.conf, add metadata secret:
[neutron]
auth_url = http://controller:5000
auth_type = password
project_domain_name = Default
user_domain_name = Default
region_name = RegionOne
project_name = service
username = neutron
password = NEUTRON_USER_PASS!
metadata_proxy_shared_secret = METADATA_SECRET!
service_metadata_proxy = True
Restart:
sudo ln -sf /etc/neutron/plugins/ml2/ml2_conf.ini /etc/neutron/plugin.ini
sudo systemctl restart nova-api
sudo systemctl restart neutron-server neutron-linuxbridge-agent neutron-dhcp-agent neutron-metadata-agent neutron-l3-agent
sudo systemctl enable neutron-server neutron-linuxbridge-agent neutron-dhcp-agent neutron-metadata-agent neutron-l3-agent
Create a provider network (flat) and subnet (example):
source ~/admin-openrc
openstack network create --share --external --provider-physical-network physnet1 --provider-network-type flat public
openstack subnet create --network public --allocation-pool start=192.168.0.200,end=192.168.0.250 \
--dns-nameserver 1.1.1.1 --gateway 192.168.0.1 --subnet-range 192.168.0.0/24 public-subnet
7) Cinder (Block Storage) — LVM on RAID5
Create LVM PV + VG on /dev/md0:
sudo apt install -y lvm2
sudo pvcreate /dev/md0
sudo vgcreate cinder-volumes /dev/md0
Install Cinder services:
sudo apt install -y cinder-api cinder-scheduler cinder-volume tgt
DB + Keystone:
sudo mysql -u root -p <<'SQL'
CREATE DATABASE cinder;
GRANT ALL PRIVILEGES ON cinder.* TO 'cinder'@'localhost' IDENTIFIED BY 'CINDER_DB_PASS!';
GRANT ALL PRIVILEGES ON cinder.* TO 'cinder'@'%' IDENTIFIED BY 'CINDER_DB_PASS!';
FLUSH PRIVILEGES;
SQL
openstack user create --domain Default --password CINDER_USER_PASS! cinder
openstack role add --project service --user cinder admin
openstack service create --name cinder --description "OpenStack Block Storage" volumev3
openstack endpoint create --region RegionOne volumev3 public http://controller:8776/v3/%\(project_id\)s
openstack endpoint create --region RegionOne volumev3 internal http://controller:8776/v3/%\(project_id\)s
openstack endpoint create --region RegionOne volumev3 admin http://controller:8776/v3/%\(project_id\)s
Key config /etc/cinder/cinder.conf:
[database]
connection = mysql+pymysql://cinder:CINDER_DB_PASS!@controller/cinder
[DEFAULT]
transport_url = rabbit://openstack:StrongRabbitPass!@controller
auth_strategy = keystone
my_ip = 192.168.0.10
enabled_backends = lvm
glance_api_servers = http://controller:9292
[keystone_authtoken]
www_authenticate_uri = http://controller:5000
auth_url = http://controller:5000
memcached_servers = controller:11211
auth_type = password
project_domain_name = Default
user_domain_name = Default
project_name = service
username = cinder
password = CINDER_USER_PASS!
[lvm]
volume_driver = cinder.volume.drivers.lvm.LVMVolumeDriver
volume_group = cinder-volumes
target_protocol = iscsi
target_helper = tgtadm
volume_backend_name = lvm
DB sync + restart:
sudo su -s /bin/bash cinder -c "cinder-manage db sync"
sudo systemctl restart cinder-scheduler cinder-volume cinder-api tgt
sudo systemctl enable cinder-scheduler cinder-volume cinder-api tgt
Verify:
openstack volume service list
8) Horizon (Dashboard)
sudo apt install -y openstack-dashboard
sudo sed -i 's/^OPENSTACK_HOST.*/OPENSTACK_HOST = "controller"/' /etc/openstack-dashboard/local_settings.py
# (Optional) allow all hosts in dev/lab:
sudo sed -i 's/^ALLOWED_HOSTS.*/ALLOWED_HOSTS = ["*"]/' /etc/openstack-dashboard/local_settings.py
sudo systemctl restart apache2
Browse to: http://controller/horizon and log in as admin with ADMIN_PASS!
9) Quick Functional Test
source ~/admin-openrc
openstack image list
openstack network list
# Create minimal flavor & keypair
openstack flavor create --ram 512 --disk 1 --vcpus 1 m1.tiny
openstack keypair create --public-key ~/.ssh/id_rsa.pub mykey
# Boot cirros on provider network (adjust net ID/name)
NET_ID=$(openstack network show public -f value -c id)
openstack server create --flavor m1.tiny --image cirros --nic net-id=$NET_ID --key-name mykey testvm1
openstack server list
Use the dashboard or CLI to access the console (NoVNC) and verify boot.
10) RAID Health, Monitoring & Benchmark
Health:
cat /proc/mdstat
sudo mdadm --detail /dev/md0
Email alerts (optional):
sudo apt install -y postfix mailutils
sudo mdadm --monitor --scan --mail=root@localhost --delay=300
Benchmark (optional, fio):
sudo apt install -y fio
fio --name=raid5test --filename=/openstack_storage/testfile --size=2G --rw=readwrite --bs=1M --numjobs=4 --direct=1 --group_reporting
Hardening & Production Notes (Read Me)
- Replace all
*_PASS!with strong secrets; store in a password manager. - Use a separate data NIC and bridges for provider/tenant traffic.
- Consider BTRFS/ZFS if you want snapshots/CRC with different trade-offs (not covered here).
- For Cinder at scale, prefer iSCSI/NFS backends (e.g., LVM over dedicated disks, Ceph for HA).
- Enable TLS for Keystone/Glance/Nova/Neutron/Cinder endpoints.
- Configure backups for MariaDB and
/etc/configs, and take regular etcd/RabbitMQ backups if applicable. - Put OpenStack services under systemd overrides and logrotate tuning.
- Use Kolla-Ansible or OpenStack-Ansible for multi-node, HA, and day‑2 ops once you outgrow single-node.
Troubleshooting Quickies
openstack service list/openstack endpoint listto confirm identity layer.journalctl -u <service> -efor any failing service.- Verify DB connectivity with
mysql -h controller -u <user> -p <db>. - Placement errors? Check
/etc/nova/nova.conf[placement]and restartplacement-apiandnova-*. - Metadata 404? Confirm
metadata_proxy_shared_secretmatches in Neutron metadata agent and Nova[neutron]section. - No external connectivity for VMs? Revisit provider network mapping and Linuxbridge
physical_interface_mappings.
Appendix: Variable Cheat Sheet
Replace these with your own strong values:
ADMIN_PASS!
KEYSTONE_DB_PASS!
GLANCE_DB_PASS!
GLANCE_USER_PASS!
NOVA_DB_PASS!
PLACEMENT_DB_PASS!
PLACEMENT_USER_PASS!
NEUTRON_USER_PASS!
CINDER_DB_PASS!
CINDER_USER_PASS!
METADATA_SECRET!
StrongRabbitPass!
You’re set. You now have an all‑in‑one OpenStack host with NVMe OS and RAID5 SSD-backed storage for instances, volumes, and images. Happy hacking!