Compilaciones de imágenes automáticas con Jenkins, Packer y Kubernetes

La creación de imágenes personalizadas para iniciar las instancias de Compute Engine o los contenedores de Docker puede reducir el tiempo de inicio y aumentar la confiabilidad. Mediante la instalación previa del software en una imagen personalizada, también puedes reducir tu dependencia de la disponibilidad de repositorios de terceros que están fuera de tu control.

Tú eliges cuánto software y cuánta configuración incluir en las imágenes personalizadas. En un extremo del espectro, una imagen mínimamente configurada, denominada imagen base en este documento, contiene una imagen del SO de base (como Ubuntu 14.10) y también puede incluir software y configuración básicos. Por ejemplo, puedes instalar previamente entornos de ejecución de lenguajes como Java o Ruby, configurar el registro remoto o aplicar parches de seguridad. Una imagen base proporciona una imagen de modelo de referencia estable que puede personalizarse aún más para entregar una aplicación.

En el otro extremo del espectro, una imagen totalmente configurada, denominada imagen inmutable en este documento, contiene no solo un SO de base o una imagen base, sino también todo lo necesario para ejecutar una aplicación. La configuración del entorno de ejecución, como la información de conexión a la base de datos o los datos sensibles, puede incluirse en la imagen, o puede proporcionarse a través del entorno, los metadatos o el servicio de administración de claves en el momento en el lanzamiento.

El proceso de compilación de imágenes tiene mucho en común con la compilación de software: tienes un código (Chef, Puppet, Bash, etc.) y las personas que lo escriben; una compilación ocurre cuando aplicas el código a una imagen base: un proceso de compilación exitoso genera un artefacto, y a menudo sometes el artefacto a diversas pruebas. Muchas de las recomendaciones que se aplican a la compilación de software también se aplican a las imágenes: puedes controlar la versión para administrar las secuencias de comandos de configuración de imágenes, activar compilaciones cuando se realizan cambios en esas secuencias de comandos; realizar compilaciones de imágenes de forma automática, y ver la versión y probar los artefactos de las imágenes resultantes cuando se completan las compilaciones.

Qué aprenderás

En esta solución, aprenderás sobre dos enfoques generales sobre la compilación de imágenes personalizadas y sobre el uso de varias herramientas de código abierto populares, como Jenkins, Packer, Docker y Kubernetes, a fin de crear una canalización automatizada para compilar imágenes de manera continua. Esta canalización se integra con Cloud Source Repositories en Google Cloud y genera imágenes de Compute Engine y de Docker.

Aprenderás a compilar imágenes inmutables y base, y aprenderás las prácticas recomendadas para administrar el acceso a estas imágenes en varios proyectos de Google Cloud. Por último, al final del documento, se incluye un instructivo completo que te permitirá implementar y usar una implementación de referencia de código abierto de la solución.

Tipos de imágenes

En la solución de Aplicaciones web escalables y resilientes, se usa una aplicación web de Ruby on Rails como referencia para ejecutar aplicaciones web en Google Cloud. El código fuente de esa solución no usa imágenes personalizadas. Cuando se inicia una instancia de Compute Engine, una secuencia de comandos de inicio instala Chef Solo, que luego instala todo lo que se requiere para ejecutar la aplicación. Esto incluye nginx, Ruby 2, cURL y otras herramientas del sistema, Unicorn, la app de Rails y todas sus gemas, imagemagick y la configuración de la app.

En el siguiente diagrama, se describe el proceso de inicio.

Un diagrama que muestra el proceso de inicio sin una imagen personalizada.

El proceso no es rápido, cada instancia tarda entre 10 y 15 minutos en iniciarse según la velocidad de descarga de los distintos repositorios necesarios para los paquetes, siempre que cada repositorio que aloja esos paquetes esté en línea y disponible. En las secciones siguientes, analizarás cómo una imagen base y una imagen inmutable podrían mejorar el rendimiento y la confiabilidad del proceso de inicio de la instancia.

Imágenes base

