[IMP] runbot: pull docker image only when needed

When a lot of Docker images are updated at the same time, all runbot
hosts will try to pull them at the same moment.

With this commit, only the images marked as `always_pull` will be pulled
and if one of them takes too much time to be pulled, we let the host
make another loop turn before continuing the images pull.

Finally, if a host uses the Docker registry, it will pull the remote
image when running a step, that way the image will be pulled
automatically if needed.
This commit is contained in:
Christophe Monniez 2024-09-06 15:41:44 +02:00 committed by xdo
parent b60d54d4ea
commit 3ae3f6dff3
6 changed files with 22 additions and 3 deletions

View File

@ -166,7 +166,7 @@ def _docker_pull(image_tag):
"""Pull a docker image from a registry.
:param image_tag: the full image tag, including the registry host
e.g.: `dockerhub.runbot102.odoo.com/odoo:PureNobleTest`
:return: tuple(success, image) where success is a boolean and image a Docker image object or None in case of failure
:return: DockerMager.result dict
"""
with DockerManager(image_tag) as dm:
for chunk in dm.consume(dm.docker_client.api.pull(image_tag, stream=True)):

View File

@ -57,6 +57,7 @@ class DockerManager:
def __exit__(self, exception_type, exception_value, exception_traceback):
if self.log_progress:
_logger.info('Finished in %.2fs', self.duration)
self.result['log_progress'] = self.log_progress
if exception_value:
self.result['success'] = False
_logger.warning(exception_value)

View File

@ -16,7 +16,7 @@ from psycopg2 import sql
from psycopg2.extensions import TransactionRollbackError
from ..common import dt2time, now, grep, local_pgadmin_cursor, s2human, dest_reg, os, list_local_dbs, pseudo_markdown, RunbotException, findall, sanitize, markdown_escape
from ..container import docker_stop, docker_state, Command, docker_run
from ..container import docker_stop, docker_state, Command, docker_run, docker_pull
from ..fields import JsonDictField
from odoo import models, fields, api
@ -853,6 +853,15 @@ class BuildResult(models.Model):
if 'image_tag' not in kwargs:
kwargs.update({'image_tag': self.params_id.dockerfile_id.image_tag})
self._log('Preparing', 'Using Dockerfile Tag [%s](/runbot/dockerfile/tag/%s)', kwargs['image_tag'], kwargs['image_tag'], log_type='markdown')
icp = self.env['ir.config_parameter']
docker_registry_host = self.env['runbot.host'].browse(int(icp.get_param('runbot.docker_registry_host_id', default=0)))
if docker_registry_host and self.host_id.use_remote_docker_registry:
result = docker_pull(f"dockerhub.{docker_registry_host.name}/{kwargs['image_tag']}")
if result['success']:
result['image'].tag(kwargs['image_tag'])
if result.get('log_progress'):
self._log('Docker Run', f'Docker image was pulled {"" if result["success"] else "with errors"}')
containers_memory_limit = self.env['ir.config_parameter'].sudo().get_param('runbot.runbot_containers_memory', 0)
if containers_memory_limit and 'memory' not in kwargs:
kwargs['memory'] = int(float(containers_memory_limit) * 1024 ** 3)

View File

@ -128,6 +128,7 @@ class Dockerfile(models.Model):
arch_base = fields.Text(related='template_id.arch_base', readonly=False, related_sudo=True)
dockerfile = fields.Text(compute='_compute_dockerfile', tracking=True)
to_build = fields.Boolean('To Build', help='Build Dockerfile. Check this when the Dockerfile is ready.', default=False)
always_pull = fields.Boolean('Always pull', help='Always Pull on the hosts, not only at the use time', default=False, tracking=True)
version_ids = fields.One2many('runbot.version', 'dockerfile_id', string='Versions')
description = fields.Text('Description')
view_ids = fields.Many2many('ir.ui.view', compute='_compute_view_ids', groups="runbot.group_runbot_admin")

View File

@ -130,11 +130,16 @@ class Host(models.Model):
all_tags = set(all_docker_files.mapped('image_tag'))
if docker_registry_host and self.use_remote_docker_registry and not is_registry:
_logger.info('Pulling docker images...')
for dockerfile in all_docker_files:
total_duration = 0
for dockerfile in all_docker_files.filtered('always_pull'):
remote_tag = f'dockerhub.{docker_registry_host.name}/{dockerfile.image_tag}'
result = docker_pull(remote_tag)
if result['success']:
result['image'].tag(dockerfile.image_tag)
total_duration += result['duration']
if total_duration > 60:
_logger.warning("Pulling images took more than 60 seconds... will continue later")
break
else:
_logger.info('Building docker images...')
for dockerfile in self.env['runbot.dockerfile'].search([('to_build', '=', True)]):

View File

@ -13,6 +13,7 @@
<field name="name"/>
<field name="image_tag"/>
<field name="to_build"/>
<field name="always_pull"/>
<field name="version_ids" widget="many2many_tags"/>
<field name="project_ids" widget="many2many_tags"/>
<field name="template_id"/>
@ -104,6 +105,8 @@
<field name="image_tag"/>
<field name="to_build" groups="!runbot.group_runbot_admin"/>
<field name="to_build" widget="boolean_toggle" groups="runbot.group_runbot_admin"/>
<field name="always_pull" groups="!runbot.group_runbot_admin"/>
<field name="always_pull" widget="boolean_toggle" groups="runbot.group_runbot_admin"/>
<field name="version_ids" widget="many2many_tags"/>
<field name="project_ids" widget="many2many_tags"/>
<field name="use_count"/>