Enable delegating all downloads (binaries, images, kubeadm images) (#4420)

* Download to delegate and sync files when download_run_once

* Fail on error after saving container image

* Do not set changed status when downloaded container was up to date

* Only sync containers when they are actually required

Previously, non-required images (pull_required=false as
image existed on target host) were synced to the target
hosts. This failed as the image was not downloaded to
the download_delegate and hence was not available for
syncing.

* Sync containers when only missing on some hosts

* Consider images with multiple repo tags

* Enable kubeadm images pull/syncing with download_delegate

* Use kubeadm images list to pull/sync

'kubeadm config images pull' is replaced by collecting the images
list with 'kubeadm config images list' and using the commonly
used method of pull/syncing the images.

* Ensure containers are downloaded and synced for all hosts

* Fix download/syncing when download_delegate is a kubernetes host
This commit is contained in:
Timoses 2019-05-01 10:10:56 +02:00 committed by Kubernetes Prow Robot
parent e814da1eec
commit d6fd0d2aca
11 changed files with 188 additions and 50 deletions

View file

@ -12,14 +12,14 @@ Kubespray supports several download/upload modes. The default is:
There is also a "pull once, push many" mode as well: There is also a "pull once, push many" mode as well:
* Override the ``download_run_once: True`` to download container images only once * Override the ``download_run_once: True`` to download container images and binaries only once
then push to cluster nodes in batches. The default delegate node then push to cluster nodes in batches. The default delegate node
for pushing images is the first `kube-master`. for pushing is the first `kube-master`.
* If your ansible runner node (aka the admin node) have password-less sudo and * If your ansible runner node (aka the admin node) have password-less sudo and
docker enabled, you may want to define the ``download_localhost: True``, which docker enabled, you may want to define the ``download_localhost: True``, which
makes that node a delegate for pushing images while running the deployment with makes that node a delegate for pushing while running the deployment with
ansible. This maybe the case if cluster nodes cannot access each over via ssh ansible. This may be the case if cluster nodes cannot access each other via ssh
or you want to use local docker images as a cache for multiple clusters. or you want to use local docker images and binaries as a cache for multiple clusters.
Container images and binary files are described by the vars like ``foo_version``, Container images and binary files are described by the vars like ``foo_version``,
``foo_download_url``, ``foo_checksum`` for binaries and ``foo_image_repo``, ``foo_download_url``, ``foo_checksum`` for binaries and ``foo_image_repo``,

View file

@ -321,7 +321,7 @@ downloads:
enabled: true enabled: true
file: true file: true
version: "{{ kubeadm_version }}" version: "{{ kubeadm_version }}"
dest: "{{local_release_dir}}/kubeadm" dest: "{{ local_release_dir }}/kubeadm-{{ kubeadm_version }}-{{ image_arch }}"
sha256: "{{ kubeadm_binary_checksum }}" sha256: "{{ kubeadm_binary_checksum }}"
url: "{{ kubeadm_download_url }}" url: "{{ kubeadm_download_url }}"
unarchive: false unarchive: false
@ -334,7 +334,7 @@ downloads:
enabled: true enabled: true
file: true file: true
version: "{{ kube_version }}" version: "{{ kube_version }}"
dest: "{{ local_release_dir }}/hyperkube" dest: "{{ local_release_dir }}/hyperkube-{{ kube_version }}-{{ image_arch }}"
sha256: "{{ hyperkube_binary_checksum }}" sha256: "{{ hyperkube_binary_checksum }}"
url: "{{ hyperkube_download_url }}" url: "{{ hyperkube_download_url }}"
unarchive: false unarchive: false

View file

@ -1,9 +1,6 @@
--- ---
- name: container_download | Make download decision if pull is required by tag or sha256 - name: container_download | Make download decision if pull is required by tag or sha256
include_tasks: set_docker_image_facts.yml include_tasks: set_docker_image_facts.yml
delegate_to: "{{ download_delegate if download_run_once or omit }}"
delegate_facts: yes
run_once: "{{ download_run_once }}"
when: when:
- download.enabled - download.enabled
- download.container - download.container
@ -18,11 +15,12 @@
until: pull_task_result is succeeded until: pull_task_result is succeeded
retries: 4 retries: 4
delay: "{{ retry_stagger | random + 3 }}" delay: "{{ retry_stagger | random + 3 }}"
changed_when: not 'up to date' in pull_task_result.stdout
when: when:
- download_run_once - download_run_once
- download.enabled - download.enabled
- download.container - download.container
- pull_required|default(download_always_pull) - any_pull_required | default(download_always_pull)
delegate_to: "{{ download_delegate }}" delegate_to: "{{ download_delegate }}"
delegate_facts: yes delegate_facts: yes
run_once: yes run_once: yes
@ -33,6 +31,7 @@
until: pull_task_result is succeeded until: pull_task_result is succeeded
retries: 4 retries: 4
delay: "{{ retry_stagger | random + 3 }}" delay: "{{ retry_stagger | random + 3 }}"
changed_when: not 'up to date' in pull_task_result.stdout
when: when:
- not download_run_once - not download_run_once
- download.enabled - download.enabled

View file

@ -15,7 +15,33 @@
- download.file - download.file
- group_names | intersect(download.groups) | length - group_names | intersect(download.groups) | length
- name: file_download | Download item # As in 'download_container.yml':
# In Ansible 2.4 omitting download delegate is broken. Move back
# to one task in the future.
- name: file_download | Download item (delegate)
get_url:
url: "{{download.url}}"
dest: "{{download.dest}}"
sha256sum: "{{download.sha256 | default(omit)}}"
owner: "{{ download.owner|default(omit) }}"
mode: "{{ download.mode|default(omit) }}"
validate_certs: "{{ download_validate_certs }}"
url_username: "{{ download.username|default(omit) }}"
url_password: "{{ download.password|default(omit) }}"
force_basic_auth: "{{ download.force_basic_auth|default(omit) }}"
register: get_url_result
until: "'OK' in get_url_result.msg or 'file already exists' in get_url_result.msg"
retries: 4
delay: "{{ retry_stagger | default(5) }}"
delegate_to: "{{ download_delegate }}"
when:
- download_run_once
- download.enabled
- download.file
- group_names | intersect(download.groups) | length
run_once: yes
- name: file_download | Download item (all)
get_url: get_url:
url: "{{download.url}}" url: "{{download.url}}"
dest: "{{download.dest}}" dest: "{{download.dest}}"
@ -31,6 +57,7 @@
retries: 4 retries: 4
delay: "{{ retry_stagger | default(5) }}" delay: "{{ retry_stagger | default(5) }}"
when: when:
- not download_run_once
- download.enabled - download.enabled
- download.file - download.file
- group_names | intersect(download.groups) | length - group_names | intersect(download.groups) | length

View file

@ -1,4 +1,22 @@
--- ---
- name: kubeadm | Download kubeadm
include_tasks: "download_file.yml"
vars:
download: "{{ download_defaults | combine(downloads.kubeadm) }}"
when:
- not skip_downloads|default(false)
- downloads.kubeadm.enabled
- name: kubeadm | Sync kubeadm
include_tasks: "sync_file.yml"
vars:
download: "{{ download_defaults | combine(downloads.kubeadm) }}"
when:
- not skip_downloads|default(false)
- downloads.kubeadm.enabled
- download_run_once
- group_names | intersect(download.groups) | length
- name: kubeadm | Create kubeadm config - name: kubeadm | Create kubeadm config
template: template:
src: "kubeadm-images.yaml.j2" src: "kubeadm-images.yaml.j2"
@ -6,7 +24,7 @@
- name: kubeadm | Copy kubeadm binary from download dir - name: kubeadm | Copy kubeadm binary from download dir
synchronize: synchronize:
src: "{{ local_release_dir }}/kubeadm" src: "{{ local_release_dir }}/kubeadm-{{ kubeadm_version }}-{{ image_arch }}"
dest: "{{ bin_dir }}/kubeadm" dest: "{{ bin_dir }}/kubeadm"
compress: no compress: no
perms: yes perms: yes
@ -22,3 +40,33 @@
- name: container_download | download images for kubeadm config images - name: container_download | download images for kubeadm config images
command: "{{ bin_dir }}/kubeadm config images pull --config={{ kube_config_dir }}/kubeadm-images.yaml" command: "{{ bin_dir }}/kubeadm config images pull --config={{ kube_config_dir }}/kubeadm-images.yaml"
when: not download_run_once
- name: container_download | fetch list of kubeadm config images
command: "{{ bin_dir }}/kubeadm config images list --config={{ kube_config_dir }}/kubeadm-images.yaml"
register: result
run_once: true
when: download_run_once
changed_when: false
- vars:
kubeadm_images_list: "{{ result.stdout_lines }}"
set_fact:
kubeadm_image:
key: "kubeadm_{{ (item | regex_replace('^(?:.*\\/)*','')).split(':')[0] }}"
value:
enabled: true
container: true
repo: "{{ item.split(':')[0] }}"
tag: "{{ item.split(':')[1] }}"
groups:
- k8s-cluster
loop: "{{ kubeadm_images_list | flatten(levels=1) }}"
run_once: true
when: download_run_once
register: result_images
- set_fact:
kubeadm_images: "{{ result_images.results | map(attribute='ansible_facts.kubeadm_image') | list | items2dict }}"
run_once: true
when: download_run_once

View file

@ -3,31 +3,36 @@
when: when:
- not skip_downloads|default(false) - not skip_downloads|default(false)
- name: "Download items"
include_tasks: "{{ include_file }}"
vars:
download: "{{ download_defaults | combine(item.value) }}"
include_file: "download_{% if download.container %}container{% else %}file{% endif %}.yml"
with_dict: "{{ downloads }}"
when:
- not skip_downloads|default(false)
- item.value.enabled
- (not (item.value.container|default(False))) or (item.value.container and download_container)
- name: "Sync container"
include_tasks: sync_container.yml
vars:
download: "{{ download_defaults | combine(item.value) }}"
with_dict: "{{ downloads }}"
when:
- not skip_downloads|default(false)
- item.value.enabled
- item.value.container | default(false)
- download_run_once
- group_names | intersect(download.groups) | length
- include_tasks: kubeadm_images.yml - include_tasks: kubeadm_images.yml
when: when:
- kube_version is version('v1.11.0', '>=') - kube_version is version('v1.11.0', '>=')
- not skip_downloads|default(false) - not skip_downloads|default(false)
- inventory_hostname in groups['kube-master'] - inventory_hostname in groups['kube-master']
- set_fact:
kubeadm_images: {}
when:
- kubeadm_images is not defined
- name: "Download items"
include_tasks: "{{ include_file }}"
vars:
download: "{{ download_defaults | combine(item.value) }}"
include_file: "download_{% if download.container %}container{% else %}file{% endif %}.yml"
with_dict: "{{ downloads | combine(kubeadm_images) }}"
when:
- not skip_downloads|default(false)
- item.value.enabled
- (not (item.value.container|default(False))) or (item.value.container and download_container)
- name: "Sync items"
include_tasks: "{{ include_file }}"
vars:
download: "{{ download_defaults | combine(item.value) }}"
include_file: "sync_{% if download.container %}container{% else %}file{% endif %}.yml"
with_dict: "{{ downloads | combine(kubeadm_images) }}"
when:
- not skip_downloads|default(false)
- item.value.enabled
- download_run_once
- group_names | intersect(download.groups) | length

View file

@ -9,7 +9,7 @@
- name: Register docker images info - name: Register docker images info
shell: >- shell: >-
{{ docker_bin_dir }}/docker images -q | xargs {{ docker_bin_dir }}/docker inspect -f "{{ '{{' }} if .RepoTags {{ '}}' }}{{ '{{' }} (index .RepoTags 0) {{ '}}' }}{{ '{{' }} end {{ '}}' }}{{ '{{' }} if .RepoDigests {{ '}}' }},{{ '{{' }} (index .RepoDigests 0) {{ '}}' }}{{ '{{' }} end {{ '}}' }}" | tr '\n' ',' {{ docker_bin_dir }}/docker images -q | xargs -r {{ docker_bin_dir }}/docker inspect -f "{{ '{{' }} if .RepoTags {{ '}}' }}{{ '{{' }} (index .RepoTags) {{ '}}' }}{{ '{{' }} end {{ '}}' }}{{ '{{' }} if .RepoDigests {{ '}}' }},{{ '{{' }} (index .RepoDigests) {{ '}}' }}{{ '{{' }} end {{ '}}' }}" | sed -e 's/^ *\[//g' -e 's/\] *$//g' -e 's/ /\n/g' | tr '\n' ','
no_log: true no_log: true
register: docker_images register: docker_images
failed_when: false failed_when: false
@ -22,6 +22,15 @@
{%- if pull_args in docker_images.stdout.split(',') %}false{%- else -%}true{%- endif -%} {%- if pull_args in docker_images.stdout.split(',') %}false{%- else -%}true{%- endif -%}
when: not download_always_pull when: not download_always_pull
- name: Does any host require container pull?
vars:
hosts_pull_required: "{{ hostvars.values() | map(attribute='pull_required') | select('defined') | list }}"
set_fact:
any_pull_required: "{{ True in hosts_pull_required }}"
run_once: true
changed_when: false
when: not download_always_pull
- name: Check the local digest sha256 corresponds to the given image tag - name: Check the local digest sha256 corresponds to the given image tag
assert: assert:
that: "{{download.repo}}:{{download.tag}} in docker_images.stdout.split(',')" that: "{{download.repo}}:{{download.tag}} in docker_images.stdout.split(',')"

View file

@ -1,9 +1,6 @@
--- ---
- name: container_download | Make download decision if pull is required by tag or sha256 - name: container_download | Make download decision if pull is required by tag or sha256
include: set_docker_image_facts.yml include: set_docker_image_facts.yml
delegate_to: "{{ download_delegate if download_run_once or omit }}"
delegate_facts: no
run_once: "{{ download_run_once }}"
when: when:
- download.enabled - download.enabled
- download.container - download.container
@ -54,6 +51,7 @@
- download.enabled - download.enabled
- download.container - download.container
- download_run_once - download_run_once
- any_pull_required | default(download_always_pull)
tags: tags:
- facts - facts
@ -62,11 +60,12 @@
delegate_to: "{{ download_delegate }}" delegate_to: "{{ download_delegate }}"
delegate_facts: no delegate_facts: no
register: saved register: saved
run_once: true failed_when: saved.stderr != ""
when: when:
- download.enabled - download.enabled
- download.container - download.container
- download_run_once - download_run_once
- any_pull_required | default(download_always_pull)
- (ansible_os_family not in ["CoreOS", "Container Linux by CoreOS"] or download_delegate == "localhost") - (ansible_os_family not in ["CoreOS", "Container Linux by CoreOS"] or download_delegate == "localhost")
- (container_changed or not img.stat.exists) - (container_changed or not img.stat.exists)
@ -82,6 +81,7 @@
- download.enabled - download.enabled
- download.container - download.container
- download_run_once - download_run_once
- any_pull_required | default(download_always_pull)
- ansible_os_family not in ["CoreOS", "Container Linux by CoreOS"] - ansible_os_family not in ["CoreOS", "Container Linux by CoreOS"]
- inventory_hostname == download_delegate - inventory_hostname == download_delegate
- download_delegate != "localhost" - download_delegate != "localhost"
@ -94,9 +94,6 @@
use_ssh_args: "{{ has_bastion | default(false) }}" use_ssh_args: "{{ has_bastion | default(false) }}"
mode: pull mode: pull
private_key: "{{ ansible_ssh_private_key_file }}" private_key: "{{ ansible_ssh_private_key_file }}"
delegate_to: localhost
delegate_facts: no
run_once: true
become: false become: false
when: when:
- download.enabled - download.enabled
@ -113,8 +110,6 @@
dest: "{{ fname }}" dest: "{{ fname }}"
use_ssh_args: "{{ has_bastion | default(false) }}" use_ssh_args: "{{ has_bastion | default(false) }}"
mode: push mode: push
delegate_to: localhost
delegate_facts: no
become: true become: true
register: get_task register: get_task
until: get_task is succeeded until: get_task is succeeded
@ -124,6 +119,7 @@
- download.enabled - download.enabled
- download.container - download.container
- download_run_once - download_run_once
- pull_required|default(download_always_pull)
- (ansible_os_family not in ["CoreOS", "Container Linux by CoreOS"] and - (ansible_os_family not in ["CoreOS", "Container Linux by CoreOS"] and
inventory_hostname != download_delegate or inventory_hostname != download_delegate or
download_delegate == "localhost") download_delegate == "localhost")
@ -137,6 +133,7 @@
- download.enabled - download.enabled
- download.container - download.container
- download_run_once - download_run_once
- pull_required|default(download_always_pull)
- (ansible_os_family not in ["CoreOS", "Container Linux by CoreOS"] and - (ansible_os_family not in ["CoreOS", "Container Linux by CoreOS"] and
inventory_hostname != download_delegate or download_delegate == "localhost") inventory_hostname != download_delegate or download_delegate == "localhost")
tags: tags:

View file

@ -0,0 +1,53 @@
---
- name: file_download | create local download destination directory
file:
path: "{{download.dest|dirname}}"
state: directory
recurse: yes
mode: 0755
delegate_to: localhost
become: false
run_once: true
when:
- download_delegate != "localhost"
- download_run_once
- download.enabled
- download.file
- name: file_download | copy file to ansible host
synchronize:
src: "{{ download.dest }}"
dest: "{{ download.dest }}"
use_ssh_args: "{{ has_bastion | default(false) }}"
mode: pull
run_once: true
become: false
when:
- download.enabled
- download.file
- download_run_once
- ansible_os_family not in ["CoreOS", "Container Linux by CoreOS"]
- inventory_hostname == download_delegate
- download_delegate != "localhost"
- name: file_download | upload file to nodes
synchronize:
src: "{{ download.dest }}"
dest: "{{ download.dest }}"
use_ssh_args: "{{ has_bastion | default(false) }}"
mode: push
become: true
register: get_task
until: get_task is succeeded
retries: 4
delay: "{{ retry_stagger | random + 3 }}"
when:
- download.enabled
- download.file
- download_run_once
- (ansible_os_family not in ["CoreOS", "Container Linux by CoreOS"] and
inventory_hostname != download_delegate or
download_delegate == "localhost")
tags:
- upload
- upgrade

View file

@ -19,7 +19,7 @@
- name: Install | Copy kubectl binary from download dir - name: Install | Copy kubectl binary from download dir
synchronize: synchronize:
src: "{{ local_release_dir }}/hyperkube" src: "{{ local_release_dir }}/hyperkube-{{ kube_version }}-{{ image_arch }}"
dest: "{{ bin_dir }}/kubectl" dest: "{{ bin_dir }}/kubectl"
compress: no compress: no
perms: yes perms: yes

View file

@ -1,7 +1,7 @@
--- ---
- name: install | Copy kubeadm binary from download dir - name: install | Copy kubeadm binary from download dir
synchronize: synchronize:
src: "{{ local_release_dir }}/kubeadm" src: "{{ local_release_dir }}/kubeadm-{{ kubeadm_version }}-{{ image_arch }}"
dest: "{{ bin_dir }}/kubeadm" dest: "{{ bin_dir }}/kubeadm"
compress: no compress: no
perms: yes perms: yes
@ -25,7 +25,7 @@
- name: install | Copy kubelet binary from download dir - name: install | Copy kubelet binary from download dir
synchronize: synchronize:
src: "{{ local_release_dir }}/hyperkube" src: "{{ local_release_dir }}/hyperkube-{{ kube_version }}-{{ image_arch }}"
dest: "{{ bin_dir }}/kubelet" dest: "{{ bin_dir }}/kubelet"
compress: no compress: no
perms: yes perms: yes
@ -48,7 +48,7 @@
- name: install | Copy hyperkube binary from download dir - name: install | Copy hyperkube binary from download dir
synchronize: synchronize:
src: "{{ local_release_dir }}/hyperkube" src: "{{ local_release_dir }}/hyperkube-{{ kube_version }}-{{ image_arch }}"
dest: "{{ bin_dir }}/hyperkube" dest: "{{ bin_dir }}/hyperkube"
compress: no compress: no
perms: yes perms: yes