diff --git a/ansible/group_vars/all.yml b/ansible/group_vars/all.yml
index 8dca5ca99d418f66747298dbc4e22e54414c5736..9b5039a3d5583c06bf9de1a876b27bc45c58ce90 100644
--- a/ansible/group_vars/all.yml
+++ b/ansible/group_vars/all.yml
@@ -361,6 +361,9 @@ openstack_logging_debug: "False"
 
 openstack_region_name: "RegionOne"
 
+# Variable defined the pin_release_version, apply for rolling upgrade process
+openstack_previous_release_name: "queens"
+
 # A list of policy file formats that are supported by Oslo.policy
 supported_policy_format_list:
   - policy.yaml
diff --git a/ansible/roles/ironic/defaults/main.yml b/ansible/roles/ironic/defaults/main.yml
index 64a76e13c47e40db9b2b877e4cc620e68b4c4860..c21b23897ff76658fe9fa0e20fa1833e90ddb9ef 100644
--- a/ansible/roles/ironic/defaults/main.yml
+++ b/ansible/roles/ironic/defaults/main.yml
@@ -143,7 +143,7 @@ ironic_dnsmasq_boot_file: "{% if enable_ironic_ipxe | bool %}undionly.kpxe{% els
 ironic_cleaning_network:
 ironic_console_serial_speed: "115200n8"
 ironic_ipxe_url: http://{{ api_interface_address }}:{{ ironic_ipxe_port }}
-
+ironic_enable_rolling_upgrade: "yes"
 
 ####################
 ## Kolla
diff --git a/ansible/roles/ironic/handlers/main.yml b/ansible/roles/ironic/handlers/main.yml
index 0fb1aa1538abc5d7a70ad9706470b17405bbc650..15ded2fafbd590cc77e5f4670aff9d5e7bbca4be 100644
--- a/ansible/roles/ironic/handlers/main.yml
+++ b/ansible/roles/ironic/handlers/main.yml
@@ -1,18 +1,19 @@
 ---
-- name: Restart ironic-api container
+- name: Restart ironic-conductor container
   vars:
-    service_name: "ironic-api"
+    service_name: "ironic-conductor"
     service: "{{ ironic_services[service_name] }}"
     config_json: "{{ ironic_config_jsons.results|selectattr('item.key', 'equalto', service_name)|first }}"
     ironic_conf: "{{ ironic_confs.results|selectattr('item.key', 'equalto', service_name)|first }}"
     policy_json: "{{ ironic_policy_jsons.results|selectattr('item.key', 'equalto', service_name)|first }}"
-    ironic_api_container: "{{ check_ironic_containers.results|selectattr('item.key', 'equalto', service_name)|first }}"
+    ironic_conductor_container: "{{ check_ironic_containers.results|selectattr('item.key', 'equalto', service_name)|first }}"
   become: true
   kolla_docker:
     action: "recreate_or_restart_container"
     common_options: "{{ docker_common_options }}"
     name: "{{ service.container_name }}"
     image: "{{ service.image }}"
+    privileged: "{{ service.privileged | default(False) }}"
     volumes: "{{ service.volumes|reject('equalto', '')|list }}"
   when:
     - kolla_action != "config"
@@ -21,23 +22,22 @@
     - config_json.changed | bool
       or ironic_conf.changed | bool
       or policy_json.changed | bool
-      or ironic_api_container.changed | bool
+      or ironic_conductor_container.changed | bool
 
-- name: Restart ironic-conductor container
+- name: Restart ironic-api container
   vars:
-    service_name: "ironic-conductor"
+    service_name: "ironic-api"
     service: "{{ ironic_services[service_name] }}"
     config_json: "{{ ironic_config_jsons.results|selectattr('item.key', 'equalto', service_name)|first }}"
     ironic_conf: "{{ ironic_confs.results|selectattr('item.key', 'equalto', service_name)|first }}"
     policy_json: "{{ ironic_policy_jsons.results|selectattr('item.key', 'equalto', service_name)|first }}"
