Compilaciones automatizadas de imágenes 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 Platform (GCP) y genera tanto imágenes de Compute Engine como de Docker.

Aprenderás a compilar imágenes inmutables y básicas, y conocerás las prácticas recomendadas para administrar el acceso a estas imágenes en múltiples proyectos en GCP. Finalmente, un instructivo completo al final del documento te permite 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, una aplicación web de Ruby on Rails se usa como referencia para ejecutar aplicaciones web en GCP. El código fuente para esa solución no usa imágenes personalizadas; cuando una instancia de Compute Engine se inicia, 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 aplicación de Rails y todas sus gemas, imagemagick, y la configuración de la aplicación.

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, y eso 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 diagrama siguiente, 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 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 las máquinas virtuales de GCP.
Container Registry Container Registry proporciona un almacenamiento de imágenes de Docker privado y seguro en GCP. Se ejecuta en GCP, y puedes acceder a este repositorio 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 automáticamente imágenes de VM y de Docker.

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

Defines un trabajo en el líder de Jenkins para cada imagen que deseas 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 que utiliza 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 documentación excelente 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. El siguiente fragmento 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.

Cómo definir las salidas de imágenes con los generadores

La sección builders de la plantilla define dónde 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 generador googlecompute incluye un atributo project_id que indica dónde se va a almacenar la imagen resultante. El atributo image_name, que asigna un nombre a la imagen resultante, concatena las variables con el fin de 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ó para compilar la imagen. Un URI de muestra para una imagen creada por el generador googlecompute puede verse de la siguiente manera:

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

El generador docker debería 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 postprocesador 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 uno, como se muestra en el siguiente diagrama.

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

Una de las ventajas de utilizar Jenkins y Packer de manera conjunta es que Jenkins puede detectar y responder a cualquier actualización que realices en las plantillas de Packer o en las secuencias de comandos de configuración. 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 centro y radios se ilustra en el siguiente diagrama.

Diagrama que muestra el proyecto del generador 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á su propio proyecto visible en Google Cloud Platform Console, como se muestra en el diagrama siguiente.

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

Se puede acceder al repositorio 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 en Cloud Source Repositories, como se muestra en la imagen siguiente.

Una imagen de pantalla que muestra cómo navegar por el código fuente con GCP Console.

En la imagen siguiente, 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 de Ruby Foundation con la plantilla de Packer y las recetas de Chef.

Para ver la URL del repositorio, haz clic en el ícono de 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 imagen siguiente.

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

Control de acceso al repositorio 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 centro y radios que se mostró con anterioridad.

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

Cada proyecto debe otorgar acceso al proyecto del generador de imágenes de Jenkins con la dirección de correo electrónico de la cuenta de servicio de procesamiento del proyecto del generador de imágenes. El formato de dirección es \{PROJECT_ID\}-compute@developer.gserviceaccount.com y está disponible para copiar en la sección Permisos de ese proyecto en GCP 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 generador de imágenes de Jenkins, ve a la sección Permisos de cada proyecto con un repositorio Cloud Repository desde el que desees compilar imágenes, selecciona Agregar miembro y otorga el permiso de Poder ver, como se muestra en la siguiente imagen.

Cómo configurar los permisos a fin de poder ver en un proyecto.

El líder de Jenkins que se ejecuta en el proyecto del generador de imágenes podrá sondear y realizar extracciones del repositorio Cloud Repository en estos proyectos, 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 Puede ver al proyecto del generador de imágenes, como se muestra en el diagrama siguiente.

Cómo agregar otro proyecto con los permisos para poder ver el proyecto del generador 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. Según la configuración predeterminada, el trabajo se ejecuta de forma 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

El paso de compilación del trabajo 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 imagen siguiente.

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 han acumulado:

Lista de copias de seguridad acumuladas para un proyecto.

Restablece una copia de seguridad

De la misma manera 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 inicia el servicio. El código siguiente 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 supone que el valor es la URL de la copia de seguridad (que incluye el esquema gs://) y la secuencia de comandos utiliza la herramienta de línea de comandos 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.

¿Te ha resultado útil esta página? Enviar comentarios:

Enviar comentarios sobre...