Skip to content

Commit 00366ab

Browse files
committed
feat: add robust Docker installation with multiple fallback mechanisms
Enhance Docker installation playbook to handle network failures in GitHub Actions: - Add embedded Docker GPG key as ultimate fallback (no network dependency) - Implement 6-layer fallback system: get_url → curl → aria2 → embedded → apt → snap - Configure aggressive retry/timeout settings (120s timeout, 8 retries, 45s delay) - Make aria2 installation optional (ignore_errors) for minimal Ubuntu environments - Add conditional aria2 usage only when successfully installed - Maintain detailed error reporting showing which methods failed/succeeded This resolves GitHub Actions E2E test failures caused by network connectivity issues preventing Docker GPG key downloads from download.docker.com.
1 parent 3fdbc3f commit 00366ab

File tree

1 file changed

+173
-51
lines changed

1 file changed

+173
-51
lines changed

templates/ansible/install-docker.yml

Lines changed: 173 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -37,18 +37,12 @@
3737
# NOTE: APT cache update logic has been moved to update-apt-cache.yml
3838
# Run that playbook first if you need to update the package cache
3939

40-
# Task 0: Detect CI environment to adjust behavior
41-
- name: Detect CI environment
40+
# Task 0: Set robust retry/timeout defaults for all environments
41+
- name: Set network operation defaults
4242
ansible.builtin.set_fact:
43-
is_ci_environment: "{{ ansible_env.GITHUB_ACTIONS is defined or ansible_env.CI is defined }}"
44-
ci_type: "{% if ansible_env.GITHUB_ACTIONS is defined %}github_actions{% elif ansible_env.CI is defined %}generic_ci{% else %}local{% endif %}"
45-
46-
- name: Display environment information
47-
ansible.builtin.debug:
48-
msg: |
49-
Environment: {{ ci_type }}
50-
CI Environment: {{ is_ci_environment }}
51-
Note: CI environments may have network connectivity limitations
43+
network_timeout: 120 # 2 minutes timeout for individual operations
44+
network_retries: 8 # High number of retries for flaky networks
45+
network_delay: 45 # 45 seconds between retries
5246

5347
# Task 1: Install required packages for Docker repository with retries
5448
- name: Install required packages for Docker repository
@@ -68,6 +62,18 @@
6862
until: prereq_packages is succeeded
6963
when: ansible_os_family == "Debian"
7064

65+
# Task 1b: Try to install aria2 (optional - better download utility for flaky networks)
66+
- name: Install aria2 (optional - enhanced download utility)
67+
ansible.builtin.apt:
68+
name:
69+
- aria2
70+
state: present
71+
force_apt_get: true
72+
update_cache: false
73+
register: aria2_install
74+
when: ansible_os_family == "Debian"
75+
ignore_errors: true # Don't fail if aria2 is not available
76+
7177
# Task 2: Add Docker's official GPG key with retries and better error handling
7278
- name: Create keyrings directory
7379
ansible.builtin.file:
@@ -76,61 +82,158 @@
7682
mode: "0755"
7783
when: ansible_os_family == "Debian"
7884

79-
- name: Add Docker's official GPG key (with retries and CI-aware timeouts)
85+
- name: Add Docker's official GPG key (with robust retry/timeout settings)
8086
ansible.builtin.get_url:
8187
url: https://download.docker.com/linux/ubuntu/gpg
8288
dest: /etc/apt/keyrings/docker.asc
8389
mode: "0644"
84-
timeout: "{{ 60 if is_ci_environment else 30 }}"
90+
timeout: "{{ network_timeout }}"
8591
force: true
8692
register: docker_gpg_key
87-
retries: "{{ 5 if is_ci_environment else 3 }}"
88-
delay: "{{ 30 if is_ci_environment else 10 }}"
93+
retries: "{{ network_retries }}"
94+
delay: "{{ network_delay }}"
8995
until: docker_gpg_key is succeeded
9096
when: ansible_os_family == "Debian"
9197
ignore_errors: true
9298

93-
# Fallback: Use curl to download GPG key if get_url fails (especially for CI)
94-
- name: Fallback - Download Docker GPG key with curl (CI-optimized)
99+
# Fallback: Use curl to download GPG key if get_url fails
100+
- name: Fallback - Download Docker GPG key with curl (high retry/timeout)
95101
ansible.builtin.shell: |
96102
curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
97-
--connect-timeout {{ 60 if is_ci_environment else 30 }} \
98-
--max-time {{ 180 if is_ci_environment else 60 }} \
99-
--retry {{ 5 if is_ci_environment else 2 }} \
100-
--retry-delay {{ 30 if is_ci_environment else 15 }} \
103+
--connect-timeout {{ network_timeout }} \
104+
--max-time {{ network_timeout * 2 }} \
105+
--retry {{ network_retries }} \
106+
--retry-delay {{ network_delay }} \
101107
--retry-connrefused \
102108
-o /etc/apt/keyrings/docker.asc
103109
chmod 644 /etc/apt/keyrings/docker.asc
104110
register: docker_gpg_curl
105111
when:
106112
- ansible_os_family == "Debian"
107113
- docker_gpg_key is failed
108-
retries: "{{ 3 if is_ci_environment else 2 }}"
109-
delay: "{{ 45 if is_ci_environment else 15 }}"
114+
retries: "{{ network_retries // 2 }}"
115+
delay: "{{ network_delay }}"
110116
until: docker_gpg_curl.rc == 0
111117
ignore_errors: true
112118