-    ironic_conductor_container: "{{ check_ironic_containers.results|selectattr('item.key', 'equalto', service_name)|first }}"
+    ironic_api_container: "{{ check_ironic_containers.results|selectattr('item.key', 'equalto', service_name)|first }}"
   become: true
   kolla_docker:
     action: "recreate_or_restart_container"
     common_options: "{{ docker_common_options }}"
     name: "{{ service.container_name }}"
     image: "{{ service.image }}"
-    privileged: "{{ service.privileged | default(False) }}"
     volumes: "{{ service.volumes|reject('equalto', '')|list }}"
   when:
     - kolla_action != "config"
@@ -46,7 +46,7 @@
     - config_json.changed | bool
       or ironic_conf.changed | bool
       or policy_json.changed | bool
-      or ironic_conductor_container.changed | bool
+      or ironic_api_container.changed | bool
 
 - name: Restart ironic-inspector container
   vars:
diff --git a/ansible/roles/ironic/tasks/bootstrap_service.yml b/ansible/roles/ironic/tasks/bootstrap_service.yml
index fa1a6df65f541fb6c8ce395036e33e62ea5d5ec1..cbb27021ca4ff40d0322c87583404e27754e0052 100644
--- a/ansible/roles/ironic/tasks/bootstrap_service.yml
+++ b/ansible/roles/ironic/tasks/bootstrap_service.yml
@@ -2,14 +2,18 @@
 - name: Running Ironic bootstrap container
   vars:
     ironic_api: "{{ ironic_services['ironic-api'] }}"
+    bootstrap_environment:
+      KOLLA_BOOTSTRAP:
+      KOLLA_CONFIG_STRATEGY: "{{ config_strategy }}"
+    upgrade_environment:
+      KOLLA_UPGRADE:
+      KOLLA_CONFIG_STRATEGY: "{{ config_strategy }}"
   become: true
   kolla_docker:
     action: "start_container"
     common_options: "{{ docker_common_options }}"
     detach: False
-    environment:
-      KOLLA_BOOTSTRAP:
-      KOLLA_CONFIG_STRATEGY: "{{ config_strategy }}"
+    environment: "{{ upgrade_environment if ironic_enable_rolling_upgrade|bool else bootstrap_environment }}"
     image: "{{ ironic_api.image }}"
     labels:
       BOOTSTRAP:
diff --git a/ansible/roles/ironic/tasks/legacy_upgrade.yml b/ansible/roles/ironic/tasks/legacy_upgrade.yml
new file mode 100644
index 0000000000000000000000000000000000000000..c38db1adf4b3fb3aebd798df843ba45d1603209f
--- /dev/null
+++ b/ansible/roles/ironic/tasks/legacy_upgrade.yml
@@ -0,0 +1,7 @@
+---
+- include: config.yml
+
+- include: bootstrap_service.yml
+
+- name: Flush handlers
+  meta: flush_handlers
diff --git a/ansible/roles/ironic/tasks/rolling_upgrade.yml b/ansible/roles/ironic/tasks/rolling_upgrade.yml
new file mode 100644
index 0000000000000000000000000000000000000000..f77d6060d858c4000d6411468fe61d2d17dfc18c
--- /dev/null
+++ b/ansible/roles/ironic/tasks/rolling_upgrade.yml
@@ -0,0 +1,44 @@
+---
+- include: pull.yml
+
+# Pin release version
+- include: config.yml
+  vars:
+    pin_release_version: "{{ openstack_previous_release_name }}"
+
+- include: bootstrap_service.yml
+
+# TODO(donghm): Flush_handlers to restart ironic services
+# should be run in serial nodes to decrease downtime. Update when
+# the module ansible strategy for rolling upgrade is finished.
+
+# Restart ironic services with pinned release version
+- name: Flush handlers
+  meta: flush_handlers
+
+# Unpin version
+- include: config.yml
+
+# Restart ironic services with unpinned release version
+- name: Flush handlers
+  meta: flush_handlers
+
+- name: Running Ironic online data migration
+  vars:
+    ironic_api: "{{ ironic_services['ironic-api'] }}"
+  kolla_docker:
+    action: "start_container"
+    common_options: "{{ docker_common_options }}"
+    detach: False
+    environment:
+      KOLLA_OSM:
+      KOLLA_CONFIG_STRATEGY: "{{ config_strategy }}"
+    image: "{{ ironic_api.image }}"
+    labels:
+      BOOTSTRAP:
+    name: "bootstrap_ironic"
+    restart_policy: "never"
+    volumes: "{{ ironic_api.volumes }}"
+  run_once: True
+  delegate_to: "{{ groups[ironic_api.group][0] }}"
+  when: inventory_hostname in groups[ironic_api.group]
diff --git a/ansible/roles/ironic/tasks/upgrade.yml b/ansible/roles/ironic/tasks/upgrade.yml
index 62e5323fdd550389b71ce9aff0cad233317e7624..3533e93670215e5f1db428b851b3451d0890ba07 100644
--- a/ansible/roles/ironic/tasks/upgrade.yml
+++ b/ansible/roles/ironic/tasks/upgrade.yml
@@ -1,12 +1,6 @@
 ---
