Compila contenedores más eficientes

En esta página, se describe cómo compilar imágenes de Docker más eficientes.

Compila contenedores más eficientes

Cuando creas contenedores para una aplicación, los archivos que no se necesitan en el entorno de ejecución, como las dependencias de tiempo de compilación y los archivos intermedios, se pueden incluir inadvertidamente en la imagen del contenedor. Estos archivos innecesarios pueden aumentar el tamaño de la imagen del contenedor y agregar tiempo y costo adicionales cuando la imagen se mueve entre tu registro de Docker y el entorno de ejecución de tu contenedor.

Para ayudar a reducir el tamaño de la imagen de tu contenedor, separa la compilación de la aplicación, junto con las herramientas usadas para compilarla, del ensamblaje del contenedor del entorno de ejecución.

Cloud Build proporciona una serie de contenedores de Docker con herramientas para desarrolladores comunes como Git, Docker y Google Cloud CLI. Usa estas herramientas a fin de definir un archivo de configuración de compilación con un paso de compilación de la aplicación y otro paso para ensamblar su entorno de ejecución final.

Por ejemplo, si compilas una aplicación de Java, que requiere archivos como el código fuente, las bibliotecas de la aplicación, los sistemas de compilación, las dependencias del sistema de compilación y el JDK, es posible que tengas un Dockerfile similar al siguiente:

FROM java:8

COPY . workdir/

WORKDIR workdir

RUN GRADLE_USER_HOME=cache ./gradlew buildDeb -x test

