--- # Deploy and configure Single Node OpenShift (SNO) on Proxmox # # Prerequisites: # ansible-galaxy collection install -r collections/requirements.yml # openshift-install is downloaded automatically during the sno_deploy_install play # # Inventory requirements: # sno.openshift.toal.ca - in 'openshift' group # host_vars: ocp_cluster_name, ocp_base_domain, ocp_version, sno_ip, # sno_gateway, sno_nameserver, sno_prefix_length, sno_machine_network, # sno_vm_name, sno_vnet, sno_storage_ip, sno_storage_ip_prefix_length, # sno_storage_vnet, proxmox_node, keycloak_url, keycloak_realm, # oidc_admin_groups, sno_deploy_letsencrypt_email, ... # secrets: vault_ocp_pull_secret, vault_keycloak_admin_password, # vault_oidc_client_secret (optional) # optional: ocp_kubeconfig (defaults to ~/.kube/config; set to # sno_install_dir/auth/kubeconfig for fresh installs) # proxmox_api - inventory host (ansible_host, ansible_port) # proxmox_host - inventory host (ansible_host, ansible_connection: ssh) # gate.toal.ca - in 'opnsense' group # host_vars: opnsense_host, opnsense_api_key, opnsense_api_secret, # opnsense_api_port, haproxy_public_ip # group_vars/all: dme_account_key, dme_account_secret # # Play order (intentional — DNS must precede VM boot): # Play 1: sno_deploy_vm — Create SNO VM # Play 2: opnsense — Configure OPNsense local DNS overrides # Play 3: dns — Configure public DNS records in DNS Made Easy # Play 4: sno_deploy_install — Generate ISO, boot VM, wait for install # Play 5: keycloak — Configure Keycloak OIDC client # Play 6: sno_deploy_oidc / sno_deploy_certmanager / sno_deploy_delete_kubeadmin # Play 7: sno_deploy_lvms — Install LVM Storage for persistent volumes # Play 8: sno_deploy_nfs — Deploy in-cluster NFS provisioner (RWX StorageClass) # Play 9: sno_deploy_service_accounts — Provision ServiceAccounts for app deployers # # AAP deployment is in a separate playbook: deploy_aap.yml # # Usage: # ansible-navigator run playbooks/deploy_openshift.yml # ansible-navigator run playbooks/deploy_openshift.yml --tags sno_deploy_vm # ansible-navigator run playbooks/deploy_openshift.yml --tags sno_deploy_install # ansible-navigator run playbooks/deploy_openshift.yml --tags opnsense,dns # ansible-navigator run playbooks/deploy_openshift.yml --tags keycloak,sno_deploy_oidc # ansible-navigator run playbooks/deploy_openshift.yml --tags sno_deploy_certmanager # ansible-navigator run playbooks/deploy_openshift.yml --tags sno_deploy_lvms # ansible-navigator run playbooks/deploy_openshift.yml --tags sno_deploy_nfs # ansible-navigator run playbooks/deploy_openshift.yml --tags sno_deploy_service_accounts # --------------------------------------------------------------------------- # Play 1: Create SNO VM in Proxmox # --------------------------------------------------------------------------- - name: Create SNO VM in Proxmox hosts: sno.openshift.toal.ca gather_facts: false connection: local tags: sno_deploy_vm tasks: - name: Create VM ansible.builtin.include_role: name: sno_deploy tasks_from: create_vm.yml # --------------------------------------------------------------------------- # Play 2: Configure OPNsense - Local DNS Overrides # Must run BEFORE booting the VM so that api.openshift.toal.ca resolves # from within the SNO node during bootstrap. # --------------------------------------------------------------------------- - name: Configure OPNsense DNS overrides for OpenShift hosts: gate.toal.ca gather_facts: false connection: local module_defaults: group/oxlorg.opnsense.all: firewall: "{{ opnsense_host }}" api_key: "{{ opnsense_api_key }}" api_secret: "{{ opnsense_api_secret }}" ssl_verify: "{{ opnsense_ssl_verify | default(false) }}" api_port: "{{ opnsense_api_port | default(omit) }}" vars: __deploy_ocp_cluster_name: "{{ hostvars['sno.openshift.toal.ca']['ocp_cluster_name'] }}" __deploy_ocp_base_domain: "{{ hostvars['sno.openshift.toal.ca']['ocp_base_domain'] }}" __deploy_sno_ip: "{{ hostvars['sno.openshift.toal.ca']['sno_ip'] }}" tags: opnsense roles: - role: opnsense_dns_override opnsense_dns_override_entries: - hostname: "api.{{ __deploy_ocp_cluster_name }}" domain: "{{ __deploy_ocp_base_domain }}" value: "{{ __deploy_sno_ip }}" type: host - hostname: "api-int.{{ __deploy_ocp_cluster_name }}" domain: "{{ __deploy_ocp_base_domain }}" value: "{{ __deploy_sno_ip }}" type: host - domain: "apps.{{ __deploy_ocp_cluster_name }}.{{ __deploy_ocp_base_domain }}" value: "{{ __deploy_sno_ip }}" type: forward # --------------------------------------------------------------------------- # Play 3: Configure Public DNS Records in DNS Made Easy # --------------------------------------------------------------------------- - name: Configure public DNS records for OpenShift hosts: sno.openshift.toal.ca gather_facts: false connection: local vars: __deploy_public_ip: "{{ hostvars['gate.toal.ca']['haproxy_public_ip'] }}" tags: dns roles: - role: dnsmadeeasy_record dnsmadeeasy_record_account_key: "{{ dme_account_key }}" dnsmadeeasy_record_account_secret: "{{ dme_account_secret }}" dnsmadeeasy_record_entries: - domain: "{{ ocp_base_domain }}" record_name: "api.{{ ocp_cluster_name }}" record_type: A record_value: "{{ __deploy_public_ip }}" record_ttl: "{{ ocp_dns_ttl }}" - domain: "{{ ocp_base_domain }}" record_name: "*.apps.{{ ocp_cluster_name }}" record_type: A record_value: "{{ __deploy_public_ip }}" record_ttl: "{{ ocp_dns_ttl }}" # --------------------------------------------------------------------------- # Play 4: Generate Agent ISO and Deploy SNO # --------------------------------------------------------------------------- - name: Generate Agent ISO and Deploy SNO hosts: sno.openshift.toal.ca gather_facts: false connection: local tags: sno_deploy_install tasks: - name: Install SNO ansible.builtin.include_role: name: sno_deploy tasks_from: install.yml # --------------------------------------------------------------------------- # Play 5: Configure Keycloak OIDC client for OpenShift # --------------------------------------------------------------------------- - name: Configure Keycloak OIDC client for OpenShift hosts: openshift gather_facts: false connection: local tags: keycloak vars: keycloak_context: "" oidc_client_id: openshift oidc_redirect_uri: "https://oauth-openshift.apps.{{ ocp_cluster_name }}.{{ ocp_base_domain }}/oauth2callback/{{ oidc_provider_name }}" __oidc_keycloak_api_url: "{{ keycloak_url }}{{ keycloak_context }}" module_defaults: middleware_automation.keycloak.keycloak_realm: auth_client_id: admin-cli auth_keycloak_url: "{{ __oidc_keycloak_api_url }}" auth_realm: master auth_username: "{{ keycloak_admin_user }}" auth_password: "{{ vault_keycloak_admin_password }}" validate_certs: "{{ keycloak_validate_certs | default(true) }}" middleware_automation.keycloak.keycloak_client: auth_client_id: admin-cli auth_keycloak_url: "{{ __oidc_keycloak_api_url }}" auth_realm: master auth_username: "{{ keycloak_admin_user }}" auth_password: "{{ vault_keycloak_admin_password }}" validate_certs: "{{ keycloak_validate_certs | default(true) }}" tasks: - name: Set OIDC client secret (use vault value or generate random) ansible.builtin.set_fact: __oidc_client_secret: "{{ vault_oidc_client_secret | default(lookup('community.general.random_string', length=32, special=false)) }}" __oidc_secret_generated: "{{ vault_oidc_client_secret is not defined }}" no_log: true - name: Ensure Keycloak realm exists middleware_automation.keycloak.keycloak_realm: realm: "{{ keycloak_realm }}" id: "{{ keycloak_realm }}" display_name: "{{ keycloak_realm_display_name | default(keycloak_realm | title) }}" enabled: true state: present no_log: "{{ keycloak_no_log | default(true) }}" - name: Create OpenShift OIDC client in Keycloak middleware_automation.keycloak.keycloak_client: realm: "{{ keycloak_realm }}" client_id: "{{ oidc_client_id }}" name: "OpenShift - {{ ocp_cluster_name }}" description: "OIDC client for OpenShift cluster {{ ocp_cluster_name }}.{{ ocp_base_domain }}" enabled: true protocol: openid-connect public_client: false standard_flow_enabled: true implicit_flow_enabled: false direct_access_grants_enabled: false service_accounts_enabled: false secret: "{{ __oidc_client_secret }}" redirect_uris: - "{{ oidc_redirect_uri }}" web_origins: - "+" protocol_mappers: - name: groups protocol: openid-connect protocolMapper: oidc-group-membership-mapper config: full.path: "false" id.token.claim: "true" access.token.claim: "true" userinfo.token.claim: "true" claim.name: groups state: present no_log: "{{ keycloak_no_log | default(true) }}" - name: Display generated client secret (save this to vault!) ansible.builtin.debug: msg: - "*** GENERATED OIDC CLIENT SECRET — SAVE THIS TO VAULT ***" - "vault_oidc_client_secret: {{ __oidc_client_secret }}" - "" - "Set this in host_vars or pass as --extra-vars on future runs." when: __oidc_secret_generated | bool - name: Display Keycloak configuration summary ansible.builtin.debug: msg: - "Keycloak OIDC client configured:" - " Realm : {{ keycloak_realm }}" - " Client : {{ oidc_client_id }}" - " Issuer : {{ keycloak_url }}{{ keycloak_context }}/realms/{{ keycloak_realm }}" - " Redirect: {{ oidc_redirect_uri }}" verbosity: 1 # --------------------------------------------------------------------------- # Play 6: Post-install OpenShift configuration # Configure OIDC OAuth, cert-manager, and delete kubeadmin # --------------------------------------------------------------------------- - name: Configure OpenShift post-install hosts: sno.openshift.toal.ca gather_facts: false connection: local environment: KUBECONFIG: "{{ ocp_kubeconfig | default('~/.kube/config') }}" K8S_AUTH_VERIFY_SSL: "false" tags: - sno_deploy_oidc - sno_deploy_certmanager - sno_deploy_delete_kubeadmin tasks: - name: Configure OpenShift OAuth with OIDC ansible.builtin.include_role: name: sno_deploy tasks_from: configure_oidc.yml tags: sno_deploy_oidc - name: Configure cert-manager and LetsEncrypt certificates ansible.builtin.include_role: name: sno_deploy tasks_from: configure_certmanager.yml tags: sno_deploy_certmanager - name: Delete kubeadmin user ansible.builtin.include_role: name: sno_deploy tasks_from: delete_kubeadmin.yml tags: - never - sno_deploy_delete_kubeadmin # --------------------------------------------------------------------------- # Play 7: Install LVM Storage for persistent volumes # --------------------------------------------------------------------------- - name: Configure LVM Storage for persistent volumes hosts: sno.openshift.toal.ca gather_facts: false connection: local tags: sno_deploy_lvms environment: KUBECONFIG: "{{ ocp_kubeconfig | default('~/.kube/config') }}" K8S_AUTH_VERIFY_SSL: "false" roles: - role: lvms_operator # --------------------------------------------------------------------------- # Play 8: Deploy NFS provisioner for ReadWriteMany storage # Set nfs_provisioner_external_server / nfs_provisioner_external_path to use # a pre-existing NFS share (e.g. 192.168.129.100:/mnt/BIGPOOL/NoBackups/OCPNFS). # When those are unset, an in-cluster NFS server is deployed; LVMS (Play 7) must # have run first to provide the backing RWO PVC. # --------------------------------------------------------------------------- - name: Deploy in-cluster NFS provisioner hosts: sno.openshift.toal.ca gather_facts: false connection: local tags: sno_deploy_nfs environment: KUBECONFIG: "{{ ocp_kubeconfig | default('~/.kube/config') }}" K8S_AUTH_VERIFY_SSL: "false" roles: - role: nfs_provisioner # --------------------------------------------------------------------------- # Play 9: Provision ServiceAccounts for application deployers # --------------------------------------------------------------------------- - name: Provision OpenShift service accounts hosts: sno.openshift.toal.ca gather_facts: false connection: local environment: KUBECONFIG: "{{ ocp_kubeconfig | default('~/.kube/config') }}" K8S_AUTH_VERIFY_SSL: "false" tags: - never - sno_deploy_service_accounts roles: - role: ocp_service_account ocp_service_account_name: aap-deployer ocp_service_account_namespace: aap ocp_service_account_cluster_role_rules: - apiGroups: [""] resources: ["namespaces"] verbs: ["get", "list", "create", "patch"] - apiGroups: [""] resources: ["secrets"] verbs: ["get", "list", "watch", "create", "patch"] - apiGroups: [""] resources: ["serviceaccounts"] verbs: ["get", "list", "watch"] - apiGroups: ["apps"] resources: ["deployments"] verbs: ["get", "list", "watch"] - apiGroups: ["operators.coreos.com"] resources: ["operatorgroups", "subscriptions", "clusterserviceversions"] verbs: ["get", "list", "create", "patch", "watch"] - apiGroups: ["apiextensions.k8s.io"] resources: ["customresourcedefinitions"] verbs: ["get", "list", "watch"] - apiGroups: ["aap.ansible.com"] resources: ["ansibleautomationplatforms"] verbs: ["get", "list", "create", "patch", "watch"]