-- include: register.yml
-  when: enable_keystone | bool and
-        (inventory_hostname in groups['ironic-api'] or
-        inventory_hostname in groups['ironic-inspector'])
+- include: rolling_upgrade.yml
+  when: ironic_enable_rolling_upgrade | bool
 
-- include: config.yml
-
-- include: bootstrap_service.yml
-
-- name: Flush handlers
-  meta: flush_handlers
+- include: legacy_upgrade.yml
+  when: not ironic_enable_rolling_upgrade | bool
diff --git a/ansible/roles/ironic/templates/ironic.conf.j2 b/ansible/roles/ironic/templates/ironic.conf.j2
index b8f71897c89edc656a9cbc6387edf9eb40c515aa..4033a098ccf1023a476dfe5a8a5c7f059b91d874 100644
--- a/ansible/roles/ironic/templates/ironic.conf.j2
+++ b/ansible/roles/ironic/templates/ironic.conf.j2
@@ -15,6 +15,10 @@ log_dir = /var/log/kolla/ironic
 
 transport_url = {{ rpc_transport_url }}
 
+{% if pin_release_version is defined %}
+pin_release_version = {{ pin_release_version }}
+{% endif %}
+
 [oslo_messaging_notifications]
 transport_url = {{ notify_transport_url }}
 
diff --git a/etc/kolla/globals.yml b/etc/kolla/globals.yml
index 33b2c2f63ed134b490aa9944025a86c5fac489dc..8f091b6406412e28dd2be51440a6d930526bf810 100644
--- a/etc/kolla/globals.yml
+++ b/etc/kolla/globals.yml
@@ -421,6 +421,13 @@ ironic_dnsmasq_dhcp_range:
 # PXE bootloader file for Ironic Inspector, relative to /tftpboot.
 #ironic_dnsmasq_boot_file: "pxelinux.0"
 
+# Configure ironic upgrade option, due to currently kolla support
+# two upgrade ways for ironic: legacy_upgrade and rolling_upgrade
+# The variable "ironic_enable_rolling_upgrade: yes" is meaning legacy_upgrade
+# were enabled and opposite
+# Rolling upgrade were enable by default
+#ironic_enable_rolling_upgrade: "yes"
+
 ######################################
 # Manila - Shared File Systems Options
 ######################################
diff --git a/releasenotes/notes/implement-ironic-rolling-upgrade-c45536fe4814212e.yaml b/releasenotes/notes/implement-ironic-rolling-upgrade-c45536fe4814212e.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..8be7841dd74343da9894097be8d4cce8a262eece
--- /dev/null
+++ b/releasenotes/notes/implement-ironic-rolling-upgrade-c45536fe4814212e.yaml
@@ -0,0 +1,5 @@
+---
+features:
+  - |
+    Implement Ironic rolling upgrade logic, enabled by default at
+    ironic_enable_rolling_upgrade: "yes" in etc/kolla/globals.yml file.