Cuando creas una imagen base, decides qué software y paquetes incluir en la imagen. A continuación, te ofrecemos algunas sugerencias que debes tener en cuenta cuando tomas esa decisión:

  • Velocidad de instalación. Los paquetes grandes pueden tardar en descargarse, el software que se debe compilar desde la fuente puede demorar mucho tiempo y los paquetes con muchas dependencias agravan aún más el problema. Considera incluir estos tipos de software y paquetes en tu imagen base.
  • Confiabilidad del repositorio remoto. Si no incluyes el software en tu imagen base y, en cambio, lo descargas en el momento del inicio, ¿confías en la disponibilidad del repositorio remoto? Si ese repositorio no está disponible durante el inicio, ¿impedirá que tu aplicación funcione? Para reducir la dependencia de los repositorios remotos que pueden estar fuera de tu control, considera incluir las dependencias críticas en una imagen base.
  • Frecuencia de cambio. ¿El software o el paquete cambian con mucha frecuencia? Si es así, considera dejarlos fuera de la imagen base y, en su lugar, almacénalos en una ubicación confiable y accesible, como un depósito de Cloud Storage.
  • Obligatorio o requerido por seguridad. Si se requiere la ejecución de determinados paquetes (como Logging, OSSEC, etc.), con una configuración específica, en cada instancia de tu organización, esos paquetes deberían instalarse en una imagen base que se extienda a todas las demás imágenes. Un equipo de seguridad puede usar una herramienta más avanzada, como Chef o Puppet, para compilar una imagen base de Docker, mientras que los desarrolladores posteriores podrían emplear Dockerfile para extender fácilmente la base.

Estos criterios sugieren que una imagen base para la aplicación Ruby on Rails de la solución Aplicaciones web escalables y resilientes podría incluir Chef Solo, nginx, Ruby, cURL y otras herramientas del sistema, y Unicorn. Las otras dependencias se instalarían en el momento del inicio.

En el siguiente diagrama, se describe el proceso de inicio con una imagen base:

Un diagrama que muestra el proceso de inicio con una imagen base.

La instancia funcional en este ejemplo recupera su configuración (por ejemplo, la string de conexión a la base de datos, las claves de API, etc.) del servicio de metadatos de Compute Engine. Puedes optar por usar diferentes servicios, como etcd o un depósito simple de Cloud Storage para administrar la configuración.

Las próximas secciones se enfocan en las herramientas usadas para automatizar la compilación de la imagen base de Ruby que se muestra aquí.

Imágenes inmutables

A diferencia de una imagen base, una imagen inmutable tiene todo su software incluido en la imagen. Cuando se inicia una instancia o un contenedor desde la imagen, no hay paquetes para descargar ni software para instalar. Una imagen inmutable para la aplicación de Ruby on Rails de la solución de Aplicaciones web escalables y resilientes incluiría todo el software, y la instancia estaría lista para entregar tráfico cuando se inicie.

Un diagrama que muestra el proceso de inicio con una imagen inmutable.

Imágenes inmutables y configuración

Puedes elegir que la aplicación acceda a los datos de configuración que necesita de un servicio de configuración o puedes incluir todas las configuraciones en la imagen inmutable. Si optas por el último enfoque, asegúrate de considerar las implicaciones de seguridad de incluir secretos en tus imágenes. Si estás enviando imágenes inmutables a repositorios públicos en Docker Hub, todos pueden acceder a ellas y no deben contener información sensible ni secreta.

Imágenes inmutables como unidad de implementación

El uso de imágenes inmutables como unidad de implementación elimina la posibilidad de que se produzca un desvío en la configuración, conforme el cual una o más instancias están en un estado diferente del esperado. Por ejemplo, esto puede suceder cuando aplicas un parche de seguridad a 100 contenedores en ejecución y algunos de ellos no se actualizan. La imagen se convierte en lo que implementas cuando se realiza cualquier cambio. Si el SO requiere un parche de software o la configuración de registro debe actualizarse, compilas una imagen nueva para incluir esos cambios y la implementas mediante el lanzamiento de instancias o contenedores nuevos y el reemplazo de todos los antiguos. Si eliges agrupar la configuración de la aplicación en una imagen inmutable, incluso un cambio sencillo como actualizar la string de conexión a la base de datos significa crear y publicar una imagen nueva.

Implementación y arquitectura de una canalización automatizada de compilación de imágenes

En esta sección, se incluye información detallada sobre la implementación de una canalización automatizada de compilación de imágenes que usa Jenkins, Packer, Docker y Google Kubernetes Engine (GKE) para compilar imágenes personalizadas de forma automática. Cada sección incluye una introducción, un diagrama de arquitectura y un análisis detallado de los componentes en ese diagrama.

