diff --git a/.zuul.yaml b/.zuul.yaml
index 2deb769d30..2ef23c89ff 100644
--- a/.zuul.yaml
+++ b/.zuul.yaml
@@ -16,19 +16,24 @@
       - name: compute1
         label: ubuntu-xenial
     groups:
+      # Node where tests are executed and test results collected
       - name: tempest
         nodes:
           - controller
+      # Nodes running the compute service
       - name: compute
         nodes:
           - controller
           - compute1
+      # Nodes that are not the controller
       - name: subnode
         nodes:
           - compute1
+      # Switch node for multinode networking setup
       - name: switch
         nodes:
           - controller
+      # Peer nodes for multinode networking setup
       - name: peers
         nodes:
           - compute1
@@ -45,7 +50,7 @@
       all single Devstack jobs, single or multinode.
       Variables are defined in job.vars, which is what is then used by single
       node jobs and by multi node jobs for the controller, as well as in
-      job.group-vars.peers, which is what is used by multi node jobs for peer
+      job.group-vars.peers, which is what is used by multi node jobs for subnode
       nodes (everything but the controller).
     required-projects:
       - openstack-dev/devstack
@@ -76,7 +81,6 @@
         # from the location below for all the CI jobs.
         ETCD_DOWNLOAD_URL: http://tarballs.openstack.org/etcd/
       devstack_services:
-        # Ignore base set of services setup by test-matrix.
         # Ignore any default set by devstack. Emit a "disable_all_services".
         base: false
       zuul_copy_output:
@@ -119,7 +123,7 @@
         localrc: True
         stackenv: True
     group-vars:
-      peers:
+      subnode:
         devstack_localrc:
           DATABASE_PASSWORD: secretdatabase
           RABBIT_PASSWORD: secretrabbit
@@ -156,7 +160,9 @@
     name: devstack
     parent: devstack-base
     description: |
-      Single or multi node devstack job for integration gate.
+      Base devstack job for integration gate.
+
+      This base job can be used for single node and multinode devstack jobs.
     nodeset: openstack-single-node
     required-projects:
       - openstack/cinder
@@ -178,6 +184,15 @@
         NOVA_VNC_ENABLED: true
         VNCSERVER_LISTEN: 0.0.0.0
         VNCSERVER_PROXYCLIENT_ADDRESS: "{{ hostvars[inventory_hostname]['nodepool']['private_ipv4'] }}"
+        # Multinode specific settings
+        SERVICE_HOST: "{{ hostvars['controller']['nodepool']['private_ipv4'] }}"
+        HOST_IP: "{{ hostvars['controller']['nodepool']['private_ipv4'] }}"
+        PUBLIC_BRIDGE_MTU: "{{ external_bridge_mtu }}"
+      devstack_localconf:
+        post-config:
+          $NEUTRON_CONF:
+            DEFAULT:
+              global_physnet_mtu: "{{ external_bridge_mtu }}"
       devstack_services:
         # Core services enabled for this branch.
         # This list replaces the test-matrix.
@@ -254,16 +269,25 @@
           # Test matrix emits ceilometer but ceilomenter is not installed in the
           # integrated gate, so specifying the services has not effect.
           # ceilometer-*: false
+        devstack_localrc:
+          # Multinode specific settings
+          HOST_IP: "{{ hostvars[inventory_hostname]['nodepool']['private_ipv4'] }}"
+          SERVICE_HOST: "{{ hostvars['controller']['nodepool']['private_ipv4'] }}"
+          PUBLIC_BRIDGE_MTU: "{{ external_bridge_mtu }}"
+          # Subnode specific settings
+          DATABASE_TYPE: mysql
+          GLANCE_HOSTPORT: "{{ hostvars['controller']['nodepool']['private_ipv4'] }}:9292"
+          Q_HOST: "{{ hostvars['controller']['nodepool']['private_ipv4'] }}"
+          RABBIT_HOST: "{{ hostvars['controller']['nodepool']['private_ipv4'] }}"
+          DATABASE_HOST: "{{ hostvars['controller']['nodepool']['private_ipv4'] }}"
 
 - job:
     name: devstack-multinode
