Las compilaciones determinísticas son necesarias para la certificación de cargas de trabajo en el entorno de ejecución confiable (TEE) de la Personalización en el dispositivo (ODP), disponible públicamente en Google Cloud como Confidential Space (CS).
Las imágenes de carga de trabajo deben generar un hash de imagen determinístico que CS pueda usar para la certificación de la carga de trabajo (que usa la arquitectura de Procedimientos de certificación remota (RATS) de RFC 9334 del NIST).
En este documento, se analizarán la implementación y la compatibilidad con las compilaciones determinísticas en el repositorio de odp-federatedcompute. Los servicios de actualización del modelo y el agregador de ODP se ejecutarán dentro de Confidential Space. El repositorio admite compilaciones determinísticas para todos nuestros servicios, que son necesarias para los casos de uso de producción.
Compilaciones determinísticas
Las compilaciones determinísticas constan de dos componentes principales:
- La compilación de los archivos binarios obligatorios Esto incluye archivos .jar, bibliotecas compartidas y metadatos.
- La imagen base y las dependencias del entorno de ejecución Imagen base del entorno de ejecución que se usa para ejecutar los archivos binarios compilados.
Actualmente, el repositorio de Federated Compute de ODP admite los siguientes tipos de cargas de trabajo:
- Cargas de trabajo de Java y Spring
- TaskAssignment, TaskManagement, Collector
- Java y Spring con cargas de trabajo de TensorFlow de JNI
- ModelUpdater, Aggregator
- Cargas de trabajo de Python
- TaskBuilder
Dependencias
En la siguiente lista, se indican las dependencias en las que se basa el ODP para mantener el determinismo y la disponibilidad:
- Bazel
- GitHub
- Maven
- PyPi
- Instantáneas de Debian
- Registro de DockerHub
- Google Container Registry (GCR)
Cargas de trabajo determinísticas
Todas las cargas de trabajo se compilan con Bazel con cadenas de herramientas específicas del lenguaje y con imágenes de contenedores compiladas con rules_oci. El archivo WORKSPACE define todas las dependencias con sus versiones y hashes correspondientes.
Instantáneas de Debian
Todas las imágenes de carga de trabajo deben compilarse dentro del dockerfile proporcionado, que se compila sobre una instantánea de Debian. Las instantáneas de Debian proporcionan una instantánea estable del repositorio con las siguientes características determinísticas:
- Encabezados y bibliotecas del sistema
- Arquitectura del sistema
- linux_x86_64
- Debian
- Compilador de C++
Cargas de trabajo de Java Spring
El remotejdk_17 de Bazel se usa para proporcionar un Java hermético para la compilación. Otras dependencias de Java se administran y definen en el archivo WORKSPACE.
Las cargas de trabajo de Java Spring se compilan en un archivo jar llamado <service>_application.jar. El frasco contiene lo siguiente:
- Archivos de clase Java
META-INF/- Datos del manifiesto de Bazel
build-data.properties- Datos de compilación de Bazel
BOOT-INF/- Son las dependencias de archivos .jar empaquetados, generadas por rules_spring.
Capas de imágenes
La imagen de carga de trabajo de Java Spring consta de dos capas:
- Capa de imagen base
- Imagen base de Java:
gcr.io/distroless/java17-debian11
- Imagen base de Java:
- Capa de carga de trabajo
binary_tar.tar<service>_application.jar
Configuración de la imagen
- Punto de entrada
java -jar <service>_application.jar
Cargas de trabajo de TensorFlow con JNI
Las cargas de trabajo de JNI TensorFlow se compilan sobre las cargas de trabajo de Java Spring. Se proporciona una cadena de herramientas hermética de Clang + LLVM de Bazel con Clang + LLVM 16 compilado previamente con un sysroot proporcionado por la imagen de instantánea de Debian para compilar código máquina.
Las cargas de trabajo de JNI se compilan en una biblioteca compartida llamada libtensorflow.so junto con <service>_application.jar.
Capas de imágenes
La imagen de carga de trabajo de TensorFlow de JNI consta de varias capas:
- Capa de imagen base
- Imagen base de Java:
gcr.io/distroless/java17-debian11
- Imagen base de Java:
- Son las capas de dependencia de paquetes de Debian. Las capas se generan con archivos .deb descargados de debian-snapshot y se vuelven a empaquetar como capas de imágenes.
libc++1-16_amd64.tarlibc++abi1-16_amd64.tarlibc6_amd64.tarlibunwind-16_amd64.tarlibgcc-s1_amd64.targcc-13-base_amd64.tar
- Capa de carga de trabajo
binary_tar.tar<service>_application.jarlibtensorflow-jni.solibaggregation-jni.so
Configuración de la imagen
- Etiquetas (solo para imágenes creadas para ejecutarse dentro del TEE)
"tee.launch_policy.allow_env_override": "FCP_OPTS"- Permite que la variable de entorno
FCP_OPTSse configure en un espacio confidencial. La carga de trabajo consumiráFCP_OPTSdurante el inicio para configurar los parámetros requeridos. - La variable de entorno
FCP_OPTSse establece cuando se ejecuta la imagen (en lugar de compilarse) para mantener el determinismo de la compilación.
- Permite que la variable de entorno
"tee.launch_policy.log_redirect": "always""tee.launch_policy.monitoring_memory_allow": "always"
- Punto de entrada
java -Djava.library.path=. -jar <service>_application.jar
Cargas de trabajo de Python
rules_python de Bazel se usa para proporcionar una cadena de herramientas hermética de Python 3.10. Se usa un archivo de requisitos de pip bloqueado para recuperar de forma determinística las dependencias de pip. La imagen de instantánea de Debian garantiza que se recuperen distribuciones determinísticas según la compatibilidad de la plataforma y proporciona una cadena de herramientas de C++ para compilar distribuciones de origen.
Las cargas de trabajo de Python se empaquetarán en un conjunto de paquetes pip descargados, una distribución de Python 3.10, el código fuente de Python de ODP y una secuencia de comandos de inicio de Python.
<service>.runfiles/- La distribución de Python se almacena en
python_x86_64-unknown-linux-gnu/. - El código fuente se almacena en
com_google_ondevicepersonalization_federatedcompute/. - Los paquetes de pip se almacenan en
pypi_<dependency_name>/
- La distribución de Python se almacena en
<service>.runfiles_manifest- Archivo de manifiesto para el directorio
<service>.runfiles/
- Archivo de manifiesto para el directorio
<service>- Secuencia de comandos de Python para ejecutar la carga de trabajo de Python con los archivos ejecutables
Capas de imágenes
La imagen de carga de trabajo de Python consta de cuatro capas:
- Capa de imagen base
- Imagen base de Python python:slim
- Capa de intérprete
interpreter_layer.jar<service>/<service>.runfiles/python_x86_64-unknown-linux-gnu/**
- Capa de paquetes
packages_layer.jar<service>/<service>.runfiles/**/site-packages/**
- Capa de carga de trabajo
app_tar_manifest.tar- Contiene el código fuente, la secuencia de comandos de inicio y el manifiesto.
<service>/<service>.runfiles_manifest<service>/<service><service>/<service>.runfiles/com_google_ondevicepersonalization_federatedcompute/**
- Contiene el código fuente, la secuencia de comandos de inicio y el manifiesto.
Configuración de la imagen
- Punto de entrada
/<service>/<service>
Compila imágenes
Una vez que hayas elegido tus cargas de trabajo, podrás compilar y publicar tus imágenes.
Requisitos previos
Procedimiento
Las imágenes se deben compilar dentro del contenedor de Docker creado por el dockerfile proporcionado. Se proporcionan dos secuencias de comandos para ayudar a compilar las imágenes determinísticas finales.
- docker_run.sh
docker_run.shcompilará la imagen de Docker a partir del Dockerfile, activará el directorio de trabajo, activará el daemon de Docker del host y ejecutará Docker con el comando bash proporcionado. Cualquier variable que se pase antes del comando bash se tratará como marcas de ejecución de Docker.
- build_images.sh
build_images.shejecutarábazel buildpara todas las imágenes y generará los hashes de las imágenes generadas para cada imagen compilada.
Compila todas las imágenes
./scripts/docker/docker_run.sh "./scripts/build_images.sh"
Los hashes de imagen esperados para cada versión se pueden encontrar en las versiones de odp-federatedcompute en GitHub.
Publica imágenes
La publicación se configura con las reglas de Bazel oci_push. Para cada servicio, el repositorio de destino debe configurarse para todos los siguientes elementos:
- agregador
- Recopilador
- model_updater
- task_assignment
- task_management
- task_scheduler
- task_builder
Publica una sola imagen
Para publicar una sola imagen, sigue estos pasos:
./scripts/docker/docker_run.sh "bazel run //shuffler/services/<servicename_no_underscore>:<servicename_with_underscore>_image_publish"
Imágenes compiladas
El creador deberá almacenar y alojar todas las imágenes compiladas, por ejemplo, en un registro de artefactos de GCP.