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:
- La compilazione dei file binari richiesti. Sono inclusi file JAR, librerie condivise e metadati.
- 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:
- Intestazioni e librerie di sistema
- Architettura di sistema
- linux_x86_64
- Debian
- Compilatore C++
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/- Dipendenze JAR pacchettizzate, generate da rules_spring.
Livelli immagine
L'immagine del carico di lavoro Java Spring è costituita da due livelli:
- Livello immagine di base
- Immagine di base Java:
gcr.io/distroless/java17-debian11
- Immagine di base Java:
- Livello del workload
binary_tar.tar<service>_application.jar
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
- Immagine di base Java:
gcr.io/distroless/java17-debian11
- Immagine di base Java:
- 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.tarlibc++abi1-16_amd64.tarlibc6_amd64.tarlibunwind-16_amd64.tarlibgcc-s1_amd64.targcc-13-base_amd64.tar
- Livello del workload
binary_tar.tar<service>_application.jarlibtensorflow-jni.solibaggregation-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_OPTSin unospazio riservato. Il workload consumeràFCP_OPTSall'avvio per configurare i parametri richiesti. - La variabile di ambiente
FCP_OPTSviene impostata quando l'immagine viene eseguita (anziché creata) per mantenere la determinazione della build.
- Consente di impostare la variabile di ambiente
"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>/
- La distribuzione Python è archiviata in
<service>.runfiles_manifest- File manifest per la directory
<service>.runfiles/
- File manifest per la directory
<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
- Immagine di base Python python:slim
- 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/**
- Contiene il codice sorgente, lo script di avvio e il manifest.
Configurazione delle immagini
- Punto di accesso
/<service>/<service>
Creare immagini
Una volta scelti i carichi di lavoro, puoi creare e pubblicare le immagini.
Prerequisiti
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.shcreerà 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.sheseguiràbazel buildper 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.