Software y servicios empleados

Este software y estos servicios se usan para crear el generador automatizado de imágenes:

Software Uso
Jenkins Jenkins es un servidor de integración continua (IC) de código abierto popular. Utilizarás Jenkins para sondear repositorios de Git en otros proyectos que contienen secuencias de comandos de configuración de imágenes, luego, para compilar imágenes en función de esos repositorios.
Packer Packer en una herramienta que permite crear imágenes de máquina idénticas para varias plataformas desde una única configuración de origen. Es compatible con muchas fuentes de configuración diferentes, que incluyen Shell, Chef, Puppet, Ansible y Salt, y puede generar imágenes para Compute Engine, Docker y otros. Los agentes de Jenkins usan Packer para compilar imágenes a partir de la configuración en los repositorios de Git.
Docker Docker es una herramienta de código abierto para empaquetar y, además, implementar aplicaciones como contenedores. La implementación de Jenkins (que incluye el nodo líder y los agentes de compilación) en esta arquitectura y este instructivo se implementa como contenedores de Docker. Los agentes de compilación también generan imágenes de Docker como una de sus arquitecturas.
GKE GKE, con la tecnología de código abierto de Kubernetes, te permite ejecutar y administrar contenedores de Docker en máquinas virtuales de Google Cloud.
Container Registry Container Registry proporciona almacenamiento privado y seguro de imágenes de Docker en Google Cloud. Se ejecuta en Google Cloud y se accede a través de un extremo HTTPS.
Compute Engine GKE usa las VM de Compute Engine para ejecutar Kubernetes y alojar el líder de Jenkins y los contenedores de agentes de compilación. El proceso de compilación de Jenkins también genera imágenes de VM de Compute Engine, además de imágenes de Docker.
Cloud Storage Usarás Cloud Storage para almacenar copias de seguridad de tu configuración de Jenkins.
Nginx Nginx proporciona funcionalidad de proxy inverso; reenvía solicitudes entrantes a la interfaz web líder de Jenkins. Puede configurarse para terminar las conexiones SSL y proporcionar autenticación básica.

Descripción general del generador de imágenes

En el siguiente diagrama, se muestra cómo interactúan los distintos componentes para crear un sistema que compila imágenes de VM y de Docker de manera automática.

Un diagrama que muestra los diversos componentes del proyecto del generador de imágenes.

Define un trabajo en el líder de Jenkins para cada imagen que desees compilar. El trabajo sondea un repositorio de código fuente, Git en esta ilustración, que contiene las secuencias de comandos de configuración y una plantilla de Packer que describe cómo compilar una imagen. Cuando el proceso de sondeo detecta un cambio, el líder de Jenkins asigna el trabajo a un agente de compilación. El agente usa Packer a fin de ejecutar la compilación, que genera una imagen de Docker para Container Registry y una imagen de VM para Compute Engine.

Packer y las secuencias de comandos de configuración

Una plantilla de Packer y las secuencias de comandos de configuración asociadas definen cómo compilar una imagen. Se tratan como software y se almacenan en su propio repositorio de Git. Cada imagen que compilas tendrá su propio repositorio con una plantilla de Packer y secuencias de comandos de configuración.

En esta sección, se proporciona una descripción general de una posible configuración de Packer, en la que se usa Chef para personalizar Ubuntu 14.04 mediante la incorporación de Ruby y rbenv. Si deseas obtener una visión integral de Packer, consulta su excelente documentación en https://www.packer.io/docs.

Nombres de las imágenes y variables de Packer

El generador de imágenes compila una imagen cada vez que se realiza un cambio en el repositorio de Git que contiene la plantilla de Packer de la imagen y las secuencias de comandos de configuración. Se recomienda asignar un nombre o etiquetar las imágenes con la rama de Git y el ID de confirmación a partir del cual se crearon. Las plantillas de Packer te permiten definir variables y proporcionarles valores en el entorno de ejecución.

{
...
  "variables": {
      "Git_commit": "",
      "Git_branch": "",
      "ruby_version_name": "212",
      "project_id": "null"
  }
...
}

El agente de compilación de Jenkins puede encontrar la rama de Git y el ID de confirmación y proporcionarlos como variables a la herramienta de línea de comandos de Packer. Verás cómo funciona más adelante en la sección de instructivo de este documento.

Configuración programática con aprovisionadores

