Skip to content

Rootless iop - #5

Open
pablomh wants to merge 108 commits into
masterfrom
rootless_iop
Open

Rootless iop#5
pablomh wants to merge 108 commits into
masterfrom
rootless_iop

Conversation

@pablomh

@pablomh pablomh commented Feb 26, 2026

Copy link
Copy Markdown
Owner

No description provided.

@pablomh
pablomh force-pushed the rootless_iop branch 20 times, most recently from 0e26970 to 88624aa Compare March 3, 2026 21:27
pablomh and others added 2 commits March 9, 2026 10:35
Introduces the deploy_network role, which wraps
containers.podman.podman_network to create a named bridge network with
configurable properties: internal (no external routing), isolate (no
cross-network packet forwarding), IPv6, subnet, gateway, and DNS.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace host networking with four named Podman bridge networks:

- foreman-db (internal, isolated): PostgreSQL and its clients
- foreman-cache (internal, isolated): Redis and its clients
- foreman-app: application layer — Foreman, Candlepin, Pulp, httpd
- foreman-proxy-net: Foreman and the smart proxy

Services reach each other by container DNS name instead of localhost.
Per-service notes:

- postgresql: also accessible via a Unix socket bind-mounted at
  /var/run/postgresql, used by Ansible's community.postgresql modules
  during deployment so no TCP port needs to be published to the host.
- redis: publishes no ports; clients reach it by DNS name on
  foreman-cache.
- candlepin: Tomcat binds to 0.0.0.0 so that other containers on the
  bridge network can reach it via its DNS name.
- migration containers (foreman db:migrate, pulp migrations): attached
  to foreman-db when the database is internal; switched to foreman-app
  when the database is external, because foreman-db uses internal: true
  which removes the default gateway, making it impossible for containers
  on that network to reach an external database host.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
pablomh and others added 3 commits March 9, 2026 15:35
The development setup runs the Foreman Rails process directly on the
host rather than in a container, so it cannot resolve container bridge
DNS names such as postgresql or candlepin. Keeping host networking for
all services in the devel environment means every service remains
reachable on localhost as before, without requiring per-service URL
overrides for the Rails process.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
foreman-certs generates certificates only for localhost and the server
FQDN. Now that Foreman connects to Candlepin via the bridge DNS name
"candlepin", TLS hostname validation fails against those certificates.
A dedicated certificate with SAN=candlepin, signed by the installer
CA, is generated using the openssl CLI (consistent with the rest of
the codebase).

The certificate validity is set to 7300 days (20 years), matching the
default used by puppet-certs (theforeman/puppet-certs manifests/init.pp
$expiration parameter). The signing step runs on every deployment to
ensure the certificate is always freshly dated, consistent with how
foreman-certs handles the other installer certificates.

The localhost certificate previously used by Candlepin's Tomcat is now
unused and removed:

- "localhost" dropped from certificates_hostnames — the certificates
  role no longer generates the cert.
- localhost_key / localhost_certificate removed from
  default_certificates.yml and installer_certificates.yml.

The healthcheck is updated to validate the new certificate instead of
skipping verification with --insecure. --resolve candlepin:23443:127.0.0.1
forces the connection to the loopback so it works in both bridge
networking (where other containers reach Candlepin by DNS name) and
host networking (where "candlepin" would not resolve via container DNS).

Tests are updated to route connectivity checks through the foreman
container (which shares the same bridge network as candlepin), to verify
against the new certificate, and to check candlepin_certificate expiry
instead of the now-removed localhost_certificate.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds docs/deployment.md with a description of the four bridge
networks introduced by this series, their properties (internal,
isolate), which containers are attached to each, and the rationale
for the port publishing decisions.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
pablomh and others added 3 commits March 9, 2026 16:05
Creates a dedicated service user (foremanctl) for running Podman
containers in rootless mode. The role handles all prerequisites:
- acl package (required for Ansible become_user on CentOS 10)
- User and group creation with a non-login shell
- subuid/subgid ranges for user namespace mapping
- Unprivileged port binding via sysctl
- loginctl enable-linger so the user session survives across boots
- XDG_RUNTIME_DIR discovery (exposed as rootless_user_xdg_runtime_dir)
- Quadlet and systemd user config directories

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…plays

base.yaml gains foremanctl_user/group/home/quadlet_dir/systemd_user_dir
vars. It also introduces foremanctl_become_method and foremanctl_runtime_env
as the single place to control how all rootless become blocks operate:
- foremanctl_become_method: swap between 'sudo' (default) or
  'community.general.machinectl' (or any future method) without touching
  individual roles
- foremanctl_runtime_env: holds XDG_RUNTIME_DIR for sudo; set to {} when
  the become method provides the session environment automatically

deploy.yaml is split into two plays so the rootless_user role can expose
foremanctl_xdg_runtime_dir as a fact before the service roles run.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Move the default certificate directory from /root/certificates to
/var/lib/foremanctl/certificates so that the rootless containers running
in foremanctl's user namespace can read them without elevated privileges.

The path is hardcoded rather than using foremanctl_home because
default_certificates.yml is loaded before base.yaml and base.yaml itself
references certificate path vars.

ca.yml: add base directory creation; set owner/group to
foremanctl_user/foremanctl_group on all directories and generated files.

issue.yml: set owner/group to foremanctl_user/foremanctl_group on all
generated keys, CSRs and certificates.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…nuser -l

Remove the duplicate run_as that was shadowing the correct implementation.
Revert the runuser call back to the original working form with -l flag,
XDG_RUNTIME_DIR export, and single-quote escaping (escaped), as it was in
commit a2c086d. XDG_RUNTIME_DIR is required so systemctl --user can
locate the D-Bus session bus socket.