113-
# Final fallback: Skip Docker installation if GPG key cannot be obtained
114-
- name: Check if Docker GPG key exists
119+
# Enhanced fallback: Use aria2 for more robust downloading (if available)
120+
- name: Enhanced fallback - Download Docker GPG key with aria2 (network-resilient)
121+
ansible.builtin.shell: |
122+
aria2c --file-allocation=none \
123+
--retry-wait={{ network_delay }} \
124+
--max-tries={{ network_retries }} \
125+
--max-connection-per-server=1 \
126+
--split=1 \
127+
--timeout={{ network_timeout }} \
128+
--connect-timeout={{ network_timeout // 2 }} \
129+
--dir=/etc/apt/keyrings \
130+
--out=docker.asc \
131+
--user-agent="aria2/1.36.0" \
132+
https://download.docker.com/linux/ubuntu/gpg
133+
chmod 644 /etc/apt/keyrings/docker.asc
134+
register: docker_gpg_aria2
135+
when:
136+
- ansible_os_family == "Debian"
137+
- docker_gpg_key is failed
138+
- docker_gpg_curl is failed
139+
- aria2_install is succeeded # Only try aria2 if it was successfully installed
140+
retries: 3
141+
delay: "{{ network_delay }}"
142+
until: docker_gpg_aria2.rc == 0
143+
ignore_errors: true
144+
145+
# Ultimate fallback: Use embedded GPG key (no network required)
146+
- name: Ultimate fallback - Create Docker GPG key from embedded content (offline)
147+
ansible.builtin.copy:
148+
content: |
149+
-----BEGIN PGP PUBLIC KEY BLOCK-----
150+
151+
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
208+
YT90qFF93M3v01BbxP+EIY2/9tiIPbrd
209+
=0YYh
210+
-----END PGP PUBLIC KEY BLOCK-----
211+
dest: /etc/apt/keyrings/docker.asc
212+
mode: "0644"
213+
register: docker_gpg_embedded
214+
when:
215+
- ansible_os_family == "Debian"
216+
- docker_gpg_key is failed
217+
- docker_gpg_curl is failed
218+
- docker_gpg_aria2 is failed
219+
ignore_errors: true
220+
221+
# Final check: Verify if Docker GPG key exists after all attempts
222+
- name: Check if Docker GPG key exists (after all fallback attempts)
115223
ansible.builtin.stat:
116224
path: /etc/apt/keyrings/docker.asc
117-
register: docker_gpg_exists
225+
register: docker_gpg_final_check
118226
when: ansible_os_family == "Debian"
119227

120228
- name: Warning about Docker GPG key failure
121229
ansible.builtin.debug:
122230
msg: |
123231
⚠️ WARNING: Could not download Docker GPG key due to network issues.
124-
{% if is_ci_environment %}
125-
This is a known limitation in CI environments, particularly GitHub Actions.
126-
See: https://github.com/actions/runner-images/issues/2890
127-
{% else %}
128232
This may be due to network connectivity issues or firewall restrictions.
129-
{% endif %}
130-
Docker installation will be skipped but the playbook will continue.
233+
Using embedded GPG key as fallback to continue installation.
131234
when:
132235
- ansible_os_family == "Debian"
133-
- not docker_gpg_exists.stat.exists
236+
- not docker_gpg_final_check.stat.exists
134237

135238
# Task 3: Add Docker repository (only if GPG key exists)
136239
- name: Add Docker repository
@@ -141,7 +244,7 @@
141244
update_cache: true # Need to update cache after adding new repository
142245
when:
143246
- ansible_os_family == "Debian"
144-
- docker_gpg_exists.stat.exists
247+
- docker_gpg_final_check.stat.exists
145248
register: docker_repo_added
146249

147250
# Task 4: Install Docker packages with retries (only if repository was added)
@@ -161,7 +264,7 @@
161264
until: docker_install is succeeded
162265
when:
163266
- ansible_os_family == "Debian"
164-
- docker_gpg_exists.stat.exists
267+
- docker_gpg_final_check.stat.exists
165268
- docker_repo_added is succeeded
166269

167270
# Alternative: Try to install Docker from default repositories if GPG/repo setup failed
@@ -176,7 +279,19 @@
176279
register: docker_fallback_install
177280
when:
178281
- ansible_os_family == "Debian"
179-
- not docker_gpg_exists.stat.exists
282+
- not docker_gpg_final_check.stat.exists
283+
ignore_errors: true
284+
285+
# Snap fallback: Install Docker via snap (more likely to work in minimal containers)
286+
- name: Ultimate fallback - Install Docker via snap (container-friendly)
287+
ansible.builtin.snap:
288+
name: docker
289+
state: present
290+
register: docker_snap_install
291+
when:
292+
- ansible_os_family == "Debian"
293+
- not docker_gpg_final_check.stat.exists
294+
- docker_fallback_install is failed
180295
ignore_errors: true
181296

182297
# Task 5: Start and enable Docker service (if Docker was installed)
@@ -187,21 +302,30 @@
187302
enabled: true
188303
when: docker_install is succeeded or docker_fallback_install is succeeded
189304

305+
# Task 5b: Start snap Docker service (if installed via snap)
306+
- name: Start and enable snap Docker service
307+
ansible.builtin.systemd:
308+
name: snap.docker.dockerd
309+
state: started
310+
enabled: true
311+
when: docker_snap_install is succeeded
312+
ignore_errors: true
313+
190314
# Task 6: Add user to docker group (for non-root Docker usage) (if Docker was installed)
191315
- name: Add user to docker group
192316
ansible.builtin.user:
193317
name: "{{ ansible_user }}"
194318
groups: docker
195319
append: true
196320
register: user_added_to_docker_group
197-
when: docker_install is succeeded or docker_fallback_install is succeeded
321+
when: docker_install is succeeded or docker_fallback_install is succeeded or docker_snap_install is succeeded
198322

199323
# Task 7: Verify Docker installation (if Docker was installed)
200324
- name: Verify Docker installation
201325
ansible.builtin.command: docker --version
202326
register: docker_version
203327
changed_when: false
204-
when: docker_install is succeeded or docker_fallback_install is succeeded
328+
when: docker_install is succeeded or docker_fallback_install is succeeded or docker_snap_install is succeeded
205329
ignore_errors: true
206330

207331
# Task 8: Display Docker version (if Docker was installed)
@@ -216,20 +340,20 @@
216340
- name: Display Docker installation status
217341
ansible.builtin.debug:
218342
msg: |
219-
⚠️ Docker installation was skipped due to network connectivity issues.
220-
{% if is_ci_environment %}
221-
This is a known issue with {{ ci_type }} environments - see:
222-
https://github.com/actions/runner-images/issues/2890
343+
⚠️ Docker installation failed after trying multiple methods:
344+
1. Official Docker repository (get_url) - {{ 'FAILED' if docker_gpg_key is failed else 'SKIPPED' }}
345+
2. Curl download fallback - {{ 'FAILED' if docker_gpg_curl is failed else 'SKIPPED' }}
346+
3. Aria2 download fallback - {{ 'FAILED' if docker_gpg_aria2 is failed else 'SKIPPED' if aria2_install is succeeded else 'NOT_AVAILABLE' }}
347+
4. Embedded GPG key fallback - {{ 'FAILED' if docker_gpg_embedded is failed else 'SKIPPED' }}
348+
5. Default apt repositories - {{ 'FAILED' if docker_fallback_install is failed else 'SKIPPED' }}
349+
6. Snap installation - {{ 'FAILED' if docker_snap_install is failed else 'SKIPPED' }}
223350
224-
The playbook completed successfully despite this limitation.
225-
In production environments, network connectivity should be stable.
226-
{% else %}
227-
This may be due to firewall restrictions or temporary network issues.
228-
Please check network connectivity and try again.
229-
{% endif %}
351+
This may be due to network connectivity or repository access issues.
352+
All available fallback methods have been attempted.
230353
when:
231354
- docker_install is skipped or docker_install is failed
232355
- docker_fallback_install is skipped or docker_fallback_install is failed
356+
- docker_snap_install is skipped or docker_snap_install is failed
233357

234358
# Task 10: Test Docker with hello-world (optional verification) (if Docker was installed)
235359
- name: Test Docker with hello-world container
@@ -258,9 +382,7 @@
258382
Alternatively, you can use 'newgrp docker' to activate the group membership in the current session.
259383
260384
NOTE: If you need to update the APT cache, run the update-apt-cache.yml playbook first.
261-
{% if is_ci_environment %}
262-
CI Environment Note: This playbook is designed to handle network limitations gracefully.
263-
{% endif %}
385+
This playbook uses robust retry/timeout settings optimized for unreliable networks.
264386
when: user_added_to_docker_group is changed
265387

266388
# Handlers section - tasks that run when triggered by other tasks

0 commit comments

Comments
 (0)