diff --git a/ansible/compute-node-flavors.yml b/ansible/compute-node-flavors.yml
index 4170f95e2..49868998c 100644
--- a/ansible/compute-node-flavors.yml
+++ b/ansible/compute-node-flavors.yml
@@ -7,7 +7,7 @@
 - name: Ensure baremetal compute node flavors are registered in nova
   hosts: controllers[0]
   vars:
-    venv: "{{ ansible_env.PWD }}/shade-venv"
+    venv: "{{ virtualenv_path }}/shade"
     flavor_base_name: baremetal-
   roles:
     - role: stackhpc.os-openstackclient
diff --git a/ansible/compute-node-provide.yml b/ansible/compute-node-provide.yml
index 5a903ed12..2ee3256c9 100644
--- a/ansible/compute-node-provide.yml
+++ b/ansible/compute-node-provide.yml
@@ -6,7 +6,7 @@
 - name: Ensure compute nodes are available in ironic
   hosts: controllers[0]
   vars:
-    venv: "{{ ansible_env.PWD }}/shade-venv"
+    venv: "{{ virtualenv_path }}/shade"
     # Set this to a colon-separated list of compute node hostnames to provide.
     # If unset, all compute nodes will be provided.
     compute_node_limit: ""
diff --git a/ansible/container-image-build.yml b/ansible/container-image-build.yml
index 17562bdf8..ae53aec9e 100644
--- a/ansible/container-image-build.yml
+++ b/ansible/container-image-build.yml
@@ -7,7 +7,6 @@
     # Set this variable to a space-separated list of regexes to override the
     # default set of images.
     container_image_regexes: ""
-    kolla_venv: "{{ ansible_env['PWD'] }}/kolla-venv"
     kolla_build_log_path: "/var/log/kolla-build.log"
   tasks:
     - name: Set the container image sets to build if images regexes specified
diff --git a/ansible/external-net.yml b/ansible/external-net.yml
index a3b8663b9..810c17312 100644
--- a/ansible/external-net.yml
+++ b/ansible/external-net.yml
@@ -2,11 +2,9 @@
 - name: Ensure external network and subnet are registered in neutron
   # Only required to run on a single host.
   hosts: controllers[0]
-  vars:
-    venv: "{{ ansible_env.PWD }}/shade-venv"
   roles:
     - role: neutron-net
-      neutron_net_venv: "{{ venv }}"
+      neutron_net_venv: "{{ virtualenv_path }}/shade"
       neutron_net_openstack_auth_type: "{{ openstack_auth_type }}"
       neutron_net_openstack_auth: "{{ openstack_auth }}"
       # Network configuration.
diff --git a/ansible/group_vars/all/kolla b/ansible/group_vars/all/kolla
index fd2493fe5..a1a588b2c 100644
--- a/ansible/group_vars/all/kolla
+++ b/ansible/group_vars/all/kolla
@@ -19,6 +19,9 @@ kolla_ansible_source_url: "https://github.com/stackhpc/kolla-ansible"
 # is 'source'.
 kolla_ansible_source_version: "stackhpc-{{ kolla_openstack_release }}"
 
+# Path to virtualenv in which to install kolla.
+kolla_venv: "{{ virtualenv_path ~ '/kolla' }}"
+
 ###############################################################################
 # Kolla configuration.
 
diff --git a/ansible/ipa-build.yml b/ansible/ipa-build.yml
index 039fdd27c..a2edf1d18 100644
--- a/ansible/ipa-build.yml
+++ b/ansible/ipa-build.yml
@@ -6,7 +6,7 @@
     ipa_build_force: False
   roles:
     - role: ipa-build
-      ipa_build_venv: "{{ ansible_env['PWD'] }}/ipa-build-venv"
+      ipa_build_venv: "{{ virtualenv_path }}/ipa-build"
       ipa_build_image_cache_path: "{{ image_cache_path }}"
       ipa_build_source_checkout_path: "{{ source_checkout_path }}"
       ipa_build_kernel_name: "{{ ipa_images_kernel_name }}"
diff --git a/ansible/ipa-images.yml b/ansible/ipa-images.yml
index 3d0df4fa3..78b422622 100644
--- a/ansible/ipa-images.yml
+++ b/ansible/ipa-images.yml
@@ -40,7 +40,7 @@
 
   roles:
     - role: ipa-images
