On-Device-Personalisierung – deterministischer Build durch föderiertes Computing

Für die Arbeitslastattestierung in der Trusted Execution Environment (TEE) von On Device Personalization (ODP), die öffentlich in Google Cloud als Confidential Space (CS) verfügbar ist, sind deterministische Builds erforderlich.

Die Arbeitslast-Images müssen einen deterministischen Image-Hash generieren, der von Confidential Space für die Arbeitslast-Attestierung verwendet werden kann. Dabei wird die RATS-Architektur (Remote ATtestation procedureS) von NIST gemäß RFC 9334 verwendet.

In diesem Dokument werden die Implementierung und Unterstützung von deterministischen Builds im Repository odp-federatedcompute beschrieben. Die Dienste „ODP Aggregator“ und „Model Updater“ werden in Confidential Space ausgeführt. Das Repository unterstützt deterministische Builds für alle unsere Dienste, die für Produktionsanwendungsfälle erforderlich sind.

Deterministische Builds

Deterministische Builds bestehen aus zwei Hauptkomponenten:

  1. Die Kompilierung der erforderlichen Binärdateien. Dazu gehören JAR-Dateien, freigegebene Bibliotheken und Metadaten.
  2. Das Basis-Image und die Laufzeitabhängigkeiten. Das Basis-Image der Laufzeitumgebung, das zum Ausführen der kompilierten Binärdateien verwendet wird.

Derzeit unterstützt das ODP Federated Compute-Repository die folgenden Arten von Arbeitslasten:

  • Java- und Spring-Arbeitslasten
    • TaskAssignment, TaskManagement, Collector
  • Java + Spring mit JNI-TensorFlow-Arbeitslasten
    • ModelUpdater, Aggregator
  • Python-Arbeitslasten
    • TaskBuilder

Abhängigkeiten

In der folgenden Liste sind die Abhängigkeiten aufgeführt, auf die ODP angewiesen ist, um Determinismus und Verfügbarkeit aufrechtzuerhalten:

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

Deterministische Arbeitslasten

Alle Arbeitslasten werden mit Bazel mit sprachspezifischen Toolchains und Container-Images kompiliert, die mit rules_oci erstellt wurden. In der WORKSPACE-Datei werden alle Abhängigkeiten mit den entsprechenden Versionen und Hashes definiert.

Debian-Snapshots

Alle Arbeitslast-Images sollten innerhalb des bereitgestellten dockerfile erstellt werden, das auf einem Debian-Snapshot basiert. Debian-Snapshots bieten einen stabilen Repository-Snapshot mit deterministischen:

Java Spring-Arbeitslasten

remotejdk_17 von Bazel wird verwendet, um eine hermetische Java-Umgebung für die Kompilierung bereitzustellen. Andere Java-Abhängigkeiten werden in der WORKSPACE-Datei verwaltet und definiert.

Die Java Spring-Arbeitslasten werden in einer JAR-Datei mit dem Namen <service>_application.jar kompiliert. Die JAR-Datei enthält:

  • Java-Klassendateien
  • META-INF/
    • Bazel-Manifestdaten
  • build-data.properties
    • Bazel-Build-Daten
  • BOOT-INF/
    • Verpackte JAR-Abhängigkeiten, die von rules_spring generiert werden.

Bildebenen

Das Java Spring-Arbeitslast-Image besteht aus zwei Ebenen:

Image-Konfiguration

  • Einstiegspunkt
    • java -jar <service>_application.jar

JNI-TensorFlow-Arbeitslasten

JNI-TensorFlow-Arbeitslasten basieren auf Java Spring-Arbeitslasten. Eine hermetische Clang+LLVM-Bazel-Toolchain wird mit vorgefertigtem Clang+LLVM 16 mit einem Sysroot bereitgestellt, das vom Debian-Snapshot-Image zum Kompilieren von Maschinencode bereitgestellt wird.

Die JNI-Arbeitslasten werden in einer gemeinsam genutzten Bibliothek mit dem Namen libtensorflow.so zusammen mit der <service>_application.jar kompiliert.

Bildebenen

Das JNI-TensorFlow-Arbeitslast-Image besteht aus mehreren Ebenen:

  • Basisebene des Images
  • Debian-Paketabhängigkeitsebenen. Die Ebenen werden mithilfe von DEB-Archiven generiert, die von debian-snapshot heruntergeladen und als Image-Ebenen neu verpackt werden.
    • 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
  • Arbeitslastschicht
    • binary_tar.tar
      • <service>_application.jar
      • libtensorflow-jni.so
      • libaggregation-jni.so

