By the end of the last post we finally got the first OpenStack service up and running: Keystone, the Identity Service. Going back to the list of services we need at a minimum, this still leaves us with some more to setup:

In this post we’ll setup the Image Service (Glance) and the Placement service. The Glance service is responsible for providing virtual machine images and enables users to discover, register, and retrieve them. Glance (as well as Keystone) exposes an API to query image metadata and to retrieve images.

There are several options for storing those virtual machine images (including block storage), but to keep this as simple as possible we’ll use a directory on the controller node.

Before we start, this is how our setup looks like currently:

We’ve done almost all the work on the controller node, and Glance is no exception to that. As with Keystone, Glance needs to store some stuff in PostgreSQL (remember that you also could use MySQL or SQLite), in this case metadata about the images. Very much the same as we did it for Keystone, we’ll create a dedicated user and database for that:

[root@controller ~]$ su - postgres -c "psql -c \"create user glance with login password 'admin'\""
CREATE ROLE
[root@controller ~]$ su - postgres -c "psql -c 'create database glance with owner=glance'"
CREATE DATABASE
[root@controller ~]$ su - postgres -c "psql -l"
                                                       List of databases
   Name    |  Owner   | Encoding | Locale Provider |   Collate   |    Ctype    | ICU Locale | ICU Rules |   Access privileges   
-----------+----------+----------+-----------------+-------------+-------------+------------+-----------+-----------------------
 glance    | glance   | UTF8     | libc            | en_US.UTF-8 | en_US.UTF-8 |            |           | 
 keystone  | keystone | UTF8     | libc            | en_US.UTF-8 | en_US.UTF-8 |            |           | 
 postgres  | postgres | UTF8     | libc            | en_US.UTF-8 | en_US.UTF-8 |            |           | 
 template0 | postgres | UTF8     | libc            | en_US.UTF-8 | en_US.UTF-8 |            |           | =c/postgres          +
           |          |          |                 |             |             |            |           | postgres=CTc/postgres
 template1 | postgres | UTF8     | libc            | en_US.UTF-8 | en_US.UTF-8 |            |           | =c/postgres          +
           |          |          |                 |             |             |            |           | postgres=CTc/postgres
(5 rows)

The next step is creating the Glance service credentials:

[root@controller ~]$ . admin-openrc
[root@controller ~]$ openstack user create --domain default --password-prompt glance
User Password:
Repeat User Password:
No password was supplied, authentication will fail when a user does not have a password.
+---------------------+----------------------------------+
| Field               | Value                            |
+---------------------+----------------------------------+
| default_project_id  | None                             |
| domain_id           | default                          |
| email               | None                             |
| enabled             | True                             |
| id                  | eea5924c69c040428c5c4ef82f46c61b |
| name                | glance                           |
| description         | None                             |
| password_expires_at | None                             |
+---------------------+----------------------------------+
[root@controller ~]$  openstack service create --name glance --description "OpenStack Image" image
+-------------+----------------------------------+
| Field       | Value                            |
+-------------+----------------------------------+
| id          | db04f3f7c7014eb7883e074625d31391 |
| name        | glance                           |
| type        | image                            |
| enabled     | True                             |
| description | OpenStack Image                  |
+-------------+----------------------------------+

Create the endpoints:

[root@controller ~]$ openstack endpoint create --region RegionOne image public http://controller:9292
+--------------+----------------------------------+
| Field        | Value                            |
+--------------+----------------------------------+
| enabled      | True                             |
| id           | bde2499cd6c34c19aa221d64b9d0f2a0 |
| interface    | public                           |
| region       | RegionOne                        |
| region_id    | RegionOne                        |
| service_id   | db04f3f7c7014eb7883e074625d31391 |
| service_name | glance                           |
| service_type | image                            |
| url          | http://controller:9292           |
+--------------+----------------------------------+
[root@controller ~]$ openstack endpoint create --region RegionOne image internal http://controller:9292
+--------------+----------------------------------+
| Field        | Value                            |
+--------------+----------------------------------+
| enabled      | True                             |
| id           | 046e64889cda43c2bfc36471e40a7a2d |
| interface    | internal                         |
| region       | RegionOne                        |
| region_id    | RegionOne                        |
| service_id   | db04f3f7c7014eb7883e074625d31391 |
| service_name | glance                           |
| service_type | image                            |
| url          | http://controller:9292           |
+--------------+----------------------------------+
[root@controller ~]$ openstack endpoint create --region RegionOne image admin http://controller:9292
+--------------+----------------------------------+
| Field        | Value                            |
+--------------+----------------------------------+
| enabled      | True                             |
| id           | c8ddc792772048a18179877355fdbd3f |
| interface    | admin                            |
| region       | RegionOne                        |
| region_id    | RegionOne                        |
| service_id   | db04f3f7c7014eb7883e074625d31391 |
| service_name | glance                           |
| service_type | image                            |
| url          | http://controller:9292           |
+--------------+----------------------------------+

