Skip to content

Commit 94ac889

Browse files
feat: Adds post-deploy OpenStack Octavia playbook
1 parent df6d7fd commit 94ac889

File tree

10 files changed

+318
-1
lines changed

10 files changed

+318
-1
lines changed

ansible/playbooks/openstack_network.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
# License for the specific language governing permissions and limitations
1414
# under the License.
1515

16-
- name: Openstack Network
16+
- name: OpenStack Network
1717
hosts: neutron
1818
connection: local
1919

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
---
2+
# Copyright (c) 2025 Rackspace Technology, Inc.
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
5+
# not use this file except in compliance with the License. You may obtain
6+
# a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
# License for the specific language governing permissions and limitations
14+
# under the License.
15+
16+
- name: OpenStack Octavia Post Deployment
17+
hosts: neutron
18+
connection: local
19+
20+
pre_tasks:
21+
- name: Fail if ENV variables are not set
22+
ansible.builtin.fail:
23+
msg: "Environment variable {{ item }} is not set. Exiting playbook."
24+
when: lookup('env', item) == ''
25+
loop:
26+
- OS_CLOUD
27+
28+
roles:
29+
- role: openstack_octavia
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
openstack_octavia_load_balancer_network_name: "lb-mgmt-net"
3+
openstack_octavia_load_balancer_subnet_name: "lb-mgmt-subnet"
4+
openstack_octavia_load_balancer_subnet: "172.31.0.0/24"
5+
openstack_octavia_load_balancer_subnet_start: "172.31.0.2"
6+
openstack_octavia_load_balancer_subnet_end: "172.31.0.200"
7+
openstack_octavia_provider_network_type: "ovn"
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
---
2+
3+
- name: Get network info
4+
openstack.cloud.networks_info:
5+
name: "{{ openstack_octavia_load_balancer_network_name }}"
6+
register: existing_networks
7+
run_once: true
8+
9+
- name: Create network if not exists
10+
openstack.cloud.network:
11+
name: "{{ openstack_octavia_load_balancer_network_name }}"
12+
provider_network_type: "{{ openstack_octavia_provider_network_type }}"
13+
state: present
14+
when: existing_networks | length == 0
15+
run_once: true
16+
17+
- name: Get network info again with the ID this time
18+
openstack.cloud.networks_info:
19+
name: "{{ openstack_octavia_load_balancer_network_name }}"
20+
register: existing_networks_again
21+
run_once: true
22+
23+
- name: Create a new subnet in neutron
24+
openstack.cloud.subnet:
25+
network_name: "{{ openstack_octavia_load_balancer_network_name }}"
26+
name: "{{ openstack_octavia_load_balancer_subnet_name }}"
27+
cidr: "{{ openstack_octavia_load_balancer_subnet }}"
28+
allocation_pool_start: "{{ openstack_octavia_load_balancer_subnet_start }}"
29+
allocation_pool_end: "{{ openstack_octavia_load_balancer_subnet_end }}"
30+
state: present
31+
run_once: true
32+
33+
- name: Create a security group
34+
openstack.cloud.security_group:
35+
name: lb-mgmt-sec-grp
36+
state: present
37+
description: security group for octavia load balancers
38+
run_once: true
39+
40+
- name: Create a security group rule
41+
openstack.cloud.security_group_rule:
42+
security_group: lb-mgmt-sec-grp
43+
protocol: icmp
44+
remote_ip_prefix: 0.0.0.0/0
45+
run_once: true
46+
47+
- name: Create a security group rule
48+
openstack.cloud.security_group_rule:
49+
security_group: lb-mgmt-sec-grp
50+
protocol: tcp
51+
port_range_min: 22
52+
port_range_max: 22
53+
remote_ip_prefix: 0.0.0.0/0
54+
run_once: true
55+
56+
- name: Create a security group rule
57+
openstack.cloud.security_group_rule:
58+
security_group: lb-mgmt-sec-grp
59+
protocol: tcp
60+
port_range_min: 9443
61+
port_range_max: 9443
62+
remote_ip_prefix: 0.0.0.0/0
63+
run_once: true
64+
65+
- name: Create a security group for octavia health manager
66+
openstack.cloud.security_group:
67+
name: lb-health-mgr-sec-grp
68+
state: present
69+
description: security group for octavia health manager
70+
run_once: true
71+
register: security_group
72+
73+
- name: Create health manager group security rules
74+
openstack.cloud.security_group_rule:
75+
security_group: lb-health-mgr-sec-grp
76+
protocol: udp
77+
port_range_min: 5555
78+
port_range_max: 5555
79+
remote_ip_prefix: 0.0.0.0/0
80+
run_once: true
81+
82+
- name: Create ports debug
83+
ansible.builtin.debug:
84+
msg: "item {{ item }}"
85+
with_items: "{{ groups['all']
86+
| map('extract', hostvars)
87+
| selectattr('ovs_enabled', 'defined')
88+
| selectattr('ovs_enabled', 'equalto', true)
89+
| map(attribute='inventory_hostname')
90+
| list }}"
91+
92+
- name: Build octavia_port_names (override or hostname)
93+
ansible.builtin.set_fact:
94+
octavia_port_names: "{{ octavia_port_names | default([]) + [ (hostvars[item].octavia_host_override | default(item)) ] }}"
95+
loop: "{{ groups['all'] }}"
96+
when:
97+
- hostvars[item].ovs_enabled is defined
98+
- hostvars[item].ovs_enabled | bool
99+
run_once: true
100+
delegate_to: localhost
101+
loop_control:
102+
label: "{{ item }}"
103+
104+
- name: Create ports debug
105+
ansible.builtin.debug:
106+
msg: "item {{ item }}"
107+
with_items: "{{ octavia_port_names }}"
108+
run_once: true
109+
110+
# We want to create the ports on the ovn nodes. In Understack, we can
111+
# identify those as the nodes having the ansible host var setting
112+
# `ovs_enabled=true`.
113+
# - name: Create ports
114+
# openstack.cloud.port:
115+
# name: "octavia-health-manager-port-{{ item }}"
116+
# network: "{{ openstack_octavia_load_balancer_network_name }}"
117+
# security_groups: lb-health-mgr-sec-grp
118+
# device_owner: "Octavia:health-mgr"
119+
# host: "{{ item }}"
120+
# state: present
121+
# with_items: "{{ octavia_port_names }}"
122+
# run_once: true
123+
124+
# ansible's openstack.cloud.port module doesn't permit you to specify
125+
# the binding:host_id, so we have to do it the hard way using
126+
# openstack.cloud.resource.
127+
- name: Create Octavia health manager port (set binding host_id)
128+
openstack.cloud.resource:
129+
state: present
130+
service: network
131+
type: port
132+
attributes:
133+
name: "octavia-health-manager-port-{{ item }}"
134+
network_id: "{{ existing_networks_again[0].id }}"
135+
device_owner: "Octavia:health-mgr"
136+
# IMPORTANT: this is the host identifier to bind the port to
137+
binding_host_id: "{{ item }}"
138+
# pass SG id(s)
139+
security_groups:
140+
- "{{ security_group.id }}"
141+
register: create_octavia_port
142+
with_items: "{{ octavia_port_names }}"
143+
run_once: true