Image-Konfiguration

  • Labels (nur für Bilder, die für die Ausführung in der TEE erstellt wurden)
    • "tee.launch_policy.allow_env_override": "FCP_OPTS"
      • Ermöglicht das Festlegen der Umgebungsvariable FCP_OPTS im vertraulichen Bereich. Die Arbeitslast verbraucht beim Start FCP_OPTS, um die erforderlichen Parameter zu konfigurieren.
      • Die Umgebungsvariable FCP_OPTS wird beim Ausführen des Images (nicht beim Erstellen) festgelegt, um die Deterministik des Builds beizubehalten.
    • "tee.launch_policy.log_redirect": "always"
    • "tee.launch_policy.monitoring_memory_allow": "always"
  • Einstiegspunkt
    • java -Djava.library.path=. -jar <service>_application.jar

Python-Arbeitslasten

Die rules_python von Bazel werden verwendet, um eine hermetische Python 3.10-Toolchain bereitzustellen. Eine gesperrte pip-Anforderungsdatei wird für das deterministische Abrufen von pip-Abhängigkeiten verwendet. Das Debian-Snapshot-Image sorgt dafür, dass deterministische Distributionen basierend auf der Plattformkompatibilität abgerufen werden, und bietet eine C++-Toolchain zum Kompilieren von Quelldistributionen.

Die Python-Arbeitslasten werden in einer Reihe heruntergeladener Pip-Pakete, einer Python 3.10-Distribution, dem ODP-Python-Quellcode und einem Python-Startskript verpackt.

  • <service>.runfiles/
    • Die Python-Distribution wird unter python_x86_64-unknown-linux-gnu/ gespeichert.
    • Der Quellcode wird unter com_google_ondevicepersonalization_federatedcompute/ gespeichert.
    • Pip-Pakete werden unter pypi_<dependency_name>/ gespeichert.
  • <service>.runfiles_manifest
    • Manifestdatei für das Verzeichnis <service>.runfiles/
  • <service>
    • Python-Skript zum Ausführen des Python-Arbeitslast mit den Runfiles

Bildebenen

Das Python-Arbeitslast-Image besteht aus vier Ebenen:

  • Basisebene des Images
  • Dolmetscherebene
    • interpreter_layer.jar
      • <service>/<service>.runfiles/python_x86_64-unknown-linux-gnu/**
  • Ebene „Pakete“
    • packages_layer.jar
      • <service>/<service>.runfiles/**/site-packages/**
  • Arbeitslastschicht
    • app_tar_manifest.tar
      • Enthält Quellcode, Startskript und Manifest.
        • <service>/<service>.runfiles_manifest
        • <service>/<service>
        • <service>/<service>.runfiles/com_google_ondevicepersonalization_federatedcompute/**

Image-Konfiguration

  • Einstiegspunkt
    • /<service>/<service>

Bilder erstellen

Nachdem Sie Ihre Arbeitslasten ausgewählt haben, können Sie Ihre Images erstellen und veröffentlichen.

Vorbereitung

  • Bazel 6.4.0
    • Java- und C++-Installationen erforderlich
  • Docker

Vorgehensweise

Bilder sollten im Docker-Container erstellt werden, der durch das bereitgestellte Dockerfile erstellt wurde. Es gibt zwei Skripts, die beim Erstellen der endgültigen deterministischen Bilder helfen.

  • docker_run.sh
    • Mit docker_run.sh wird das Docker-Image aus dem Dockerfile erstellt, das Arbeitsverzeichnis und der Docker-Daemon des Hosts werden bereitgestellt und Docker wird mit dem angegebenen Bash-Befehl ausgeführt. Alle Variablen, die vor dem Bash-Befehl übergeben werden, werden als „docker run“-Flags behandelt.
  • build_images.sh
    • Mit build_images.sh wird bazel build für alle Bilder ausgeführt und die generierten Bild-Hashes für jedes erstellte Bild ausgegeben.

Alle Bilder erstellen

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

Die erwarteten Image-Hashes für die einzelnen Releases finden Sie unter odp-federatedcompute GitHub-Releases.

Bilder veröffentlichen

Die Veröffentlichung wird mit oci_push-Bazel-Regeln konfiguriert. Das Ziel-Repository sollte für jeden Dienst für Folgendes konfiguriert sein:

  • Aggregator
  • Datensammler
  • model_updater
  • task_assignment
  • task_management
  • task_scheduler
  • task_builder

Einzelnes Bild veröffentlichen

So veröffentlichen Sie ein einzelnes Bild:

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

Erstellte Bilder

Alle erstellten Bilder müssen vom Ersteller gespeichert und gehostet werden, z. B. in einer GCP Artifact Registry.