Creazione deterministica di calcolo federato sul dispositivo personalizzazione sul dispositivo

Le build deterministiche sono necessarie per l'attestazione del workload nell'ambiente di esecuzione attendibile (TEE) di On Device Personalization (ODP), disponibile pubblicamente su Google Cloud come Confidential Space (CS).

Le immagini dei workload devono generare un hash dell'immagine deterministico che può essere utilizzato da CS per l'attestazione dei workload (che utilizza l'architettura RFC 9334 Remote ATtestation procedureS (RATS) di NIST).

Questo documento esamina l'implementazione e il supporto per le build deterministiche nel repository odp-federatedcompute. I servizi ODP Aggregator e Model Updater verranno eseguiti all'interno di Confidential Space. Il repository supporta build deterministiche per tutti i nostri servizi, che sono necessari per i casi d'uso di produzione.

Build deterministiche

Le build deterministiche sono costituite da due componenti principali:

  1. La compilazione dei file binari richiesti. Sono inclusi file JAR, librerie condivise e metadati.
  2. L'immagine di base e le dipendenze runtime. L'immagine di base dell'ambiente di runtime utilizzata per eseguire i file binari compilati.

Al momento, il repository ODP Federated Compute supporta i seguenti tipi di workload:

  • Carichi di lavoro Java + Spring
    • TaskAssignment, TaskManagement, Collector
  • Java + Spring con workload JNI tensorflow
    • ModelUpdater, Aggregator
  • Carichi di lavoro Python
    • TaskBuilder

Dipendenze

Il seguente elenco contiene le dipendenze su cui si basa ODP per mantenere il determinismo e la disponibilità:

  • Bazel
  • GitHub
  • Maven
  • PyPi
  • Snapshot Debian
  • DockerHub Registry
  • Google Container Registry (GCR)

Workload deterministici

Tutti i carichi di lavoro vengono compilati utilizzando Bazel con toolchain e immagini container specifiche per la lingua create utilizzando rules_oci. Il file WORKSPACE definisce tutte le dipendenze con le versioni e gli hash corrispondenti.

Snapshot Debian

Tutte le immagini del workload devono essere create all'interno del Dockerfile fornito, basato su uno snapshot Debian. Gli snapshot di Debian forniscono uno snapshot del repository stabile con:

Carichi di lavoro Java Spring

remotejdk_17 di Bazel viene utilizzato per fornire una versione ermetica di Java per la compilazione. Le altre dipendenze Java sono gestite e definite nel file WORKSPACE.

I carichi di lavoro Java Spring vengono compilati in un file jar denominato <service>_application.jar. Il barattolo contiene:

  • File di classe Java
  • META-INF/
    • Dati del manifest Bazel
  • build-data.properties
    • Bazel build-data
  • BOOT-INF/

Livelli immagine

L'immagine del carico di lavoro Java Spring è costituita da due livelli:

Configurazione delle immagini

  • Punto di accesso
    • java -jar <service>_application.jar

Carichi di lavoro JNI Tensorflow

I carichi di lavoro JNI Tensorflow si basano sui carichi di lavoro Java Spring. Viene fornita una toolchain Bazel Clang+LLVM ermetica utilizzando Clang+LLVM 16 precompilato con un sysroot fornito dall'immagine snapshot di Debian per compilare il codice macchina.

I carichi di lavoro JNI vengono compilati in una libreria condivisa denominata libtensorflow.so insieme a <service>_application.jar.

Livelli immagine

L'immagine del workload TensorFlow JNI è composta da diversi livelli:

  • Livello immagine di base
  • Livelli di dipendenza dei pacchetti Debian. I livelli vengono generati utilizzando gli archivi deb scaricati da debian-snapshot e ricompattati come livelli di immagine
    • 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
  • Livello del workload
    • binary_tar.tar
      • <service>_application.jar
      • libtensorflow-jni.so
      • libaggregation-jni.so

Configurazione delle immagini

  • Etichette (solo per le immagini create per essere eseguite all'interno di TEE)
    • "tee.launch_policy.allow_env_override": "FCP_OPTS"
      • Consente di impostare la variabile di ambiente FCP_OPTS in unospazio riservato. Il workload consumerà FCP_OPTS all'avvio per configurare i parametri richiesti.
      • La variabile di ambiente FCP_OPTS viene impostata quando l'immagine viene eseguita (anziché creata) per mantenere la determinazione della build.
    • "tee.launch_policy.log_redirect": "always"
    • "tee.launch_policy.monitoring_memory_allow": "always"
  • Punto di accesso
    • java -Djava.library.path=. -jar <service>_application.jar

Carichi di lavoro Python

rules_python di Bazel viene utilizzato per fornire una toolchain Python 3.10 ermetica. Un file requirements pip bloccato viene utilizzato per il recupero deterministico delle dipendenze pip. L'immagine dello snapshot Debian garantisce che le distribuzioni deterministiche vengano recuperate in base alla compatibilità della piattaforma e fornisce una toolchain C++ per la compilazione delle distribuzioni di origine.

I carichi di lavoro Python verranno pacchettizzati in un insieme di pacchetti pip scaricati, una distribuzione Python 3.10, il codice sorgente Python ODP e uno script di avvio Python.

  • <service>.runfiles/
    • La distribuzione Python è archiviata in python_x86_64-unknown-linux-gnu/
    • Il codice sorgente è archiviato in com_google_ondevicepersonalization_federatedcompute/
    • I pacchetti pip sono memorizzati in pypi_<dependency_name>/
  • <service>.runfiles_manifest
    • File manifest per la directory <service>.runfiles/
  • <service>
    • Script Python per eseguire il workload Python utilizzando i runfile

Livelli immagine

L'immagine del carico di lavoro Python è composta da quattro livelli:

  • Livello immagine di base
  • Livello di interprete
    • interpreter_layer.jar
      • <service>/<service>.runfiles/python_x86_64-unknown-linux-gnu/**
  • Livello Pacchi
    • packages_layer.jar
      • <service>/<service>.runfiles/**/site-packages/**
  • Livello del workload
    • app_tar_manifest.tar
      • Contiene il codice sorgente, lo script di avvio e il manifest.
        • <service>/<service>.runfiles_manifest
        • <service>/<service>
        • <service>/<service>.runfiles/com_google_ondevicepersonalization_federatedcompute/**

Configurazione delle immagini

  • Punto di accesso
    • /<service>/<service>

Creare immagini

Una volta scelti i carichi di lavoro, puoi creare e pubblicare le immagini.

Prerequisiti

  • Bazel 6.4.0
    • Richiede l'installazione di Java e C++
  • Docker

Procedura

Le immagini devono essere create all'interno del container Docker creato dal dockerfile fornito. Vengono forniti due script per facilitare la creazione delle immagini deterministiche finali.

  • docker_run.sh
    • docker_run.sh creerà l'immagine Docker dal Dockerfile, monterà la directory di lavoro, monterà il daemon Docker host ed eseguirà Docker con il comando bash fornito. Le variabili passate prima del comando bash verranno trattate come flag di esecuzione di Docker.
  • build_images.sh
    • build_images.sh eseguirà bazel build per tutte le immagini e restituirà gli hash delle immagini generati per ogni immagine creata.

Crea tutte le immagini

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

Gli hash delle immagini previsti per ogni release sono disponibili in odp-federatedcompute GitHub releases.

Pubblicare immagini

La pubblicazione viene configurata utilizzando le regole Bazel oci_push. Per ogni servizio, il repository di destinazione deve essere configurato per tutti:

  • aggregatore
  • agente di raccolta
  • model_updater
  • task_assignment
  • task_management
  • task_scheduler
  • task_builder

Pubblicare una singola immagine

Per pubblicare una singola immagine:

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

Immagini integrate

Tutte le immagini create dovranno essere archiviate e ospitate dal creator, ad esempio in un registro degli artefatti Google Cloud.