Compilation déterministe de calcul fédéré de personnalisation sur l'appareil

Les compilations déterministes sont requises pour l'attestation de charge de travail dans l'environnement d'exécution sécurisé (TEE, Trusted Execution Environment) de la personnalisation sur l'appareil (ODP, On Device Personalization), disponible publiquement sur Google Cloud sous le nom d'espace confidentiel (CS, Confidential Space).

Les images de charge de travail doivent générer un hachage d'image déterministe qui peut être utilisé par CS pour l'attestation de charge de travail (qui utilise l'architecture RATS (Remote ATtestation procedureS) de la RFC 9334 du NIST).

Ce document décrit l'implémentation et la compatibilité des builds déterministes dans le dépôt odp-federatedcompute. Les services ODP Aggregator et Model Updater s'exécuteront dans Confidential Space. Le dépôt est compatible avec les builds déterministes pour tous nos services, qui sont nécessaires pour les cas d'utilisation en production.

Compilations déterministes

Les builds déterministes se composent de deux éléments principaux :

  1. Compilation des binaires requis. Cela inclut les fichiers JAR, les bibliothèques partagées et les métadonnées.
  2. Image de base et dépendances de l'environnement d'exécution. Image de base de l'environnement d'exécution utilisée pour exécuter les binaires compilés.

À l'heure actuelle, le dépôt ODP Federated Compute est compatible avec les types de charges de travail suivants :

  • Charges de travail Java + Spring
    • TaskAssignment, TaskManagement, Collector
  • Java + Spring avec des charges de travail TensorFlow JNI
    • ModelUpdater, Aggregator
  • Charges de travail Python
    • TaskBuilder

Dépendances

La liste suivante présente les dépendances sur lesquelles s'appuie ODP pour maintenir le déterminisme et la disponibilité :

  • Bazel
  • GitHub
  • Maven
  • PyPi
  • Instantanés Debian
  • Registre DockerHub
  • Google Container Registry (GCR)

Charges de travail déterministes

Toutes les charges de travail sont compilées à l'aide de Bazel avec des chaînes d'outils spécifiques à la langue et des images de conteneur créées à l'aide de rules_oci. Le fichier WORKSPACE définit toutes les dépendances avec les versions et les hachages correspondants.

Instantanés Debian

Toutes les images de charge de travail doivent être créées dans le dockerfile fourni, qui est basé sur un instantané Debian. Les instantanés Debian fournissent un instantané de dépôt stable avec les éléments déterministes suivants :

Charges de travail Java Spring

remotejdk_17 de Bazel est utilisé pour fournir un Java hermétique pour la compilation. Les autres dépendances Java sont gérées et définies dans le fichier WORKSPACE.

Les charges de travail Java Spring sont compilées dans un fichier JAR nommé <service>_application.jar. Le fichier JAR contient :

  • Fichiers de classe Java
  • META-INF/
    • Données du fichier manifeste Bazel
  • build-data.properties
    • Données de compilation Bazel
  • BOOT-INF/
    • Dépendances jar empaquetées, générées par rules_spring.

Calques d'images

L'image de charge de travail Java Spring se compose de deux couches :

Configuration des images

  • Point d'entrée
    • java -jar <service>_application.jar

Charges de travail JNI Tensorflow

Les charges de travail TensorFlow JNI sont basées sur les charges de travail Java Spring. Une chaîne d'outils Bazel Clang+LLVM hermétique est fournie à l'aide de Clang+LLVM 16 prédéfini avec un sysroot fourni par l'image instantanée Debian pour compiler le code machine.

Les charges de travail JNI sont compilées dans une bibliothèque partagée nommée libtensorflow.so, ainsi que dans <service>_application.jar.

Calques d'images

L'image de charge de travail TensorFlow JNI se compose de plusieurs couches :

  • Calque d'image de base
  • Couches de dépendances des packages Debian. Les calques sont générés à l'aide d'archives deb téléchargées à partir de debian-snapshot et reconditionnées en tant que calques d'image.
    • libc++1-16_amd64.tar
    • libc++abi1-16_amd64.tar
    • libc6_amd64.tar
    • libunwind-16_amd64.tar
    • libgcc-s1_amd64.tar
    • gcc-13-base_amd64.tar
  • Couche de charge de travail
    • binary_tar.tar
      • <service>_application.jar
      • libtensorflow-jni.so
      • libaggregation-jni.so

Configuration des images

  • Libellés (uniquement pour les images conçues pour s'exécuter dans un environnement d'exécution sécurisé)
    • "tee.launch_policy.allow_env_override": "FCP_OPTS"
    • "tee.launch_policy.log_redirect": "always"
    • "tee.launch_policy.monitoring_memory_allow": "always"
  • Point d'entrée
    • java -Djava.library.path=. -jar <service>_application.jar

Charges de travail Python

rules_python de Bazel est utilisé pour fournir une chaîne d'outils Python 3.10 hermétique. Un fichier d'exigences pip verrouillé est utilisé pour récupérer les dépendances pip de manière déterministe. L'image instantanée Debian garantit que des distributions déterministes sont récupérées en fonction de la compatibilité de la plate-forme et fournit une chaîne d'outils C++ pour compiler les distributions sources.

Les charges de travail Python seront empaquetées dans un ensemble de packages pip téléchargés, une distribution Python 3.10, le code source Python ODP et un script de démarrage Python.

  • <service>.runfiles/
    • La distribution Python est stockée sous python_x86_64-unknown-linux-gnu/.
    • Le code source est stocké sous com_google_ondevicepersonalization_federatedcompute/.
    • Les packages Pip sont stockés sous pypi_<dependency_name>/.
  • <service>.runfiles_manifest
    • Fichier manifeste du répertoire <service>.runfiles/
  • <service>
    • Script Python permettant d'exécuter la charge de travail Python à l'aide des runfiles

Calques d'images

L'image de charge de travail Python se compose de quatre couches :

  • Calque d'image de base
  • Couche de l'interprète
    • interpreter_layer.jar
      • <service>/<service>.runfiles/python_x86_64-unknown-linux-gnu/**
  • Couche "Packages"
    • packages_layer.jar
      • <service>/<service>.runfiles/**/site-packages/**
  • Couche de charge de travail
    • app_tar_manifest.tar
      • Contient le code source, le script de démarrage et le fichier manifeste.
        • <service>/<service>.runfiles_manifest
        • <service>/<service>
        • <service>/<service>.runfiles/com_google_ondevicepersonalization_federatedcompute/**

Configuration des images

  • Point d'entrée
    • /<service>/<service>

Compiler des images

Une fois vos charges de travail choisies, vous êtes prêt à créer et à publier vos images.

Prérequis

  • Bazel 6.4.0
    • Nécessite l'installation de Java et de C++
  • Docker

Procédure

Les images doivent être créées dans le conteneur Docker créé par le dockerfile fourni. Deux scripts sont fournis pour vous aider à créer les images déterministes finales.

  • docker_run.sh
    • docker_run.sh crée l'image Docker à partir du fichier Dockerfile, monte le répertoire de travail et le daemon Docker de l'hôte, puis exécute Docker avec la commande Bash fournie. Toutes les variables transmises avant la commande Bash seront traitées comme des indicateurs de l'exécution Docker.
  • build_images.sh
    • build_images.sh exécutera bazel build pour toutes les images et générera les hachages d'image pour chaque image créée.

Compiler toutes les images

./scripts/docker/docker_run.sh "./scripts/build_images.sh"

Les hachages d'image attendus pour chaque version sont disponibles dans les versions GitHub d'odp-federatedcompute.

Publier des images

La publication est configurée à l'aide des règles Bazel oci_push. Pour chaque service, le dépôt cible doit être configuré pour tous les éléments suivants :

  • agrégateur
  • collecteur
  • model_updater
  • task_assignment
  • task_management
  • task_scheduler
  • task_builder

Publier une seule image

Pour publier une seule image :

./scripts/docker/docker_run.sh "bazel run //shuffler/services/<servicename_no_underscore>:<servicename_with_underscore>_image_publish"

Images compilées

Toutes les images compilées devront être stockées et hébergées par le créateur, par exemple dans un registre d'artefacts GCP.