Una plantilla de Packer define uno o más aprovisionadores que describen cómo usar una herramienta como Chef o Puppet, o secuencias de comandos de shell para configurar una instancia. Packer admite muchos aprovisionadores; consulta el índice en la documentación de Packer para obtener una lista completa. En el siguiente fragmento, se define un aprovisionador chef-solo con rutas de acceso de guía de soluciones y recetas para ejecutar a fin de configurar una imagen:

{
  ...
  "provisioners": [
    {
      "type": "chef-solo",
      "install_command": "apt-get install -y curl && curl -L https://www.opscode.com/chef/install.sh | {{if .Sudo}}sudo{{end}} bash",
      "cookbook_paths": ["chef/site-cookbooks"],
      "run_list": [{{
        "recipe[ruby]",
        "recipe[ruby::user]",
        "recipe[ruby::ruby212]"
      ]
    }
  ],
  ...
}

La guía de soluciones y las recetas del chef se almacenan en el mismo repositorio de Git que la plantilla de Packer.

Define las salidas de imágenes con los compiladores

En la sección builders de la plantilla, se define en qué lugar se ejecutarán los aprovisionadores para crear imágenes nuevas. A fin de compilar una imagen de Compute Engine y una imagen de Docker, define dos generadores:

{
  "variables": {...},
  "provisioners": [...],
  "builders": [
    {
      "type": "googlecompute",
      "project_id": "{{user `project_id`}}",
      "source_image": "ubuntu-1410-utopic-v20150202",
      "zone": "us-central1-a",
      "image_name": "{{user `ruby_version_name`}}-{{user `Git_branch`}}-{{user `Git_commit`}}"
    },
    {
      "type": "docker",
      "image": "ubuntu:14.10",
      "commit": "true"
    }
  ],
 ...
}

El compilador de googlecompute incluye un atributo project_id que indica en qué lugar se almacenará la imagen resultante. El atributo image_name, que asigna un nombre a la imagen resultante, concatena variables para crear un nombre con información sobre la imagen: la versión de Ruby, la rama de Git y el ID de confirmación de Git que se usó a fin de compilar la imagen. Un URI de muestra para una imagen que creó el compilador de googlecompute puede ser de la siguiente manera:

https://www.googleapis.com/compute/v1/projects/image-builder-project-name/global/images/ruby212-master-9909043

El compilador de docker debe incluir un atributo post-processors para etiquetar la imagen con el registro de Docker y el repositorio al que se lo enviará:

{
  "variables": {...},
  "provisioners": [...],
  "builders": [...],
  "post-processors": [
    [
      {
        "type": "docker-tag",
        "repository": "gcr.io/{{user `project_id`}}/ruby212",
        "tag": "{{user `Git_branch`}}-{{user `Git_commit`}}",
        "only": ["docker"]
      }
    ]
  ]
}

Este posprocesador etiquetará la imagen para su almacenamiento en Container Registry mediante el project_id que se proporciona cuando se ejecuta la compilación. Una vez que esta imagen de Docker se envía, puedes recuperarla:

docker pull gcr.io/image-builder-project-name/ruby212:master-9909043

Cada imagen que deseas compilar tendrá una plantilla de Packer y secuencias de comandos de configuración en su propio repositorio de código fuente, y el líder de Jenkins tendrá un trabajo definido para cada una, como se muestra en el siguiente diagrama.

Diagrama que muestra el proyecto del compilador de imágenes con imágenes personalizadas.

Una de las ventajas de usar Jenkins y Packer de manera conjunta es que Jenkins puede detectar cualquier actualización que realices en las plantillas de Packer o en las secuencias de comandos de configuración y responder a ellas. Por ejemplo, si actualizas la versión de Ruby instalada en la imagen de Ruby Foundation, el líder de Jenkins responde mediante la asignación de un agente para clonar el repositorio, ejecutar Packer en la plantilla y compilar las imágenes.

El instructivo que figura al final de esta solución cubrirá en detalle el proceso de configuración de un trabajo de Jenkins para ejecutar la compilación de Packer.

Aislamiento de proyectos

El líder de Jenkins y los agentes de compilación se ejecutan de manera conjunta en el mismo proyecto de Cloud Platform, y las imágenes que crean se almacenan en este proyecto. Los proyectos te permiten aislar aplicaciones por función. No se aplican cargos por el proyecto; solo se te cobra por los recursos que utilizas. En esta solución, la infraestructura de Jenkins se ejecutará en su propio proyecto, separada de los repositorios de control de origen que utiliza. Las copias de seguridad de Jenkins, que se analizan más adelante, se almacenan en un depósito de Google Cloud Storage dentro del proyecto. Esto permite a Jenkins actuar como un "concentrador de imágenes" y compartir imágenes con otros proyectos, al tiempo que permite que otros proyectos mantengan sus propios repositorios de código con controles de acceso separados.

Compila y comparte imágenes en toda la organización

Para facilitar el uso compartido de imágenes, esta solución coloca cada imagen de compilación almacenada en Git en un proyecto de configuración de imagen separado. Esta separación proporciona aislamiento entre el proyecto del generador de imágenes y las imágenes de compilación. Con esta arquitectura de centro y radios, en que el proyecto del generador de imágenes es el centro y los proyectos de configuración de imágenes son los radios, los equipos separados pueden poseer y administrar las configuraciones de las imágenes con mayor facilidad.

Esta arquitectura de concentrador y radio se ilustra en el siguiente diagrama.

Un diagrama que muestra el proyecto del compilador de imágenes como un sistema de concentrador y radio.

El control de acceso (que otorga al clúster de Jenkins acceso a cada proyecto de imagen y a los otros proyectos acceso a las imágenes que Jenkins compiló) se analizará a continuación.

Un proyecto por imagen

Cada proyecto que creas tiene un repositorio Cloud Repository dedicado basado en Git. No hay un límite en cuanto a la cantidad de proyectos que puedes crear, y solo pagas por los recursos que usas en un proyecto, como las instancias de Compute Engine. Por ejemplo, si tienes imágenes de PHP, Ruby y Wordpress, cada una tendría su propio proyecto visible en Google Cloud Console, como se muestra en el siguiente diagrama.

Un diagrama que muestra el proyecto del compilador de imágenes con proyectos diferentes para cada imagen personalizada.

Se puede acceder al repositorio de Cloud Repository de un proyecto desde el elemento de menú Código fuente. En los proyectos nuevos, puedes elegir cómo inicializar el repositorio: puedes duplicar un repositorio existente de GitHub o Bitbucket, enviar un repositorio de Git local existente o crear un repositorio de Git local nuevo desde Cloud Source Repositories, como se muestra en la siguiente imagen.

Imagen de una pantalla para navegar por el código fuente con Cloud Console.

En la siguiente imagen, se muestra el proyecto de imagen base de Ruby inicializado con una plantilla de Packer y recetas de Chef que definen la compilación.

La imagen base de Ruby con la plantilla de Packer y las recetas de Chef.

Para ver la URL del repositorio, haz clic en Configuración. Necesitarás esta URL a fin de crear un trabajo de compilación para el repositorio en el líder de Jenkins, como se muestra en la siguiente imagen.

La configuración del repositorio de código fuente para el líder de Jenkins.

Control de acceso al repositorio de Cloud Repository

El generador de imágenes de Jenkins necesita los permisos de Poder ver para cada repositorio Cloud Repository del proyecto de configuración de imágenes. En el siguiente diagrama, se muestra una vista simplificada de la arquitectura de concentrador y radio que se mostró con anterioridad.

Proyecto del compilador de imágenes con los permisos necesarios.

Cada proyecto debe otorgar acceso al proyecto del compilador de imágenes de Jenkins mediante el uso de la dirección de correo electrónico de la cuenta de servicio de procesamiento del proyecto del compilador de imágenes. Ese formato de dirección es \{PROJECT_ID\}-compute@developer.gserviceaccount.com y está disponible para copiarlo en la sección Permisos (Permissions) de ese proyecto en Cloud Console, como se muestra en la siguiente imagen.

Dirección para copiar desde la sección Permisos de un proyecto.

Una vez que tengas la dirección de correo electrónico de la cuenta de servicio de procesamiento para el proyecto que ejecuta el compilador de imágenes de Jenkins, ve a la sección Permisos (Permissions) de cada proyecto con el repositorio de Cloud Repository desde el que desees compilar imágenes, selecciona Agregar miembro (Add member) y otorga el permiso Poder ver (Can View), como se muestra en la siguiente imagen.

Configura los permisos Poder ver (Can view) en un proyecto.

El líder de Jenkins que se ejecuta en el proyecto del compilador de imágenes podrá consultar el repositorio de Cloud Repository en estos proyectos, realizar extracciones de este y compilar imágenes nuevas a medida que se confirmen los cambios.

Comparte imágenes de Compute Engine y Docker

Las imágenes de Compute Engine y Docker que crea el generador de imágenes se almacenan en el mismo proyecto que el generador de imágenes. Las aplicaciones utilizarán las imágenes en otros proyectos para iniciar instancias de Compute Engine y contenedores de Docker, y cada proyecto de aplicación que desee acceder a estas imágenes deberá tener el permiso Puede ver para el proyecto del generador de imágenes. Sigue el proceso definido en la sección anterior, esta vez mediante la ubicación de la cuenta de servicio de procesamiento de cada proyecto de aplicación y su incorporación como miembro con los permisos para Poder ver al proyecto del compilador de imágenes, como se muestra en el siguiente diagrama.

Agrega otro proyecto con los permisos Poder ver el proyecto del compilador de imágenes.

Copia de seguridad y restablecimiento de Jenkins

El líder de Jenkins incluye un trabajo predefinido para realizar copias de seguridad periódicas de la configuración de Jenkins y el historial de trabajos en Google Cloud Storage. De forma predeterminada, el trabajo se ejecuta de manera periódica (una vez cada dos horas, todos los días de la semana), como se muestra en la siguiente imagen.

Configuración de compilación automatizada en el líder de Jenkins.

En el paso de compilación del trabajo se ejecuta una secuencia de comandos de shell que archiva secretos, usuarios, trabajos y, además, historiales en un archivo comprimido. Se crearon dos copias del archivo: a una copia se le asigna un nombre con una marca de fecha, la otra copia se denomina LATEST, lo que te permite restablecer de manera fácil y automática la copia de seguridad más reciente. Puedes personalizar este paso a fin de agregar o quitar elementos para realizar una copia de seguridad, como se muestra en la siguiente imagen.

Cómo personalizar la secuencia de comandos de compilación.

Una acción posterior a la compilación usa el complemento de Cloud Storage y la credencial de metadatos de Google que creaste para interactuar con las API de Google y subir el archivo de copia de seguridad a Cloud Storage. Sube tanto el archivo de marca de fecha como el archivo LATEST. En la siguiente imagen, se muestra la definición del paso.

Interfaz para definir las acciones posteriores a la compilación.

En la siguiente imagen, se muestra un depósito con algunas copias de seguridad que se acumularon:

Lista de copias de seguridad acumuladas de un proyecto.

Restablece una copia de seguridad

De la misma manera en la que usaste variables de entorno para habilitar SSL o la autenticación básica en el proxy inverso de Nginx en una sección anterior, puedes usar una variable de entorno para configurar la definición del controlador de replicación del líder de Jenkins a fin de que restablezca una copia de seguridad cuando se inicie el servicio. El siguiente código es un fragmento de la definición del controlador de replicación:

{
  "kind": "ReplicationController",
  ...
  "spec": {
    ...
    "template": {
      "spec": {
        "containers": [
            {
              "name": "jenkins",
              "env": [
                {
                  "name": "GCS_RESTORE_URL",
                  "value": "gs://your-backup-bucket/jenkins-backup/LATEST.tar.gz"
                }
              ],
             ...
           }
        ]
      }
    }
  }
}

La imagen de Docker del líder de Jenkins comprueba la existencia de la variable del entorno GCS_RESTORE_URL cuando se inicia. Si se encuentra, se da por sentado que el valor es la URL de la copia de seguridad (que incluye el esquema gs://) y la secuencia de comandos usa la herramienta de línea de comandos de gsutil que está instalada en la imagen del líder de Jenkins para descargar y restablecer la copia de seguridad de forma segura.

El proceso de restablecimiento solo tiene lugar cuando se inicia un contenedor. Para restablecer una copia de seguridad después de haber iniciado un líder de Jenkins, cambia el tamaño de su controlador de replicación a 0, actualiza la definición del controlador para que apunte a la URL de la copia de seguridad y, a continuación, restablece el tamaño a 1. Esto se explica en el instructivo.

Instructivo

El contenido completo del instructivo, que incluye las instrucciones y el código fuente, está disponibles en GitHub, en https://github.com/GoogleCloudPlatform/kube-jenkins-imager.