Also revert postgresql and pulp tests to their original commands from
a2c086d, avoiding the multiple-shell-layer escaping issues introduced
by subsequent "fix" commits. The original psql -c style commands work
correctly through testinfra's sudo wrapper.

Also fix iop_fdw and iop_inventory roles to connect to PostgreSQL via Unix
socket instead of TCP (login_host), matching how all other roles connect.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
pablomh and others added 4 commits March 14, 2026 12:21
Add AddHost={{ ansible_facts['fqdn'] }}:host-gateway to dynflow-sidekiq
containers so they can resolve the server FQDN to reach the HTTPD
reverse proxy and Pulp API. Without this, Katello background workers
fail with PulpRpmClient::ApiError: Connection failed when processing
repository tasks. The Foreman web container already had this fix
(added in 975e3d8), but the dynflow workers were missed.

Skip test_postgresql_service when database_mode=external: in that
configuration postgresql runs on a separate database VM, not on
quadlet. The companion test_postgresql_missing_with_external already
asserts the correct behaviour for external mode.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Use iop-core-gateway hostname instead of localhost when curling from
inside the container. The server certificate has SAN=iop-core-gateway,
so curl to https://localhost:8443/ fails TLS verification, returning
000 twice (once from -w http_code, once from || echo 000).
Inside the container, iop-core-gateway resolves via aardvark-dns to
the container own network IP.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove || true from upgrade stop step; the stop should succeed
- Group installer certificate pre_tasks in a single block with one
  when condition; drop redundant become: true (inherited from play)
- Remove noisy comment from candlepin handler
- Fix hardcoded /var/lib/foremanctl in certs_secrets.yml comments
- Add blank line before Create Candlepin configuration task
- Add foremanctl_registry_auth_file to base.yaml and include
  REGISTRY_AUTH_FILE in foremanctl_runtime_env so all podman pull
  tasks in foremanctl blocks get registry authentication automatically

Flush handlers after service start is intentional: restarting before
start would fail on first deploy.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
runuser requires root and misparses --user as its own flag. Replace with
sudo -u foremanctl and set XDG_RUNTIME_DIR explicitly so systemctl --user
can reach the D-Bus session bus.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@pablomh
pablomh force-pushed the rootless_iop branch 6 times, most recently from dbd6e99 to 1a99c8c Compare March 14, 2026 22:21
Tests run last (filename sorts after redis_test.py) since stopping the
target would break any subsequent service checks. This also validates
the stop command used in the upgrade workflow.

Use server.run with systemctl --machine=foremanctl@ --user for all
systemctl operations, consistent with how non-podman commands are run
in other tests. After each lifecycle operation, poll the Foreman HTTPS
frontend (https://server_fqdn/api/v2/ping with CA cert) matching the
pattern used in test_https_foreman_ping. On timeout, dump user service
states for diagnosis.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@pablomh
pablomh force-pushed the rootless_iop branch 5 times, most recently from 36923e7 to 8ea52c7 Compare March 15, 2026 03:53
Tests run last (filename sorts after redis_test.py) since stopping the
target would break any subsequent service checks. This also validates
the stop command used in the upgrade workflow.

Use server.run with systemctl --machine=foremanctl@ --user for all
systemctl operations. After each lifecycle operation, poll the Foreman
HTTPS frontend (https://server_fqdn/api/v2/ping with CA cert) matching
the pattern used in test_https_foreman_ping. On timeout, dump service
states, foreman.service status, container states and foreman logs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@pablomh
pablomh force-pushed the rootless_iop branch 4 times, most recently from bf0aed4 to 0dad8d8 Compare March 15, 2026 13:02
Tests run last (filename sorts after redis_test.py) since stopping the
target would break any subsequent service checks. This also validates
the stop command used in the upgrade workflow.

Use server.run with systemctl --machine=foremanctl@ --user for all
systemctl operations. After each lifecycle operation, poll the Foreman
HTTPS frontend (https://server_fqdn/api/v2/ping with CA cert) matching
the pattern used in test_https_foreman_ping.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
pablomh and others added 5 commits March 18, 2026 01:07
Wrap the systemd restart in block/rescue to collect service status
and container logs when the restart fails, then re-raise so CI
still marks the run as failed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Foreman depends on Candlepin being fully initialised before it can
serve requests reliably. Without this ordering, foreman.service starts
simultaneously with candlepin.service, competing for CPU and disk I/O
during the JVM startup. This pushes Rails loading time close to the
default TimeoutStartSec (90s) and causes intermittent failures on
restart, particularly when the system is under load.

Because candlepin.service already declares:
  After=redis.service postgresql.service

the transitive ordering becomes:

  postgresql --+
               +--> candlepin --> foreman
  redis -------+

redis.service and postgresql.service are kept as explicit dependencies
to preserve correct ordering in future non-Katello deployments that
may not include candlepin.service.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Extends foreman_target_test.py with lifecycle tests intended to run
at the end of the test suite. Tests that foreman.target can be cleanly
stopped and restarted, and that Foreman becomes available again
afterwards via the HTTPS endpoint (same curl pattern as
test_https_foreman_ping in httpd_test.py).
Incorporates upstream commits:
- Use candlepin.service in foreman startup ordering (f2e162d)
- Add foreman.target lifecycle tests (5a2eb8b)

Conflicts resolved keeping rootless implementations; remote-database
playbook updated to include both SSL cert vars and postgresql_networks.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants