Managed Service for Apache Spark Serverless führt Arbeitslasten in Docker-Containern aus. Der Container stellt die Laufzeitumgebung für die Treiber- und Executor-Prozesse der Arbeitslast bereit. Standardmäßig verwendet Managed Service for Apache Spark ein Container-Image, das die Standardpakete für Spark, Java, Python und R enthält, die mit einer Laufzeit-Release-Version verknüpft sind. Mit der Batches API von Managed Service for Apache Spark können Sie ein benutzerdefiniertes Container-Image anstelle des Standard-Images verwenden. In der Regel werden mit einem benutzerdefinierten Container-Image Java- oder Python-Abhängigkeiten für Spark-Arbeitslasten hinzugefügt, die nicht im Standard-Container-Image enthalten sind. Wichtig: Fügen Sie Spark nicht in Ihr benutzerdefiniertes Container-Image ein. Managed Service for Apache Spark stellt Spark zur Laufzeit im Container bereit.
Spark-Batcharbeitslast mit einem benutzerdefinierten Container-Image senden
gcloud
Verwenden Sie den Befehl gcloud dataproc batches submit spark mit dem Flag --container-image, um Ihr benutzerdefiniertes Container-Image anzugeben, wenn Sie eine Spark-Batcharbeitslast senden.
gcloud dataproc batches submit spark \ --container-image=custom-image, for example, "gcr.io/my-project-id/my-image:1.0.1" \ --region=region \ --jars=path to user workload jar located in Cloud Storage or included in the custom container \ --class=The fully qualified name of a class in the jar file, such as org.apache.spark.examples.SparkPi \ -- add any workload arguments here
Hinweise:
- Benutzerdefiniertes Image: Geben Sie das benutzerdefinierte Container-Image im folgenden Container Registry-Image-Namensformat an:
{hostname}/{project-id}/{image}:{tag}, z. B. „gcr.io/my-project-id/my-image:1.0.1“. Hinweis: Sie müssen Ihr benutzerdefiniertes Container-Image in Container Registry oder Artifact Registry (GAR) hosten. (Managed Service for Apache Spark kann keine Container aus anderen Registries abrufen.) --jars: Geben Sie einen Pfad zu einer Nutzerarbeitslast an, die in Ihrem benutzerdefinierten Container-Image enthalten ist oder sich in Cloud Storage befindet, z. B.file:///opt/spark/jars/spark-examples.jarodergs://my-bucket/spark/jars/spark-examples.jar.- Weitere Befehlsoptionen für Batches: Sie können weitere optionale Befehls-Flags für Batches hinzufügen, z. B. um einen Persistent History Server (PHS) zu verwenden. Hinweis: Der PHS muss sich in der Region befinden, in der Sie Batch-Arbeitslasten ausführen.
- Arbeitslastargumente: Sie können beliebige Arbeitslastargumente hinzufügen, indem Sie am Ende des Befehls „--“ gefolgt von den Arbeitslastargumenten hinzufügen.
REST
Das benutzerdefinierte Container-Image wird über das Feld RuntimeConfig.containerImage als Teil einer batches.create-API-Anfrage bereitgestellt.
Im folgenden Beispiel wird gezeigt, wie Sie einen benutzerdefinierten Container verwenden, um eine Batcharbeitslast mit der Managed Service for Apache Spark-API „batches.create“ einzureichen.
Ersetzen Sie diese Werte in den folgenden Anfragedaten:
- project-id: Google Cloud-Projekt-ID
- region: Region
- custom-container-image: Geben Sie das benutzerdefinierte Container-Image im folgenden Container Registry-Image-Namensformat an:
{hostname}/{project-id}/{image}:{tag}, z. B. „gcr.io/my-project-id/my-image:1.0.1“. Hinweis: Der benutzerdefinierte Container muss in Container Registry oder Artifact Registry gehostet werden. (Managed Service for Apache Spark kann keine Container aus anderen Registries abrufen.) jar-uri: Geben Sie einen Pfad zu einer Arbeitslast-JAR-Datei an, die in Ihrem benutzerdefinierten Container-Image enthalten ist oder sich in Cloud Storage befindet, z. B. „/opt/spark/jars/spark-examples.jar“ oder „gs:///spark/jars/spark-examples.jar“.class: Der voll qualifizierte Name einer Klasse in der JAR-Datei, z. B. „org.apache.spark.examples.SparkPi“.- Weitere Optionen: Sie können andere Ressourcenfelder für Batcharbeitslasten verwenden, z. B. das Feld
sparkBatch.args, um Argumente an Ihre Arbeitslast zu übergeben. Weitere Informationen finden Sie in der Dokumentation zur RessourceBatch. Informationen zur Verwendung eines Persistent History Server (PHS) finden Sie unter Persistent History Server einrichten. Hinweis: Der PHS muss sich in der Region befinden, in der Sie Batch-Arbeitslasten ausführen.
HTTP-Methode und URL:
POST https://dataproc.googleapis.com/v1/projects/project-id/locations/region/batches
JSON-Text anfordern:
{
"runtimeConfig":{
"containerImage":"custom-container-image
},
"sparkBatch":{
"jarFileUris":[
"jar-uri"
],
"mainClass":"class"
}
}
Wenn Sie die Anfrage senden möchten, maximieren Sie eine der folgenden Optionen:
Sie sollten eine JSON-Antwort ähnlich wie diese erhalten:
{
"name":"projects/project-id/locations/region/batches/batch-id",
"uuid":",uuid",
"createTime":"2021-07-22T17:03:46.393957Z",
"runtimeConfig":{
"containerImage":"gcr.io/my-project/my-image:1.0.1"
},
"sparkBatch":{
"mainClass":"org.apache.spark.examples.SparkPi",
"jarFileUris":[
"/opt/spark/jars/spark-examples.jar"
]
},
"runtimeInfo":{
"outputUri":"gs://dataproc-.../driveroutput"
},
"state":"SUCCEEDED",
"stateTime":"2021-07-22T17:06:30.301789Z",
"creator":"account-email-address",
"runtimeConfig":{
"properties":{
"spark:spark.executor.instances":"2",
"spark:spark.driver.cores":"2",
"spark:spark.executor.cores":"2",
"spark:spark.app.name":"projects/project-id/locations/region/batches/batch-id"
}
},
"environmentConfig":{
"peripheralsConfig":{
"sparkHistoryServerConfig":{
}
}
},
"operation":"projects/project-id/regions/region/operation-id"
}
Benutzerdefiniertes Container-Image erstellen
Benutzerdefinierte Container-Images für Managed Service for Apache Spark sind Docker-Images. Sie können die Tools zum Erstellen von Docker-Images verwenden, um benutzerdefinierte Container-Images zu erstellen. Die Images müssen jedoch bestimmte Bedingungen erfüllen, um mit Managed Service for Apache Spark kompatibel zu sein. In den folgenden Abschnitten werden diese Bedingungen erläutert.
Betriebssystem
Sie können ein beliebiges Betriebssystem-Basis-Image für Ihr benutzerdefiniertes Container-Image auswählen.
Empfehlung: Verwenden Sie die Standard-Debian 12-Images, z. B. debian:12-slim, da sie getestet wurden, um Kompatibilitätsprobleme zu vermeiden.
Dienstprogramme
Sie müssen die folgenden Dienstprogrammpakete, die zum Ausführen von Spark erforderlich sind, in Ihr benutzerdefiniertes Container-Image aufnehmen:
procpstini
Wenn Sie XGBoost über Spark (Java oder Scala) ausführen möchten, müssen Sie libgomp1 einbeziehen.
Containernutzer
Managed Service for Apache Spark führt Container als Linux-Nutzer spark mit der UID 1099 und der GID 1099 aus. USER-Anweisungen, die in Dockerfiles für benutzerdefinierte Container-Images festgelegt sind, werden zur Laufzeit ignoriert. Verwenden Sie die UID und GID für Dateisystemberechtigungen. Wenn Sie beispielsweise eine JAR-Datei unter /opt/spark/jars/my-lib.jar im Image als Arbeitslastabhängigkeit hinzufügen, müssen Sie dem Nutzer spark Leseberechtigung für die Datei erteilen.
Image-Streaming
Managed Service for Apache Spark startet eine Arbeitslast, die ein benutzerdefiniertes Container-Image erfordert, normalerweise, indem das gesamte Image auf das Laufwerk heruntergeladen wird. Dies kann zu einer Verzögerung der Initialisierungszeit führen, insbesondere bei Kunden mit großen Images.
Stattdessen können Sie Image-Streaming verwenden, eine Methode, mit der Image-Daten nach Bedarf abgerufen werden. Dadurch kann die Arbeitslast gestartet werden, ohne auf den Download des gesamten Images warten zu müssen. Dies kann die Initialisierungszeit verbessern. Wenn Sie Image-Streaming aktivieren möchten, müssen Sie die Container-Dateisystem-API aktivieren. Sie müssen Ihre Container-Images auch in Artifact Registry speichern. Das Artifact Registry-Repository muss sich in derselben Region wie Ihre Managed Service for Apache Spark-Arbeitslast oder in einer Multiregion befinden, die der Region entspricht, in der Ihre Arbeitslast ausgeführt wird. Wenn Managed Service for Apache Spark das Image nicht unterstützt oder der Image-Streamingdienst nicht verfügbar ist, lädt unsere Streaming-Implementierung das gesamte Image herunter.
Folgendes wird für das Image-Streaming nicht unterstützt:
- Images mit leeren oder doppelten Ebenen
- Images, die das V2 Image Manifest, Schemaversion 1 verwenden
In diesen Fällen ruft Managed Service for Apache Spark das gesamte Image ab, bevor die Arbeitslast gestartet wird.
Zusätzliche Anforderungen
Wenn VPC Service Controls Ihre Container-Images schützt und Sie Image-Streaming verwenden, müssen Sie auch die Image-Streaming-API (
containerfilesystem.googleapis.com) in den Dienstperimeter aufnehmen.Wenn die Spark-Arbeitslast nicht das Standarddienstkonto verwendet, müssen Sie dafür sorgen, dass Ihr benutzerdefiniertes Dienstkonto die IAM-Rolle Service Usage-Nutzer (
roles/serviceusage.serviceUsageConsumer) in dem Projekt hat, in dem das Container-Image gehostet wird.
Spark
Fügen Sie Spark nicht in Ihr benutzerdefiniertes Container-Image ein. Zur Laufzeit stellt Managed Service for Apache Spark Spark-Binärdateien und -Konfigurationen vom Host im Container bereit: Binärdateien werden im Verzeichnis /usr/lib/spark und Konfigurationen im Verzeichnis /etc/spark/conf bereitgestellt. Vorhandene Dateien in diesen Verzeichnissen werden von Managed Service for Apache Spark zur Laufzeit überschrieben.
Java-Laufzeitumgebung
Fügen Sie keine eigene Java-Laufzeitumgebung (JRE) in Ihr benutzerdefiniertes Container-Image ein. Zur Laufzeit stellt Managed Service for Apache Spark OpenJDK vom Host im Container bereit. Wenn Sie eine JRE in Ihr benutzerdefiniertes Container-Image einfügen, wird sie ignoriert.
Java-Pakete
Sie können JAR-Dateien als Spark-Arbeitslastabhängigkeiten in Ihr benutzerdefiniertes Container-Image einfügen und die Umgebungsvariable SPARK_EXTRA_CLASSPATH so festlegen, dass die JAR-Dateien enthalten sind. Managed Service for Apache Spark fügt den Wert der Umgebungsvariablen im Klassenpfad von Spark-JVM-Prozessen hinzu. Empfehlung: Legen Sie JAR-Dateien im Verzeichnis /opt/spark/jars ab und setzen Sie SPARK_EXTRA_CLASSPATH auf /opt/spark/jars/*.
Sie können die JAR-Datei der Arbeitslast in Ihr benutzerdefiniertes Container-Image einfügen und dann beim Senden der Arbeitslast mit einem lokalen Pfad darauf verweisen, z. B. file:///opt/spark/jars/my-spark-job.jar (Beispiel siehe Spark-Batcharbeitslast mit einem benutzerdefinierten Container-Image senden).
Python-Pakete
Standardmäßig stellt Managed Service for Apache Spark zur Laufzeit einen Conda-Umgebungs-Build bereit, der ein OSS-Conda-Forge-Repository vom Host im Verzeichnis /opt/managed-spark/conda im Container verwendet.
PYSPARK_PYTHON ist auf /opt/managed-spark/conda/bin/python gesetzt. Das Basisverzeichnis /opt/managed-spark/conda/bin ist in PATH enthalten.
Sie können Ihre Python-Umgebung mit Paketen in einem anderen Verzeichnis in Ihrem benutzerdefinierten Container-Image einfügen, z. B. in /opt/conda, und die Umgebungsvariable PYSPARK_PYTHON auf /opt/conda/bin/python setzen.
Ihr benutzerdefiniertes Container-Image kann andere Python-Module enthalten, die nicht Teil der Python-Umgebung sind, z. B. Python-Skripts mit Dienstfunktionen.
Legen Sie die Umgebungsvariable PYTHONPATH so fest, dass sie die Verzeichnisse enthält, in denen sich die Module befinden.
R-Umgebung
Sie können die R-Umgebung in Ihrem benutzerdefinierten Container-Image mit einer der folgenden Optionen anpassen:
- Verwenden Sie Conda, um R-Pakete aus dem
conda-forge-Kanal zu verwalten und zu installieren. - Fügen Sie ein R-Repository für das Linux-Betriebssystem Ihres Container-Images hinzu und installieren Sie R-Pakete mit dem Paketmanager des Linux-Betriebssystems (siehe R-Softwarepaketindex).
Wenn Sie eine der beiden Optionen verwenden, müssen Sie die Umgebungsvariable R_HOME so festlegen, dass sie auf Ihre benutzerdefinierte R-Umgebung verweist. Ausnahme: Wenn Sie Conda sowohl zum Verwalten Ihrer R-Umgebung als auch zum Anpassen Ihrer Python-Umgebung verwenden, müssen Sie die Umgebungsvariable R_HOME nicht festlegen. Sie wird automatisch anhand der Umgebungsvariablen PYSPARK_PYTHON festgelegt.
Beispiel für die Erstellung eines benutzerdefinierten Container-Images
Dieser Abschnitt enthält Beispiele für das Erstellen benutzerdefinierter Container-Images, einschließlich Dockerfiles, gefolgt von Build-Befehlen. Ein Beispiel enthält die Mindestkonfiguration, die zum Erstellen eines Images erforderlich ist. Das andere Beispiel enthält Beispiele für zusätzliche Konfigurationen, einschließlich Python- und R-Bibliotheken.
Empfehlung zur gemeinsamen Standortnutzung für die Architektur
Damit Container mit Google CloudImage-Streaming (Container Dateisystem API) schnell initialisiert werden und keine Gebühren für die regionsübergreifende Datenübertragung anfallen, sollten sich die folgenden Dienste alle in der gleichen Region befinden:
- Artifact Registry
- Cloud Storage-Bucket
- Managed Service for Apache Spark-Batcharbeitslast
Schritt 1: Umgebungskonfiguration
Öffnen Sie Ihr Terminal oder die Google Cloud Shell und richten Sie die folgenden Umgebungsvariablen ein, um den Bereitstellungsprozess zu vereinfachen.
export PROJECT_ID="YOUR_PROJECT_ID"
export REGION="us-east1"
export REPO_NAME="spark-images"
export BUCKET_NAME="spark-jobs-bucket-${PROJECT_ID}-${REGION}"
Schritt 2: Dockerfile-Konfiguration
Mindestkonfiguration
# Recommendation: Use Debian 12.
FROM debian:12-slim
# Suppress interactive prompts.
ENV DEBIAN_FRONTEND=noninteractive
# Install utilities required by Spark scripts.
RUN apt update && apt install -y procps tini libjemalloc2
# Enable jemalloc as default memory allocator.
ENV LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so.2
# Create the 'spark' group/user.
# The GID and UID must be 1099. Home directory is required.
RUN groupadd -g 1099 spark
RUN useradd -u 1099 -g 1099 -d /home/spark -m spark
USER spark
Zusätzliche Konfiguration
# Recommendation: Use Debian 12.
FROM debian:12-slim
# Suppress interactive prompts.
ENV DEBIAN_FRONTEND=noninteractive
# Install utilities required by Spark scripts.
RUN apt update && apt install -y procps tini libjemalloc2
# Enable jemalloc as default memory allocator
ENV LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so.2
# Install utilities required by XGBoost for Spark.
RUN apt install -y procps libgomp1
# Install and configure Miniconda3.
ENV CONDA_HOME=/opt/miniforge3
ENV PYSPARK_PYTHON=${CONDA_HOME}/bin/python
ENV PATH=${CONDA_HOME}/bin:${PATH}
ADD https://github.com/conda-forge/miniforge/releases/latest/download/Miniforge3-Linux-x86_64.sh .
RUN bash Miniforge3-Linux-x86_64.sh -b -p /opt/miniforge3 \
&& ${CONDA_HOME}/bin/conda config --system --set always_yes True \
&& ${CONDA_HOME}/bin/conda config --system --set auto_update_conda False \
&& ${CONDA_HOME}/bin/conda config --system --set channel_priority strict
# Packages ipython and ipykernel are required if using custom conda and want to
# use this container for running notebooks.
RUN ${CONDA_HOME}/bin/mamba install ipython ipykernel jupyter
# Install Google Cloud SDK.
RUN ${CONDA_HOME}/bin/mamba install -n base google-cloud-sdk
# Install Conda packages.
#
# The following packages are installed in the default image.
# Recommendation: include all packages.
#
# Use mamba to quickly install packages.
RUN ${CONDA_HOME}/bin/mamba install -n base \
accelerate \
bigframes \
cython \
deepspeed \
evaluate \
fastavro \
fastparquet \
gcsfs \
google-cloud-aiplatform \
google-cloud-bigquery-storage \
google-cloud-bigquery[pandas] \
google-cloud-bigtable \
google-cloud-container \
google-cloud-datacatalog \
google-cloud-dataproc \
google-cloud-datastore \
google-cloud-language \
google-cloud-logging \
google-cloud-monitoring \
google-cloud-pubsub \
google-cloud-redis \
google-cloud-spanner \
google-cloud-speech \
google-cloud-storage \
google-cloud-texttospeech \
google-cloud-translate \
google-cloud-vision \
langchain \
lightgbm \
koalas \
matplotlib \
mlflow \
nltk \
numba \
numpy \
openblas \
orc \
pandas \
pyarrow \
pynvml \
pysal \
pytables \
python \
pytorch-cpu \
regex \
requests \
rtree \
scikit-image \
scikit-learn \
scipy \
seaborn \
sentence-transformers \
sqlalchemy \
sympy \
tokenizers \
transformers \
virtualenv \
xgboost
# Install pip packages.
RUN ${PYSPARK_PYTHON} -m pip install \
spark-tensorflow-distributor \
torcheval
# Install R and R libraries.
RUN ${CONDA_HOME}/bin/mamba install -n base \
r-askpass \
r-assertthat \
r-backports \
r-bit \
r-bit64 \
r-blob \
r-boot \
r-brew \
r-broom \
r-callr \
r-caret \
r-cellranger \
r-chron \
r-class \
r-cli \
r-clipr \
r-cluster \
r-codetools \
r-colorspace \
r-commonmark \
r-cpp11 \
r-crayon \
r-curl \
r-data.table \
r-dbi \
r-dbplyr \
r-desc \
r-devtools \
r-digest \
r-dplyr \
r-ellipsis \
r-evaluate \
r-fansi \
r-fastmap \
r-forcats \
r-foreach \
r-foreign \
r-fs \
r-future \
r-generics \
r-ggplot2 \
r-gh \
r-glmnet \
r-globals \
r-glue \
r-gower \
r-gtable \
r-haven \
r-highr \
r-hms \
r-htmltools \
r-htmlwidgets \
r-httpuv \
r-httr \
r-hwriter \
r-ini \
r-ipred \
r-isoband \
r-iterators \
r-jsonlite \
r-kernsmooth \
r-knitr \
r-labeling \
r-later \
r-lattice \
r-lava \
r-lifecycle \
r-listenv \
r-lubridate \
r-magrittr \
r-markdown \
r-mass \
r-matrix \
r-memoise \
r-mgcv \
r-mime \
r-modelmetrics \
r-modelr \
r-munsell \
r-nlme \
r-nnet \
r-numderiv \
r-openssl \
r-pillar \
r-pkgbuild \
r-pkgconfig \
r-pkgload \
r-plogr \
r-plyr \
r-praise \
r-prettyunits \
r-processx \
r-prodlim \
r-progress \
r-promises \
r-proto \
r-ps \
r-purrr \
r-r6 \
r-randomforest \
r-rappdirs \
r-rcmdcheck \
r-rcolorbrewer \
r-rcpp \
r-rcurl \
r-readr \
r-readxl \
r-recipes \
r-recommended \
r-rematch \
r-remotes \
r-reprex \
r-reshape2 \
r-rlang \
r-rmarkdown \
r-rodbc \
r-roxygen2 \
r-rpart \
r-rprojroot \
r-rserve \
r-rsqlite \
r-rstudioapi \
r-rvest \
r-scales \
r-selectr \
r-sessioninfo \
r-shape \
r-shiny \
r-sourcetools \
r-spatial \
r-squarem \
r-stringi \
r-stringr \
r-survival \
r-sys \
r-teachingdemos \
r-testthat \
r-tibble \
r-tidyr \
r-tidyselect \
r-tidyverse \
r-timedate \
r-tinytex \
r-usethis \
r-utf8 \
r-uuid \
r-vctrs \
r-whisker \
r-withr \
r-xfun \
r-xml2 \
r-xopen \
r-xtable \
r-yaml \
r-zip
ENV R_HOME=/usr/lib/R
# Add extra Python modules.
ENV PYTHONPATH=/opt/python/packages
RUN mkdir -p "${PYTHONPATH}"
# Add extra jars.
ENV SPARK_EXTRA_JARS_DIR=/opt/spark/jars/
ENV SPARK_EXTRA_CLASSPATH='/opt/spark/jars/*'
RUN mkdir -p "${SPARK_EXTRA_JARS_DIR}"
# Uncomment below and replace EXTRA_JAR_NAME with the jar file name.
# COPY "EXTRA_JAR_NAME" "${SPARK_EXTRA_JARS_DIR}"
# Create the 'spark' group/user.
# The GID and UID must be 1099. Home directory is required.
RUN groupadd -g 1099 spark
RUN useradd -u 1099 -g 1099 -d /home/spark -m spark
USER spark
Schritt 3: Build- und Push-Befehle
Führen Sie diese Befehle aus, um Ihr Container-Image mit Cloud Build zu erstellen und in Artifact Registry hochzuladen.
Cloud Storage-Bucket erstellen:
gcloud storage buckets create gs://${BUCKET_NAME} --project=${PROJECT_ID} \ --location=${REGION}Erstellen Sie das Artifact Registry-Repository:
gcloud artifacts repositories create ${REPO_NAME} \ --repository-format=docker --location=${REGION} --description="Custom Dataproc Serverless images"Erstellen Sie das Image mit Cloud Build und übertragen Sie es per Push:
gcloud builds submit --tag ${REGION}-docker.pkg.dev/${PROJECT_ID}/${REPO_NAME}/spark-custom:latest .
Schritt 4: Jobübermittlung
Stellen Sie den Job bereit, indem Sie die Laufzeit --version=2.3 definieren, um Probleme mit Versionsabweichungen zu vermeiden. In Produktionsumgebungen empfiehlt es sich, die Image-Version anstelle des :latest-Tags anzugeben.
- Berechtigungen: Achten Sie darauf, dass das entsprechende Dienstkonto die Rolle **Artifact Registry-Leser** (
roles/artifactregistry.reader) für das Artifact Registry-Repository (empfohlen) oder das Projekt hat, in dem das Container-Image gehostet wird. Das ist besonders wichtig, wenn das Image in einem anderen Projekt als dem gehostet wird, in dem die Arbeitslast ausgeführt wird.- Erteilen Sie diese Berechtigung für Laufzeitversionen vor
3.0dem Ausführungsdienstkonto der Arbeitslast oder Sitzung.- Erteilen Sie diese Berechtigung für Laufzeitversionen ab
3.0dem Dataproc Resource Manager Node Service Agent-Dienstkonto.
- Erteilen Sie diese Berechtigung für Laufzeitversionen ab
- Erteilen Sie diese Berechtigung für Laufzeitversionen vor
gcloud dataproc batches submit pyspark \
file:///usr/lib/spark/examples/src/main/python/pi.py \
--region=${REGION} \
--project=${PROJECT_ID} \
--version=2.3 \
--container-image=${REGION}-docker.pkg.dev/${PROJECT_ID}/${REPO_NAME}/spark-custom:latest \
-- 10