Install the operating system package:

[root@controller ~]$ dnf install openstack-glance -y

And finally the configuration of the Glance API service:

[root@ostack-controller ~]$ egrep -v "^#|^$" /etc/glance/glance-api.conf
[DEFAULT]
enabled_backends=fs:file
[barbican]
[barbican_service_user]
[cinder]
[cors]
[database]
connection = postgresql+psycopg2://glance:admin@localhost/glance
[file]
[glance.store.http.store]
[glance.store.rbd.store]
[glance.store.s3.store]
[glance.store.swift.store]
[glance.store.vmware_datastore.store]
[glance_store]
default_backend = fs
[healthcheck]
[image_format]
[key_manager]
[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 = admin
[os_brick]
[oslo_concurrency]
[oslo_limit]
auth_url = http://controller:5000
auth_type = password
user_domain_id = default
username = glance
system_scope = all
password = admin
endpoint_id = 1e7748b2d7d44fb6a0c17edb3c68c4de
region_name = RegionOne
[oslo_messaging_amqp]
[oslo_messaging_kafka]
[oslo_messaging_notifications]
[oslo_messaging_rabbit]
[oslo_middleware]
[oslo_policy]
[oslo_reports]
[paste_deploy]
flavor = keystone
[profiler]
[task]
[taskflow_executor]
[vault]
[wsgi]
[fs]
filesystem_store_datadir = /var/lib/glance/images/

The endpoint_id is the public image endpoint ID:

[root@controller ~]$ openstack endpoint list --service glance --region RegionOne
+----------------------------------+-----------+--------------+--------------+---------+-----------+------------------------+
| ID                               | Region    | Service Name | Service Type | Enabled | Interface | URL                    |
+----------------------------------+-----------+--------------+--------------+---------+-----------+------------------------+
| bde2499cd6c34c19aa221d64b9d0f2a0 | RegionOne | glance       | image        | True    | public    | http://controller:9292 |
| 046e64889cda43c2bfc36471e40a7a2d | RegionOne | glance       | image        | True    | internal  | http://controller:9292 |
| c8ddc792772048a18179877355fdbd3f | RegionOne | glance       | image        | True    | admin     | http://controller:9292 |
+----------------------------------+-----------+--------------+--------------+---------+-----------+------------------------+

Make sure that the glance account has reader access to system-scope resources (like limits):

[root@controller ~]$ openstack role add --user glance --user-domain Default --system all reader

Populate the Image service database:

[root@controller ~]$ /bin/sh -c "glance-manage db_sync" glance
2025-01-20 14:20:25.319 35181 INFO alembic.runtime.migration [-] Context impl PostgresqlImpl.
2025-01-20 14:20:25.319 35181 INFO alembic.runtime.migration [-] Will assume transactional DDL.
2025-01-20 14:20:25.327 35181 INFO alembic.runtime.migration [-] Context impl PostgresqlImpl.
2025-01-20 14:20:25.328 35181 INFO alembic.runtime.migration [-] Will assume transactional DDL.
2025-01-20 14:20:25.340 35181 INFO alembic.runtime.migration [-] Context impl PostgresqlImpl.
2025-01-20 14:20:25.340 35181 INFO alembic.runtime.migration [-] Will assume transactional DDL.
2025-01-20 14:20:25.350 35181 INFO alembic.runtime.migration [-] Running upgrade  -> liberty, liberty initial
2025-01-20 14:20:25.526 35181 INFO alembic.runtime.migration [-] Running upgrade liberty -> mitaka01, add index on created_at and updated_at columns of 'images' table
2025-01-20 14:20:25.534 35181 INFO alembic.runtime.migration [-] Running upgrade mitaka01 -> mitaka02, update metadef os_nova_server
2025-01-20 14:20:25.571 35181 INFO alembic.runtime.migration [-] Running upgrade mitaka02 -> ocata_expand01, add visibility to images
2025-01-20 14:20:25.575 35181 INFO alembic.runtime.migration [-] Running upgrade ocata_expand01 -> pike_expand01, empty expand for symmetry with pike_contract01
2025-01-20 14:20:25.576 35181 INFO alembic.runtime.migration [-] Running upgrade pike_expand01 -> queens_expand01
2025-01-20 14:20:25.576 35181 INFO alembic.runtime.migration [-] Running upgrade queens_expand01 -> rocky_expand01, add os_hidden column to images table
2025-01-20 14:20:25.580 35181 INFO alembic.runtime.migration [-] Running upgrade rocky_expand01 -> rocky_expand02, add os_hash_algo and os_hash_value columns to images table
2025-01-20 14:20:25.584 35181 INFO alembic.runtime.migration [-] Running upgrade rocky_expand02 -> train_expand01, empty expand for symmetry with train_contract01
2025-01-20 14:20:25.585 35181 INFO alembic.runtime.migration [-] Running upgrade train_expand01 -> ussuri_expand01, empty expand for symmetry with ussuri_expand01
2025-01-20 14:20:25.586 35181 INFO alembic.runtime.migration [-] Running upgrade ussuri_expand01 -> wallaby_expand01, add image_id, request_id, user columns to tasks table"
2025-01-20 14:20:25.598 35181 INFO alembic.runtime.migration [-] Running upgrade wallaby_expand01 -> xena_expand01, empty expand for symmetry with 2023_1_expand01
2025-01-20 14:20:25.600 35181 INFO alembic.runtime.migration [-] Running upgrade xena_expand01 -> yoga_expand01, empty expand for symmetry with 2023_1_expand01
2025-01-20 14:20:25.602 35181 INFO alembic.runtime.migration [-] Running upgrade yoga_expand01 -> zed_expand01, empty expand for symmetry with 2023_1_expand01
2025-01-20 14:20:25.603 35181 INFO alembic.runtime.migration [-] Running upgrade zed_expand01 -> 2023_1_expand01, empty expand for symmetry with 2023_1_expand01
2025-01-20 14:20:25.605 35181 INFO alembic.runtime.migration [-] Running upgrade 2023_1_expand01 -> 2024_1_expand01, adds cache_node_reference and cached_images table(s)
2025-01-20 14:20:25.677 35181 INFO alembic.runtime.migration [-] Context impl PostgresqlImpl.
2025-01-20 14:20:25.677 35181 INFO alembic.runtime.migration [-] Will assume transactional DDL.
Upgraded database to: 2024_1_expand01, current revision(s): 2024_1_expand01
2025-01-20 14:20:25.681 35181 INFO alembic.runtime.migration [-] Context impl PostgresqlImpl.
2025-01-20 14:20:25.681 35181 INFO alembic.runtime.migration [-] Will assume transactional DDL.
2025-01-20 14:20:25.684 35181 INFO alembic.runtime.migration [-] Context impl PostgresqlImpl.
2025-01-20 14:20:25.684 35181 INFO alembic.runtime.migration [-] Will assume transactional DDL.
Database migration is up to date. No migration needed.
2025-01-20 14:20:25.692 35181 INFO alembic.runtime.migration [-] Context impl PostgresqlImpl.
2025-01-20 14:20:25.692 35181 INFO alembic.runtime.migration [-] Will assume transactional DDL.
2025-01-20 14:20:25.699 35181 INFO alembic.runtime.migration [-] Context impl PostgresqlImpl.
2025-01-20 14:20:25.699 35181 INFO alembic.runtime.migration [-] Will assume transactional DDL.
2025-01-20 14:20:25.702 35181 INFO alembic.runtime.migration [-] Running upgrade mitaka02 -> ocata_contract01, remove is_public from images
2025-01-20 14:20:25.705 35181 INFO alembic.runtime.migration [-] Running upgrade ocata_contract01 -> pike_contract01, drop glare artifacts tables
2025-01-20 14:20:25.711 35181 INFO alembic.runtime.migration [-] Running upgrade pike_contract01 -> queens_contract01
2025-01-20 14:20:25.711 35181 INFO alembic.runtime.migration [-] Running upgrade queens_contract01 -> rocky_contract01
2025-01-20 14:20:25.712 35181 INFO alembic.runtime.migration [-] Running upgrade rocky_contract01 -> rocky_contract02
2025-01-20 14:20:25.712 35181 INFO alembic.runtime.migration [-] Running upgrade rocky_contract02 -> train_contract01
2025-01-20 14:20:25.712 35181 INFO alembic.runtime.migration [-] Running upgrade train_contract01 -> ussuri_contract01
2025-01-20 14:20:25.713 35181 INFO alembic.runtime.migration [-] Running upgrade ussuri_contract01 -> wallaby_contract01
2025-01-20 14:20:25.713 35181 INFO alembic.runtime.migration [-] Running upgrade wallaby_contract01 -> xena_contract01
2025-01-20 14:20:25.713 35181 INFO alembic.runtime.migration [-] Running upgrade xena_contract01 -> yoga_contract01
2025-01-20 14:20:25.714 35181 INFO alembic.runtime.migration [-] Running upgrade yoga_contract01 -> zed_contract01
2025-01-20 14:20:25.714 35181 INFO alembic.runtime.migration [-] Running upgrade zed_contract01 -> 2023_1_contract01
2025-01-20 14:20:25.715 35181 INFO alembic.runtime.migration [-] Running upgrade 2023_1_contract01 -> 2024_1_contract01
2025-01-20 14:20:25.717 35181 INFO alembic.runtime.migration [-] Context impl PostgresqlImpl.
2025-01-20 14:20:25.717 35181 INFO alembic.runtime.migration [-] Will assume transactional DDL.
Upgraded database to: 2024_1_contract01, current revision(s): 2024_1_contract01
2025-01-20 14:20:25.719 35181 INFO alembic.runtime.migration [-] Context impl PostgresqlImpl.
2025-01-20 14:20:25.719 35181 INFO alembic.runtime.migration [-] Will assume transactional DDL.
Database is synced successfully.

Create the policy definition to allow creating an image:

[root@controller ~]$ cat /etc/glance/policy.yaml 
{
    "default": "",
    "add_image": "role:admin",
    "modify_image": "role:admin",
    "delete_image": "role:admin"
}

Enable and start the service:

[root@controller ~]$ systemctl enable openstack-glance-api.service
[root@controller ~]$ systemctl start openstack-glance-api.service

Verify operation of the Image service using CirrOS, a small Linux image that helps you test your OpenStack deployment:

[root@controller ~]$ mkdir -p /var/cache/glance/api/
[root@controller ~]$ . admin-openrc
[root@controller ~]$ wget http://download.cirros-cloud.net/0.4.0/cirros-0.4.0-x86_64-disk.img
[root@controller ~]$ glance image-create --name "cirros" --file cirros-0.4.0-x86_64-disk.img   --disk-format qcow2 --container-format bare   --visibility=public
+------------------+----------------------------------------------------------------------------------+
| Property         | Value                                                                            |
+------------------+----------------------------------------------------------------------------------+
| checksum         | 443b7623e27ecf03dc9e01ee93f67afe                                                 |
| container_format | bare                                                                             |
| created_at       | 2025-01-20T14:15:21Z                                                             |
| disk_format      | qcow2                                                                            |
| id               | 150fd48b-8ed4-4170-ad98-213d9eddcba0                                             |
| min_disk         | 0                                                                                |
| min_ram          | 0                                                                                |
| name             | cirros                                                                           |
| os_hash_algo     | sha512                                                                           |
| os_hash_value    | 6513f21e44aa3da349f248188a44bc304a3653a04122d8fb4535423c8e1d14cd6a153f735bb0982e |
|                  | 2161b5b5186106570c17a9e58b64dd39390617cd5a350f78                                 |
| os_hidden        | False                                                                            |
| owner            | 920bf34a6c88454f90d405124ca1076d                                                 |
| protected        | False                                                                            |
| size             | 12716032                                                                         |
| status           | active                                                                           |
| stores           | fs                                                                               |
| tags             | []                                                                               |
| updated_at       | 2025-01-20T14:15:22Z                                                             |
| virtual_size     | 46137344                                                                         |
| visibility       | public                                                                           |
+------------------+----------------------------------------------------------------------------------+

[root@controller ~]$ glance image-list
+--------------------------------------+--------+
| ID                                   | Name   |
+--------------------------------------+--------+
| 150fd48b-8ed4-4170-ad98-213d9eddcba0 | cirros |
+--------------------------------------+--------+

Fine, the image is available and now we have this:

The next service we need to deploy is the Placement service. This service is used by other services to manage and allocate their resources. Like the Keystone and Glance service, this service needs the database backend:

[root@controller ~]$ su - postgres -c "psql -c \"create user placement with login password 'admin'\""
CREATE ROLE
[root@controller ~]$ su - postgres -c "psql -c 'create database placement with owner=placement'"
CREATE DATABASE
[root@controller ~]$ su - postgres -c "psql -l"
                                                        List of databases
   Name    |   Owner   | Encoding | Locale Provider |   Collate   |    Ctype    | ICU Locale | ICU Rules |   Access privileges   
-----------+-----------+----------+-----------------+-------------+-------------+------------+-----------+-----------------------
 glance    | glance    | UTF8     | libc            | en_US.UTF-8 | en_US.UTF-8 |            |           | 
 keystone  | keystone  | UTF8     | libc            | en_US.UTF-8 | en_US.UTF-8 |            |           | 
 placement | placement | UTF8     | libc            | en_US.UTF-8 | en_US.UTF-8 |            |           | 
 postgres  | postgres  | UTF8     | libc            | en_US.UTF-8 | en_US.UTF-8 |            |           | 
 template0 | postgres  | UTF8     | libc            | en_US.UTF-8 | en_US.UTF-8 |            |           | =c/postgres          +
           |           |          |                 |             |             |            |           | postgres=CTc/postgres
 template1 | postgres  | UTF8     | libc            | en_US.UTF-8 | en_US.UTF-8 |            |           | =c/postgres          +
           |           |          |                 |             |             |            |           | postgres=CTc/postgres
(6 rows)

In the same way as with the Glance service, the user, role, service and endpoints need to be created:

[root@controller ~]$ . admin-openrc
[root@controller ~]$ openstack user create --domain default --password-prompt placement
User Password:
Repeat User Password:
No password was supplied, authentication will fail when a user does not have a password.
+---------------------+----------------------------------+
| Field               | Value                            |
+---------------------+----------------------------------+
| default_project_id  | None                             |
| domain_id           | default                          |
| email               | None                             |
| enabled             | True                             |
| id                  | 9d1de7fda54a441b9c1289f8dc520e2b |
| name                | placement                        |
| description         | None                             |
| password_expires_at | None                             |
+---------------------+----------------------------------+
[root@controller ~]$ openstack role add --project service --user placement admin
[root@controller ~]$ openstack service create --name placement --description "Placement API" placement
+-------------+----------------------------------+
| Field       | Value                            |
+-------------+----------------------------------+
| id          | 43865e881fb84730b2bfc7c099c8038d |
| name        | placement                        |
| type        | placement                        |
| enabled     | True                             |
| description | Placement API                    |
+-------------+----------------------------------+

[root@controller ~]$ openstack endpoint create --region RegionOne placement public http://controller:8778
+--------------+----------------------------------+
| Field        | Value                            |
+--------------+----------------------------------+
| enabled      | True                             |
| id           | 5dac42d9532e4fd7b34a8e990ec5d408 |
| interface    | public                           |
| region       | RegionOne                        |
| region_id    | RegionOne                        |
| service_id   | 43865e881fb84730b2bfc7c099c8038d |
| service_name | placement                        |
| service_type | placement                        |
| url          | http://controller:8778           |
+--------------+----------------------------------+

[root@controller ~]$ openstack endpoint create --region RegionOne placement internal http://controller:8778
+--------------+----------------------------------+
| Field        | Value                            |
+--------------+----------------------------------+
| enabled      | True                             |
| id           | 535c652f6e654f56843076cf91a0fe84 |
| interface    | internal                         |
| region       | RegionOne                        |
| region_id    | RegionOne                        |
| service_id   | 43865e881fb84730b2bfc7c099c8038d |
| service_name | placement                        |
| service_type | placement                        |
| url          | http://controller:8778           |
+--------------+----------------------------------+

[root@controller ~]$ openstack endpoint create --region RegionOne placement admin http://controller:8778
+--------------+----------------------------------+
| Field        | Value                            |
+--------------+----------------------------------+
| enabled      | True                             |
| id           | 58370b01f8904e1f8aab8c718c8a4cb6 |
| interface    | admin                            |
| region       | RegionOne                        |
| region_id    | RegionOne                        |
| service_id   | 43865e881fb84730b2bfc7c099c8038d |
| service_name | placement                        |
| service_type | placement                        |
| url          | http://controller:8778           |
+--------------+----------------------------------+

Install the package which brings the Placement API and configure the service:

[root@controller ~]$ dnf install openstack-placement-api -y
[root@controller ~]$ egrep -v "^#|^$" /etc/placement/placement.conf
[DEFAULT]
[api]
auth_strategy = keystone
[cors]
[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 = admin
www_authenticate_uri  = http://controller:5000
[oslo_middleware]
[oslo_policy]
[placement]
[placement_database]
connection = postgresql+psycopg2://placement:admin@localhost/placement
[profiler]
[profiler_jaeger]
[profiler_otlp]

Populate the database (this does not produce any output if successful):

[root@controller ~]$ sh -c "placement-manage db sync" placement

… and restart the web server after adding this block:

<Directory /usr/bin>
   <IfVersion >= 2.4>
      Require all granted
   </IfVersion>
   <IfVersion < 2.4>
      Order allow,deny
      Allow from all
   </IfVersion>
</Directory>

… to the end of “/etc/httpd/conf.d/00-placement-api.conf”.

[root@controller ~]$ systemctl restart httpd

If all went fine and was configured correctly, the service can be verified to be working with:

[root@controller ~]$ placement-status upgrade check
+-------------------------------------------+
| Upgrade Check Results                     |
+-------------------------------------------+
| Check: Missing Root Provider IDs          |
| Result: Success                           |
| Details: None                             |
+-------------------------------------------+
| Check: Incomplete Consumers               |
| Result: Success                           |
| Details: None                             |
+-------------------------------------------+
| Check: Policy File JSON to YAML Migration |
| Result: Success                           |
| Details: None                             |

[root@controller ~]$ dnf install python3-osc-placement -y
[root@controller ~]$ openstack --os-placement-api-version 1.2 resource class list
+----------------------------------------+
| name                                   |
+----------------------------------------+
| VCPU                                   |
| MEMORY_MB                              |
| DISK_GB                                |
| PCI_DEVICE                             |
| SRIOV_NET_VF                           |
| NUMA_SOCKET                            |
| NUMA_CORE                              |
| NUMA_THREAD                            |
| NUMA_MEMORY_MB                         |
| IPV4_ADDRESS                           |
| VGPU                                   |
| VGPU_DISPLAY_HEAD                      |
| NET_BW_EGR_KILOBIT_PER_SEC             |
| NET_BW_IGR_KILOBIT_PER_SEC             |
| PCPU                                   |
| MEM_ENCRYPTION_CONTEXT                 |
| FPGA                                   |
| PGPU                                   |
| NET_PACKET_RATE_KILOPACKET_PER_SEC     |
| NET_PACKET_RATE_EGR_KILOPACKET_PER_SEC |
| NET_PACKET_RATE_IGR_KILOPACKET_PER_SEC |
+----------------------------------------+

Fine, now we have the Image and Placement service up and running and our setup looks like this:

In the next post we’ll continue with setting up the compute service (Nova).