components/octavia/kustomization.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ kind: Kustomization
55
resources:
66
- octavia-rabbitmq-queue.yaml
77
- octavia-mariadb-db.yaml
8+
- octavia-post-deployment-job.yaml
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
---
2+
apiVersion: batch/v1
3+
kind: Job
4+
metadata:
5+
name: octavia-post-deployment-job
6+
generateName: octavia-post-deployment-job-
7+
annotations:
8+
argocd.argoproj.io/hook: PostSync
9+
argocd.argoproj.io/hook-delete-policy: BeforeHookCreation
10+
spec:
11+
template:
12+
spec:
13+
containers:
14+
- name: octavia-post-deploy
15+
# TODO: switch back to latest tag
16+
image: ghcr.io/rackerlabs/understack/ansible:pr-1182
17+
imagePullPolicy: Always
18+
command: ["ansible-runner", "run", "/runner", "-vvv", "--playbook", "openstack_octavia.yaml"]
19+
env:
20+
- name: OS_CLOUD
21+
value: understack
22+
volumeMounts:
23+
- name: ansible-inventory
24+
mountPath: /runner/inventory/hosts.yaml
25+
subPath: hosts.yaml
26+
- name: ansible-kubernetes-inventory
27+
mountPath: /runner/inventory/inventory.yaml
28+
subPath: inventory.yaml
29+
- name: ansible-group-vars
30+
mountPath: /runner/inventory/group_vars/
31+
- name: openstack-svc-acct
32+
mountPath: /etc/openstack
33+
readOnly: true
34+
volumes:
35+
- name: runner-data
36+
emptyDir: {}
37+
- name: ansible-inventory
38+
configMap:
39+
name: ansible-inventory
40+
- name: ansible-kubernetes-inventory
41+
configMap:
42+
name: ansible-kubernetes-inventory
43+
- name: ansible-group-vars
44+
configMap:
45+
name: ansible-group-vars
46+
- name: openstack-svc-acct
47+
secret:
48+
secretName: openstack-svc-acct
49+
restartPolicy: OnFailure