RUN dpkg -i ./gate-web/build/distributions/*.deb

CMD ["/opt/gate/bin/gate"]

En el ejemplo anterior, Gradle, que se usa a fin de compilar el paquete, descarga muchas bibliotecas para funcionar. Estas bibliotecas son indispensables para la compilación del paquete, pero no son necesarias en el entorno de ejecución. Todas las dependencias del entorno de ejecución se juntan en el paquete.

Con cada comando en el Dockerfile, se crea una capa nueva en el contenedor. Si se generan datos en esa capa y no se borran en el mismo comando, ese espacio no se puede recuperar. En este caso, Gradle descarga cientos de megabytes de bibliotecas en el directorio cache para realizar la compilación, pero las bibliotecas no se borran.

Una forma más eficaz de realizar la compilación es usar Cloud Build a fin de separar la compilación de la aplicación de la compilación de su capa de entorno de ejecución.

El ejemplo siguiente separa el paso de compilación de la aplicación Java del paso de ensamblaje del contenedor del entorno de ejecución:

YAML

  1. Compila la aplicación: En cloudbuild.yaml, agrega un paso para compilar la aplicación.

    Con el siguiente código, se agrega un paso que compila la imagen java:8, que contiene el código de Java.

    steps:
    
    - name: 'java:8'
      env: ['GRADLE_USER_HOME=cache']
      entrypoint: 'bash'
      args: ['-c', './gradlew gate-web:installDist -x test']
    
    
  2. Ensambla el contenedor de entorno de ejecución: En cloudbuild.yaml, agrega un paso para ensamblar el contenedor de entorno de ejecución.

    Con el siguiente código, se agrega un paso llamado gcr.io/cloud-builders/docker que ensambla el contenedor de entorno de ejecución. Define el contenedor de entorno de ejecución en un archivo diferente llamado Dockerfile.slim.

    En el ejemplo, se usa la capa base de Alpine Linux openjdk:8u111-jre-alpine, que es increíblemente eficiente. Además, incluye el JRE, en lugar del JDK más voluminoso que era necesario para compilar la aplicación.

    cloudbuild.yaml
    
    steps:
    - name: 'java:8'
      env: ['GRADLE_USER_HOME=cache']
      entrypoint: 'bash'
      args: ['-c',
             './gradlew gate-web:installDist -x test']
    
    - name: 'gcr.io/cloud-builders/docker'
      args: ['build',
             '-t', 'gcr.io/$PROJECT_ID/$REPO_NAME:$COMMIT_SHA',
             '-t', 'gcr.io/$PROJECT_ID/$REPO_NAME:latest',
             '-f', 'Dockerfile.slim',
             '.'
      ]
    
    
    Dockerfile.slim
    
    FROM openjdk:8-jre-alpine
    
    COPY ./gate-web/build/install/gate /opt/gate
    
    CMD ["/opt/gate/bin/gate"]
    
  3. Crea las imágenes de Docker: En cloudbuild.yaml, agrega un paso para crear las imágenes.
    steps:
    - name: 'java:8'
      env: ['GRADLE_USER_HOME=cache']
      entrypoint: 'bash'
      args: ['-c', './gradlew gate-web:installDist -x test']
    - name: 'gcr.io/cloud-builders/docker'
      args: ['build',
             '-t', 'gcr.io/$PROJECT_ID/$REPO_NAME:$COMMIT_SHA',
             '-t', 'gcr.io/$PROJECT_ID/$REPO_NAME:latest',
             '-f', 'Dockerfile.slim', '.']
    images:
    - 'gcr.io/$PROJECT_ID/$REPO_NAME:$COMMIT_SHA'
    - 'gcr.io/$PROJECT_ID/$REPO_NAME:latest'
    

JSON

  1. Compila la aplicación: En cloudbuild.json, agrega un paso para compilar la aplicación.

    Con el siguiente código, se agrega un paso llamado java:8 para compilar el código de Java.

    {
        "steps": [
        {
            "name": "java:8",
            "env": [
                "GRADLE_USER_HOME=cache"
            ],
            "entrypoint": "bash",
            "args": [
                "-c",
                "./gradlew gate-web:installDist -x test"
            ]
        },
    }
    
  2. Ensambla el contenedor de entorno de ejecución: En cloudbuild.json, agrega un paso para ensamblar el contenedor de entorno de ejecución.

    Con el siguiente código, se agrega un paso llamado gcr.io/cloud-builders/docker que ensambla el contenedor de entorno de ejecución. Define el contenedor de entorno de ejecución en un archivo diferente llamado Dockerfile.slim.

    En el ejemplo, se usa la capa base de Alpine Linux openjdk:8u111-jre-alpine, que es increíblemente eficiente. Además, incluye el JRE, en lugar del JDK más voluminoso que era necesario para compilar la aplicación.

    cloudbuild.json:
    
    {
        "steps": [
        {
            "name": "java:8",
            "env": [
                "GRADLE_USER_HOME=cache"
            ],
            "entrypoint": "bash",
            "args": [
                "-c",
                "./gradlew gate-web:installDist -x test"
            ]
        },
        {
            "name": "gcr.io/cloud-builders/docker",
            "args": [
                "build",
                "-t",
                "gcr.io/$PROJECT_ID/$REPO_NAME:$COMMIT_SHA",
                "-t",
                "gcr.io/$PROJECT_ID/$REPO_NAME:latest",
                "-f",
                "Dockerfile.slim",
                "."
            ]
        }
        ],
    }
    
    Dockerfile.slim:
    
    FROM openjdk:8u111-jre-alpine
    
    COPY ./gate-web/build/install/gate /opt/gate
    
    CMD ["/opt/gate/bin/gate"]
    
  3. Crea las imágenes de Docker: En cloudbuild.json, agrega un paso para crear las imágenes.
    {
        "steps": [
        {
            "name": "java:8",
            "env": [
                "GRADLE_USER_HOME=cache"
            ],
            "entrypoint": "bash",
            "args": [
                "-c",
                "./gradlew gate-web:installDist -x test"
            ]
        },
        {
            "name": "gcr.io/cloud-builders/docker",
            "args": [
                "build",
                "-t",
                "gcr.io/$PROJECT_ID/$REPO_NAME:$COMMIT_SHA",
                "-t",
                "gcr.io/$PROJECT_ID/$REPO_NAME:latest",
                "-f",
                "Dockerfile.slim",
                "."
            ]
        }
        ],
        "images": [
            "gcr.io/$PROJECT_ID/$REPO_NAME:$COMMIT_SHA",
            "gcr.io/$PROJECT_ID/$REPO_NAME:latest"
        ]
    }