Build determinístico de computação federada da personalização no dispositivo

Builds deterministas são necessários para atestados de carga de trabalho no recurso "No dispositivo" Ambiente de execução confiável (TEE) da Personalização (ODP), disponível publicamente no Google Cloud como Confidential Space (CS).

As imagens de carga de trabalho precisam gerar um hash determinístico de imagem que pode ser usado CS para atestado de carga de trabalho (que usa a ATtestação remota RFC 9334 do NIST Arquitetura de procedimentos (RATS, na sigla em inglês).

Este documento examinará a implementação e o suporte para análises determinísticas se desenvolve no odp-federatedcompute repositório de dados. Os serviços ODP Aggregator e Model Updater serão executados Confidential Space. O repositório oferece suporte a builds determinísticos para todos os nossos serviços, que são necessários para casos de uso de produção.

Builds determinísticos

Os builds determinísticos consistem em dois componentes principais:

  1. A compilação dos binários necessários. Isso inclui jars, bibliotecas compartilhadas e metadados.
  2. A imagem base e as dependências do ambiente de execução. A base do ambiente de execução usada para executar os binários compilados.

A partir de agora, o repositório de computação federada do ODP oferece suporte aos seguintes tipos de cargas de trabalho:

  • Cargas de trabalho Java + Spring
    • Atribuição de tarefas, Gerenciamento de tarefas, Coletor
  • Java + Spring com cargas de trabalho JNI do TensorFlow
    • ModelUpdater, agregador
  • Cargas de trabalho do Python
    • TaskBuilder

Dependências

A lista a seguir são dependências que a ODP usa para manter o determinismo e disponibilidade:

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

Cargas de trabalho determinísticas

Todas as cargas de trabalho são compiladas usando o Bazel com conjuntos de ferramentas específicos da linguagem e imagens de contêiner criadas usando rules_oci. O espaço de trabalho arquivo define todas as dependências com versões e hashes correspondentes.

Snapshots do Debian

Todas as imagens de carga de trabalho precisam ser criadas dockerfile criado com base em um snapshot do Debian. Debian os snapshots fornecem um snapshot de repositório estável com:

Cargas de trabalho do Java Spring

do Bazel remotejdk_17 é usada para fornecer um Java hermético para compilação. Outras dependências do Java são gerenciadas e definidas no espaço de trabalho arquivo.

As cargas de trabalho Java Spring são compilados em um arquivo jar chamado <service>_application.jar: O jar contém:

  • Arquivos de classe do Java
  • META-INF/
    • Dados do manifesto do Bazel
  • build-data.properties
    • Dados de build do Bazel
  • BOOT-INF/

Camadas de imagem

A imagem da carga de trabalho do Java Spring consiste em duas camadas:

Configuração de imagem

  • Ponto de entrada
    • java -jar <service>_application.jar

Cargas de trabalho JNI do Tensorflow

As cargas de trabalho do JNI do Tensorflow são criadas com base nas cargas de trabalho do Java Spring. Um O conjunto de ferramentas hermético Clang+LLVM Bazel é fornecido usando o Clang+LLVM pré-compilado. 16 com um sysroot fornecido pela imagem do snapshot do Debian para compilar o código de máquina.

As cargas de trabalho JNI são compiladas em uma biblioteca compartilhada chamada libtensorflow.so. com o <service>_application.jar.

Camadas de imagem

A imagem da carga de trabalho do JNI TensorFlow consiste em várias camadas:

  • Camada de imagem de base
  • as camadas de dependência do pacote Debian. As camadas são geradas usando deb arquivos baixados do debian-snapshot e reempacotados como camadas de imagem
    • 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
  • Camada de carga de trabalho
    • binary_tar.tar
      • <service>_application.jar
      • libtensorflow-jni.so
      • libaggregation-jni.so

Configuração de imagem

  • Rótulos (apenas para imagens criadas para execução no TEE)
    • "tee.launch_policy.allow_env_override": "FCP_OPTS"
      • Permite que a variável de ambiente FCP_OPTS seja definida em confidencial espaço. A carga de trabalho consumirá FCP_OPTS na inicialização para configurar parâmetros obrigatórios.
      • A variável de ambiente FCP_OPTS é definida quando a imagem é executada (em vez de construídos) para manter o determinismo do build.
    • "tee.launch_policy.log_redirect": "always"
    • "tee.launch_policy.monitoring_memory_allow": "always"
  • Ponto de entrada
    • java -Djava.library.path=. -jar <service>_application.jar

Cargas de trabalho do Python

O rules_python do Bazel é usado para fornecem um conjunto de ferramentas hermético do Python 3.10. Requisitos de pip bloqueado arquivo é usado para a busca determinística de dependências de pip. O snapshot do Debian imagem garante que distribuições deterministas sejam buscadas com base na plataforma compatibilidade e fornece um conjunto de ferramentas em C++ para compilar distribuições de origem.

As cargas de trabalho do Python são agrupadas em um conjunto de pacotes pip baixados, um Distribuição Python 3.10, código-fonte ODP do Python e uma inicialização do Python script.

  • <service>.runfiles/
    • A distribuição Python está armazenada em python_x86_64-unknown-linux-gnu/.
    • O código-fonte é armazenado em com_google_ondevicepersonalization_federatedcompute/
    • Os pacotes pip são armazenados em pypi_<dependency_name>/.
  • <service>.runfiles_manifest
    • Arquivo de manifesto para o diretório <service>.runfiles/
  • <service>
    • Script Python para executar a carga de trabalho do Python usando os arquivos de execução

Camadas de imagem

A imagem da carga de trabalho do Python consiste em quatro camadas:

  • Camada de imagem de base
  • Camada de intérprete
    • interpreter_layer.jar
      • <service>/<service>.runfiles/python_x86_64-unknown-linux-gnu/**
  • Camada de pacotes
    • packages_layer.jar
      • <service>/<service>.runfiles/**/site-packages/**
  • Camada de carga de trabalho
    • app_tar_manifest.tar
      • Contém código-fonte, script de inicialização e manifesto.
        • <service>/<service>.runfiles_manifest
        • <service>/<service>
        • <service>/<service>.runfiles/com_google_ondevicepersonalization_federatedcompute/**

Configuração de imagem

  • Ponto de entrada
    • /<service>/<service>

Criar imagens

Depois de escolher as cargas de trabalho, tudo está pronto para você criar e publicar os de imagens de contêiner.

Pré-requisitos

  • Bazel 6.4.0
    • Requer instalações de Java e C++
  • Docker

Procedimento

As imagens devem ser criadas dentro do contêiner do Docker criado pelo dockerfile. Dois scripts são fornecidos para ajudar na criação das imagens determinísticas finais.

  • docker_run.sh
    • docker_run.sh criará a imagem do Docker pelo dockerfile, mount no diretório de trabalho, montar o daemon do docker do host e executar o docker com o comando bash fornecido. Todas as variáveis transmitidas antes do comando bash ser tratadas como sinalizações de execução do Docker.
  • build_images.sh
    • build_images.sh vai executar bazel build para todas as imagens e gerar a saída gera hashes de imagem para cada imagem criada.

Criar todas as imagens

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

Os hashes de imagem esperados para cada versão podem ser encontrados em odp-federatedcompute no GitHub de versões.

Publicar imagens

A publicação é configurada usando oci_push Regras do Bazel. Para cada serviço, o repositório de destino precisa ser configurado para todos:

  • agregador
  • coletor
  • model_updater
  • task_assignment
  • task_management
  • task_scheduler
  • task_builder

Publicar uma única imagem

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

Imagens criadas

Todas as imagens criadas precisarão ser armazenadas e hospedadas pelo criador, como em um Artifact Registry do GCP.