-      ipa_images_venv: "{{ ansible_env['PWD'] }}/shade-venv"
+      ipa_images_venv: "{{ virtualenv_path }}/shade"
       ipa_images_openstack_auth_type: "{{ openstack_auth_type }}"
       ipa_images_openstack_auth: "{{ openstack_auth }}"
       ipa_images_cache_path: "{{ image_cache_path }}"
diff --git a/ansible/overcloud-introspection-rules-dell-lldp-workaround.yml b/ansible/overcloud-introspection-rules-dell-lldp-workaround.yml
index a7af1c232..cc792bd05 100644
--- a/ansible/overcloud-introspection-rules-dell-lldp-workaround.yml
+++ b/ansible/overcloud-introspection-rules-dell-lldp-workaround.yml
@@ -19,7 +19,6 @@
   # Only required to run on a single host.
   hosts: controllers_require_workaround_True[0]
   vars:
-    venv: "{{ ansible_env.PWD }}/shade-venv"
     all_switch_interfaces: []
     ironic_inspector_rules: []
     # This rule template is used in a with_subelements loop.
@@ -99,6 +98,6 @@
   
   roles:
     - role: ironic-inspector-rules
-      ironic_inspector_venv: "{{ venv }}"
+      ironic_inspector_venv: "{{ virtualenv_path }}/shade"
       ironic_inspector_auth_type: "{{ openstack_auth_type }}"
       ironic_inspector_auth: "{{ openstack_auth }}"
diff --git a/ansible/overcloud-introspection-rules.yml b/ansible/overcloud-introspection-rules.yml
index ae3e9687f..7807dd8f4 100644
--- a/ansible/overcloud-introspection-rules.yml
+++ b/ansible/overcloud-introspection-rules.yml
@@ -1,9 +1,9 @@
 ---
-- name: Ensure openstackclient is installed
+- name: Ensure introspection rules are registered in Ironic Inspector
   # Only required to run on a single host.
   hosts: controllers[0]
   vars:
-    venv: "{{ ansible_env.PWD }}/shade-venv"
+    venv: "{{ virtualenv_path }}/shade"
   pre_tasks:
     - name: Validate OpenStack password authentication parameters
       fail:
@@ -18,16 +18,9 @@
       tags:
         - config-validation
 
-  roles:
-    - role: stackhpc.os-openstackclient
+    - include_role: stackhpc.os-openstackclient
       os_openstackclient_venv: "{{ venv }}"
 
-- name: Ensure introspection rules are registered in Ironic Inspector
-  # Only required to run on a single host.
-  hosts: controllers[0]
-  vars:
-    venv: "{{ ansible_env.PWD }}/shade-venv"
-  pre_tasks:
     - name: Retrieve the IPA kernel Glance image UUID
       shell: >
         source {{ venv }}/bin/activate &&
diff --git a/ansible/provision-net.yml b/ansible/provision-net.yml
index b61c1e7d5..fca740d8b 100644
--- a/ansible/provision-net.yml
+++ b/ansible/provision-net.yml
@@ -2,8 +2,6 @@
 - name: Ensure provisioning network and subnet are registered in neutron
   # Only required to run on a single host.
   hosts: controllers[0]
-  vars:
-    venv: "{{ ansible_env.PWD }}/shade-venv"
   pre_tasks:
     - name: Validate OpenStack password authentication parameters
       fail:
@@ -20,7 +18,7 @@
 
   roles:
     - role: stackhpc.os-networks
-      os_networks_venv: "{{ venv }}"
+      os_networks_venv: "{{ virtualenv_path }}/shade"
       os_networks_auth_type: "{{ openstack_auth_type }}"
       os_networks_auth: "{{ openstack_auth }}"
       # Network configuration.
diff --git a/ansible/roles/kolla-ansible/defaults/main.yml b/ansible/roles/kolla-ansible/defaults/main.yml
index 5541ec43d..da0dace26 100644
--- a/ansible/roles/kolla-ansible/defaults/main.yml
+++ b/ansible/roles/kolla-ansible/defaults/main.yml
@@ -12,7 +12,7 @@ kolla_ansible_source_url:
 # is 'source'.
 kolla_ansible_source_version:
 
-# Virtualenv directory where Kolla will be installed.
+# Virtualenv directory where Kolla-ansible will be installed.
 kolla_venv: "{{ ansible_env['PWD'] }}/kolla-venv"
 
 # Password to use to encrypt the passwords.yml file.