-    parent: devstack-base
-    description: Base devstack multinode job
+    parent: devstack
     nodeset: openstack-two-node
-    # NOTE(andreaf) The multinode job is useful to see the setup of different
-    # services on different nodes, however the subnode configuration is not
-    # ready yet. Until then this job should stay non-voting.
-    voting: false
+    description: |
+      Simple multinode test to verify multinode functionality on devstack side.
+      This is not meant to be used as a parent job.
 
 - job:
     name: devstack-tox-base
diff --git a/playbooks/devstack.yaml b/playbooks/devstack.yaml
index ede8382632..93d19f1c7a 100644
--- a/playbooks/devstack.yaml
+++ b/playbooks/devstack.yaml
@@ -1,3 +1,3 @@
 - hosts: all
   roles:
-    - run-devstack
+    - orchestrate-devstack
diff --git a/playbooks/pre.yaml b/playbooks/pre.yaml
index 6681fb20a5..4689a6354f 100644
--- a/playbooks/pre.yaml
+++ b/playbooks/pre.yaml
@@ -1,15 +1,25 @@
-- hosts: controller
-  roles:
-    - role: test-matrix
-      test_matrix_role: primary 
-
-- hosts: subnode
-  roles:
-    - role: test-matrix
-      test_matrix_role: subnode
-
 - hosts: all
+  pre_tasks:
+    - name: Gather minimum local MTU
+      set_fact:
+        local_mtu: >
+          {% set mtus = [] -%}
+          {% for interface in ansible_interfaces -%}
+            {% set interface_variable = 'ansible_' + interface -%}
+            {% if interface_variable in hostvars[inventory_hostname] -%}
+              {% set _ = mtus.append(hostvars[inventory_hostname][interface_variable]['mtu']|int) -%}
+            {% endif -%}
+          {% endfor -%}
+          {{- mtus|min -}}
+    - name: Calculate external_bridge_mtu
+      # 50 bytes is overhead for vxlan (which is greater than GRE
+      # allowing us to use either overlay option with this MTU.
+      # TODO(andreaf) This should work, but it may have to be reconcilied with
+      # the MTU setting used by the multinode setup roles in multinode pre.yaml
+      set_fact:
+        external_bridge_mtu: "{{ local_mtu | int - 50 }}"
   roles:
+    - test-matrix
     - configure-swap
     - setup-stack-user
     - setup-tempest-user