docs/deploy-guide/load-balancers.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Load Balancer Configuration
2+
3+
If you decide to use Octavia Load Balancers there is a post-install ansible
4+
playbook which sets up networks, security groups, and ports for octavia's
5+
use.
6+
7+
The ansible playbook is `understack/ansible/playbooks/openstack_octavia.yaml`
8+
and it gets triggered after ArgoCD successfully syncs the octavia component
9+
from an ArgoCD post sync hook:
10+
11+
``` yaml
12+
metadata:
13+
annotations:
14+
argocd.argoproj.io/hook: PostSync
15+
argocd.argoproj.io/hook-delete-policy: BeforeHookCreation
16+
```
17+
18+
The post-sync job is `components/octavia/octavia-post-deployment-job.yaml`.
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# Testing and Verification
2+
3+
After deploying a new Understack, we have a few tools available to help us test our new deployment
4+
and ensure everything is working.
5+
6+
## OpenStack Tempest
7+
8+
We'll use [OpenStack Tempest](https://docs.openstack.org/tempest/latest/index.html)
9+
to quickly perform API tests against Understack.
10+
11+
Here's a small example using tempest to run keypair tests against an Understack instance:
12+
13+
``` text
14+
$ tempest run --concurrency 1 --serial --include-list include-keypair.txt
15+
{0} tempest.api.compute.keypairs.test_keypairs.KeyPairsV2TestJSON.test_get_keypair_detail [1.563604s] ... ok
16+
{0} tempest.api.compute.keypairs.test_keypairs.KeyPairsV2TestJSON.test_keypair_create_delete [0.884011s] ... ok
17+
{0} tempest.api.compute.keypairs.test_keypairs.KeyPairsV2TestJSON.test_keypair_create_with_pub_key [0.893123s] ... ok
18+
{0} tempest.api.compute.keypairs.test_keypairs.KeyPairsV2TestJSON.test_keypairs_create_list_delete [3.178549s] ... ok
19+
{0} tempest.api.compute.keypairs.test_keypairs_negative.KeyPairsNegativeTestJSON.test_create_keypair_invalid_name [0.589208s] ... ok
20+
{0} tempest.api.compute.keypairs.test_keypairs_negative.KeyPairsNegativeTestJSON.test_create_keypair_when_public_key_bits_exceeds_maximum [0.432699s] ... ok
21+
{0} tempest.api.compute.keypairs.test_keypairs_negative.KeyPairsNegativeTestJSON.test_create_keypair_with_duplicate_name [1.471715s] ... ok
22+
{0} tempest.api.compute.keypairs.test_keypairs_negative.KeyPairsNegativeTestJSON.test_create_keypair_with_empty_name_string [0.540209s] ... ok
23+
{0} tempest.api.compute.keypairs.test_keypairs_negative.KeyPairsNegativeTestJSON.test_create_keypair_with_empty_public_key [0.440568s] ... ok
24+
{0} tempest.api.compute.keypairs.test_keypairs_negative.KeyPairsNegativeTestJSON.test_create_keypair_with_long_keynames [0.435756s] ... ok
25+
{0} tempest.api.compute.keypairs.test_keypairs_negative.KeyPairsNegativeTestJSON.test_keypair_create_with_invalid_pub_key [0.479691s] ... ok
26+
{0} tempest.api.compute.keypairs.test_keypairs_negative.KeyPairsNegativeTestJSON.test_keypair_delete_nonexistent_key [0.509478s] ... ok
27+
28+
======
29+
Totals
30+
======
31+
Ran: 12 tests in 12.3828 sec.
32+
- Passed: 12
33+
- Skipped: 0
34+
- Expected Fail: 0
35+
- Unexpected Success: 0
36+
- Failed: 0
37+
Sum of execute time for each test: 11.4186 sec.
38+
39+
==============
40+
Worker Balance
41+
==============
42+
- Worker 0 (12 tests) => 0:00:12.382768
43+
```
44+
45+
## OpenStack Rally
46+
47+
We'll also use [OpenStack Rally](https://docs.openstack.org/rally/latest/) to run integration and scenario tests.
48+
49+
See the [understack-tests](https://github.com/rackerlabs/understack/tree/main/python/understack-tests) in the understack repo
50+
for docs on running our rally scenarios.

docs/deploy-guide/troubleshooting.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Deployment Troubleshooting
2+
3+
We have documentation for individual components in the [Operator Guide](../operator-guide/index.md).
4+
5+
## ArgoCD Stuck Syncing
6+
7+
We have seen ArgoCD can sometimes become "stuck" while processing deployments. Sometimes
8+
this can be due to the app component running a kubernetes job which gets stuck indefinitely.
9+
10+
For example, some openstack-helm components have init jobs which depends on other steps being
11+
completed or may have a misconfiguration, where the init will loop forever. In argo you can
12+
see what jobs are stuck, then check the kubernetes job pod logs for further details. Note that
13+
a lot of OpenStack pods may have multiple containers, so interesting logs may not be in the
14+
default container output.
15+
16+
In argo, we've also seen it can be helpful to terminate an old sync and issue a new sync.

mkdocs.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,10 @@ nav:
135135
- deploy-guide/config-argo-workflows.md
136136
- Starting the Deployment:
137137
- deploy-guide/management-cluster.md
138+
- Post Deployment:
139+
- deploy-guide/load-balancers.md
140+
- deploy-guide/testing-verification.md
141+
- deploy-guide/troubleshooting.md
138142
- Further Actions:
139143
- deploy-guide/extra-sites.md
140144
- deploy-guide/add-remove-app.md

0 commit comments

Comments
 (0)