Skip to content
Open
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
1 change: 1 addition & 0 deletions playbooks/satellite/capsule_lbs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@
roles:
- role: linux-system-roles.firewall
- role: ../common/roles/enlarge-arp-table
- role: registration_cache
- role: haproxy
...
23 changes: 23 additions & 0 deletions playbooks/satellite/roles/capsule/tasks/main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,29 @@
ansible.builtin.shell:
cmd: "{{ run_after_installer }}"

- name: Configure registration script shared cache
# Set :cache_url in the smart-proxy registration settings so this capsule
# connects to the shared Redis/Valkey instance on the LB host. This allows
# a single warm request on any capsule to populate the cache for all nodes
# behind the same LB.
when:
- "'capsule_lbs' in groups"
- groups['capsule_lbs'] | intersect(groups[location_groupname]) | length > 0
vars:
location_lb: "{{ groups['capsule_lbs'] | intersect(groups[location_groupname]) | first }}"
block:
- name: Set :cache_url in smart-proxy registration settings
ansible.builtin.lineinfile:
path: /etc/foreman-proxy/settings.d/registration.yml
regexp: '^#?\s*:cache_url'
line: ":cache_url: redis://{{ hostvars[location_lb]['private_ip'] }}:6379/0"
create: false

- name: Restart foreman-proxy to pick up cache URL
ansible.builtin.systemd_service:
name: foreman-proxy
state: restarted

- name: Load-balanced capsule IDs
when:
- "'capsule_lbs' in groups"
Expand Down
14 changes: 14 additions & 0 deletions playbooks/satellite/roles/haproxy/defaults/main.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
# Number of consecutive health check failures before a capsule is removed
# from rotation in the host_registration backend.
#
# Default is 9999 (effectively never) to prevent load-induced false failover
# during stress tests — a capsule under heavy registration load may fail the
# /register/health check transiently even though it is still functioning.
# Removing it from rotation would concentrate load on the remaining capsules
# and cause cascading failures.
#
# Set to 3 for production-grade failover (removes capsule after ~90s of
# consecutive failures at the default 30s check interval):
# haproxy_registration_fall: 3
haproxy_registration_fall: 9999
10 changes: 8 additions & 2 deletions playbooks/satellite/roles/haproxy/templates/haproxy.cfg.j2
Original file line number Diff line number Diff line change
Expand Up @@ -145,9 +145,15 @@ frontend host_registration
default_backend host_registration-backend

backend host_registration-backend
option tcp-check
# HTTP health check against the smart-proxy /register/health endpoint.
# Returns 200 if the capsule can reach Foreman, 503 if not.
# 'fall' defaults to 9999 (see defaults/main.yaml) to prevent
# load-induced false failover during stress tests. Set
# haproxy_registration_fall: 3 for production-grade automatic failover.
option httpchk GET /register/health HTTP/1.1\r\nHost:\ localhost
http-check expect status 200
balance roundrobin

{% for capsule in lb_capsules %}
server host_registration-backend-{{loop.index}} {{ capsule }}:9090 check
server host_registration-backend-{{loop.index}} {{ capsule }}:9090 check ssl verify none fall {{ haproxy_registration_fall }} rise 2 inter 30s
{% endfor %}
13 changes: 13 additions & 0 deletions playbooks/satellite/roles/registration_cache/defaults/main.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
# Maximum memory the cache backend is allowed to use.
# Registration scripts are small (~5-10 KB each); 64 MB handles thousands
# of distinct parameter combinations before any eviction occurs.
registration_cache_maxmemory: 64mb

# Eviction policy when maxmemory is reached.
# allkeys-lru evicts the least-recently-used key regardless of TTL,
# which is appropriate since all cached scripts share the same TTL.
registration_cache_maxmemory_policy: allkeys-lru

# Port the cache backend listens on. Both Redis and Valkey default to 6379.
registration_cache_port: 6379
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
- name: Restart registration cache
ansible.builtin.systemd_service:
name: "{{ registration_cache_service }}"
state: restarted
58 changes: 58 additions & 0 deletions playbooks/satellite/roles/registration_cache/tasks/main.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
---
# Select package/service/config based on OS version.
# RHEL 9 and earlier: Redis (from AppStream)
# RHEL 10 and later: Valkey (Redis fork; Redis removed from RHEL 10 due to
# SSPL license change). Both use port 6379 and the same
# redis:// wire protocol.
- name: "Set cache backend facts for RHEL {{ os_major_release }}"
ansible.builtin.set_fact:
registration_cache_package: "{{ 'valkey' if os_major_release | int >= 10 else 'redis' }}"
registration_cache_service: "{{ 'valkey' if os_major_release | int >= 10 else 'redis' }}"
registration_cache_config: "{{ '/etc/valkey/valkey.conf' if os_major_release | int >= 10 else '/etc/redis/redis.conf' }}"

- name: "Install {{ registration_cache_package }}"
ansible.builtin.dnf:
name: "{{ registration_cache_package }}"
state: latest

# Bind to localhost (for local HAProxy stats queries) and to the private IP
# so that capsule nodes on the private network can reach the cache.
- name: "Configure cache backend bind address"
ansible.builtin.lineinfile:
path: "{{ registration_cache_config }}"
regexp: '^bind '
line: "bind 127.0.0.1 {{ private_ip }}"
backup: true
notify: Restart registration cache

- name: "Configure cache backend maxmemory"
ansible.builtin.lineinfile:
path: "{{ registration_cache_config }}"
regexp: '^#?\s*maxmemory\s'
line: "maxmemory {{ registration_cache_maxmemory }}"
backup: true
notify: Restart registration cache

- name: "Configure cache backend maxmemory-policy"
ansible.builtin.lineinfile:
path: "{{ registration_cache_config }}"
regexp: '^#?\s*maxmemory-policy\s'
line: "maxmemory-policy {{ registration_cache_maxmemory_policy }}"
backup: true
notify: Restart registration cache

# Open the cache port for capsule nodes on the private network.
# The firewall role has already run; this task adds the cache-specific rule.
- name: "Open cache port {{ registration_cache_port }}/tcp for private network"
ansible.posix.firewalld:
port: "{{ registration_cache_port }}/tcp"
zone: internal
permanent: true
immediate: true
state: enabled

- name: "Start and enable {{ registration_cache_service }}"
ansible.builtin.systemd_service:
name: "{{ registration_cache_service }}"
state: started
enabled: true
Loading