diff --git a/roles/orchestrate-devstack/README.rst b/roles/orchestrate-devstack/README.rst
new file mode 100644
index 0000000000..7803ee4d74
--- /dev/null
+++ b/roles/orchestrate-devstack/README.rst
@@ -0,0 +1,24 @@
+Orchestrate a devstack
+
+Runs devstack in a multinode scenario, with one controller node
+and a group of subnodes.
+
+The reason for this role is so that jobs in other repository may
+run devstack in their plays with no need for re-implementing the
+orchestration logic.
+
+The "run-devstack" role is available to run devstack with no
+orchestration.
+
+This role sets up the controller and CA first, it then pushes CA
+data to sub-nodes and run devstack there. The only requirement for
+this role is for the controller inventory_hostname to be "controller"
+and for all sub-nodes to be defined in a group called "subnode".
+
+
+**Role Variables**
+
+.. zuul:rolevar:: devstack_base_dir
+   :default: /opt/stack
+
+   The devstack base directory.
diff --git a/roles/orchestrate-devstack/defaults/main.yaml b/roles/orchestrate-devstack/defaults/main.yaml
new file mode 100644
index 0000000000..fea05c8146
--- /dev/null
+++ b/roles/orchestrate-devstack/defaults/main.yaml
@@ -0,0 +1 @@
+devstack_base_dir: /opt/stack
diff --git a/roles/orchestrate-devstack/tasks/main.yaml b/roles/orchestrate-devstack/tasks/main.yaml
new file mode 100644
index 0000000000..12db58c520
--- /dev/null
+++ b/roles/orchestrate-devstack/tasks/main.yaml
@@ -0,0 +1,38 @@
+- name: Run devstack on the controller
+  include_role:
+    name: run-devstack
+  when: inventory_hostname == 'controller'
+
+- name: Setup devstack on sub-nodes
+  block:
+
+  - name: Sync CA data to subnodes (when any)
+    # Only do this if the tls-proxy service is defined and enabled
+    include_role:
+      name: sync-devstack-data
+    when: devstack_services['tls-proxy']|default(false)
+
+  - name: Run devstack on the sub-nodes
+    include_role:
+      name: run-devstack
+    when: inventory_hostname in groups['subnode']
+
+  - name: Discover hosts
+    # Discovers compute nodes (subnodes) and maps them to cells. Only run
+    # on the controller node.
+    # NOTE(mriedem): We want to remove this if/when nova supports
+    # auto-registration of computes with cells, but that's not happening in
+    # Ocata.
+    # NOTE(andreaf) This is taken (NOTE included) from the discover_hosts
+    # function in devstack gate. Since this is now in devstack, which is
+    # branched, we know that the discover_hosts tool exists.
+    become: true
+    become_user: stack
+    shell: ./tools/discover_hosts.sh
+    args:
+      chdir: "{{ devstack_base_dir }}/devstack"
+    when: inventory_hostname == 'controller'
+
+  when:
+    - '"controller" in hostvars'
+    - '"subnode" in groups'
diff --git a/roles/sync-devstack-data/README.rst b/roles/sync-devstack-data/README.rst
new file mode 100644
index 0000000000..500e8cccc4
--- /dev/null
+++ b/roles/sync-devstack-data/README.rst
@@ -0,0 +1,12 @@
+Sync devstack data for multinode configurations
+
+Sync any data files which include certificates to be used if TLS is enabled.
+This role must be executed on the controller and it pushes data to all
+subnodes.
+
+**Role Variables**
+
+.. zuul:rolevar:: devstack_base_dir
+   :default: /opt/stack
+
+   The devstack base directory.
diff --git a/roles/sync-devstack-data/defaults/main.yaml b/roles/sync-devstack-data/defaults/main.yaml
new file mode 100644
index 0000000000..fea05c8146
--- /dev/null
+++ b/roles/sync-devstack-data/defaults/main.yaml
@@ -0,0 +1 @@
+devstack_base_dir: /opt/stack
diff --git a/roles/sync-devstack-data/tasks/main.yaml b/roles/sync-devstack-data/tasks/main.yaml
new file mode 100644
index 0000000000..46000159d4
--- /dev/null
+++ b/roles/sync-devstack-data/tasks/main.yaml
@@ -0,0 +1,48 @@
+- name: Ensure the data folder exists
+  become: true
+  file:
+    path: "{{ devstack_base_dir }}/data"
+    state: directory
+    owner: stack
+    group: stack
+    mode: 0755
+  when: 'inventory_hostname in groups["subnode"]|default([])'
+
+- name: Ensure the CA folder exists
+  become: true
+  file:
+    path: "{{ devstack_base_dir }}/data/CA"
+    state: directory
+    owner: stack
+    group: stack
+    mode: 0755
+  when: 'inventory_hostname in groups["subnode"]|default([])'
+
+- name: Pull the CA certificate and folder
+  become: true
+  synchronize:
+    src: "{{ item }}"
+    dest: "{{ zuul.executor.work_root }}/{{ item | basename }}"
+    mode: pull
+  with_items:
+    - "{{ devstack_base_dir }}/data/ca-bundle.pem"
+    - "{{ devstack_base_dir }}/data/CA"
+  when: inventory_hostname == 'controller'
+
+- name: Push the CA certificate
+  become: true
+  become_user: stack
+  synchronize:
+    src: "{{ zuul.executor.work_root }}/ca-bundle.pem"
+    dest: "{{ devstack_base_dir }}/data/ca-bundle.pem"
+    mode: push
+  when: 'inventory_hostname in groups["subnode"]|default([])'
+
+- name: Push the CA folder
+  become: true
+  become_user: stack
+  synchronize:
+    src: "{{ zuul.executor.work_root }}/CA/"
+    dest: "{{ devstack_base_dir }}/data/"
+    mode: push
+  when: 'inventory_hostname in groups["subnode"]|default([])'