diff --git a/ansible/seed-introspection-rules.yml b/ansible/seed-introspection-rules.yml
index bb5bf7d22..1759e70a3 100644
--- a/ansible/seed-introspection-rules.yml
+++ b/ansible/seed-introspection-rules.yml
@@ -1,11 +1,9 @@
 ---
 - name: Ensure introspection rules are registered in Bifrost
   hosts: seed
-  vars:
-    venv: "{{ ansible_env.PWD }}/shade-venv"
   roles:
     - role: ironic-inspector-rules
-      ironic_inspector_venv: "{{ venv }}"
+      ironic_inspector_venv: "{{ virtualenv_path }}/shade"
       # No auth required for Bifrost.
       ironic_inspector_auth_type: None
       ironic_inspector_auth: {}
diff --git a/ansible/test-image-centos-cloud.yml b/ansible/test-image-centos-cloud.yml
index 28e019fe6..6d9904e05 100644
--- a/ansible/test-image-centos-cloud.yml
+++ b/ansible/test-image-centos-cloud.yml
@@ -2,7 +2,7 @@
 - name: Ensure CentOS cloud image is registered with Glance
   hosts: controllers[0]
   vars:
-    os_shade_venv: "{{ ansible_env['PWD'] }}/shade-venv"
+    os_shade_venv: "{{ virtualenv_path }}/shade"
   roles:
     - role: stackhpc.os-shade
   tasks:
diff --git a/ansible/test-image.yml b/ansible/test-image.yml
index 9aa6ee97d..5a4ea4238 100644
--- a/ansible/test-image.yml
+++ b/ansible/test-image.yml
@@ -16,7 +16,7 @@
       - grub2
     image_extra_elements: []
     image_elements: "{{ image_base_elements + (image_whole_disk_elements if image_is_whole_disk|bool else image_partition_elements) + image_extra_elements }}"
-    os_shade_venv: "{{ ansible_env['PWD'] }}/shade-venv"
+    os_shade_venv: "{{ virtualenv_path }}/shade"
   roles:
     - role: stackhpc.os-shade
   tasks:
diff --git a/ansible/test-keypair.yml b/ansible/test-keypair.yml
index dd74edbbb..a0a6c28ee 100644
--- a/ansible/test-keypair.yml
+++ b/ansible/test-keypair.yml
@@ -3,7 +3,7 @@
   hosts: controllers[0]
   vars:
     public_key_path: "{{ ssh_public_key_path }}"
-    os_shade_venv: "{{ ansible_env['PWD'] }}/shade-venv"
+    os_shade_venv: "{{ virtualenv_path }}/shade"
   roles:
     - role: stackhpc.os-shade
   tasks:
diff --git a/ansible/test-project.yml b/ansible/test-project.yml
index c93f23334..8a9c26b03 100644
--- a/ansible/test-project.yml
+++ b/ansible/test-project.yml
@@ -2,7 +2,6 @@
 - name: Ensure a test project exists
   hosts: controllers[0]
   vars:
-    venv: "{{ ansible_env.PWD }}/shade-venv"
     # Dict of quotas to set for the test project.
     test_project_quotas:
       cores: -1
@@ -52,7 +51,7 @@
 
   roles:
     - role: stackhpc.os-projects
-      os_projects_venv: "{{ venv }}"
+      os_projects_venv: "{{ virtualenv_path }}/shade"
       os_projects_auth_type: "{{ openstack_auth_type }}"
       os_projects_admin_auth: "{{ openstack_auth }}"
       os_projects:
diff --git a/kayobe/kolla_ansible.py b/kayobe/kolla_ansible.py
index 0e102aacb..9d8c71e4b 100644
--- a/kayobe/kolla_ansible.py
+++ b/kayobe/kolla_ansible.py
@@ -25,7 +25,7 @@ DEFAULT_CONFIG_PATH = "/etc/kolla"
 
 CONFIG_PATH_ENV = "KOLLA_CONFIG_PATH"
 
-DEFAULT_VENV_PATH = "ansible/kolla-venv"
+DEFAULT_VENV_PATH = "/opt/kayobe/venvs/kolla"
 
 VENV_PATH_ENV = "KOLLA_VENV"
 
@@ -54,7 +54,8 @@ def add_args(parser):
                              "values in Kolla Ansible")
     parser.add_argument("--kolla-venv", metavar="VENV", default=default_venv,
                         help="path to virtualenv where Kolla Ansible is "
-                             "installed")
+                             "installed (default=$%s or %s)" %
+                             (VENV_PATH_ENV, DEFAULT_VENV_PATH))
 
 
 def _get_inventory_path(parsed_args, inventory_filename):