Skip to content

Commit bb6a24d

Browse files
committed
Merge remote-tracking branch 'origin/main' into l4_support
2 parents 2f16075 + 8cc4e36 commit bb6a24d

File tree

5 files changed

+95
-14
lines changed

5 files changed

+95
-14
lines changed

docs/guides/base-images.mdx

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
---
2+
title: How to use base images
3+
description: "A guide to configuring a base image for your truss"
4+
---
5+
6+
7+
Model serving enviroments will often be standardized as container images to avoid wrangling python, system, and other requirements needed to run your model on every deploy.
8+
Leverage your existing container artifacts by bringing your own base image to Truss.
9+
10+
## Setting a base image in config.yaml
11+
12+
To specify a base image to build a truss container image from in your `config.yaml` configure a `base_image`.
13+
14+
```yaml config.yaml
15+
base_image:
16+
image: <image_name:tag>
17+
python_executable_path: <path-to-python>
18+
```
19+
20+
where `python_executable_path` is a path to a python executable with which to run your server.
21+
22+
## Example usage
23+
24+
This [example truss](https://github.com/basetenlabs/truss/tree/main/examples/nemo-titanet) demonstrates how to properly configure a base image for Nvidia NeMo TitaNet:
25+
26+
```yaml config.yaml
27+
base_image:
28+
image: nvcr.io/nvidia/nemo:23.03
29+
python_executable_path: /usr/bin/python
30+
apply_library_patches: true
31+
bundled_packages_dir: packages
32+
data_dir: data
33+
requirements:
34+
- PySoundFile
35+
live_reload: false
36+
resources:
37+
accelerator: T4
38+
cpu: 2500m
39+
memory: 4512Mi
40+
use_gpu: true
41+
secrets: {}
42+
spec_version: '2.0'
43+
system_packages:
44+
- python3.8-venv
45+
```
46+
47+
## Configuring private base images with build time secrets
48+
49+
Secrets of the form `DOCKER_REGISTRY_<REGISTRY_URL>` will be supplied to your model build to authenticate image pulls from private container registries.
50+
For information on where to store secret values see the [secrets guide](secrets#storing-secrets-on-your-remote).
51+
52+
For example, to configure docker credentials to a private dockerhub repository your `config.yaml` should include the following secret and placeholder:
53+
54+
```yaml config.yaml
55+
secrets:
56+
DOCKER_REGISTRY_https://index.docker.io/v1/: null
57+
```
58+
59+
along with a configured Baseten secret `DOCKER_REGISTRY_https://index.docker.io/v1/` with a base64 encoded `username:password` secret value:
60+
61+
```sh
62+
echo -n 'username:password' | base64
63+
```
64+
65+
To add docker credentials for gcloud artifact registry provide an [access token](https://cloud.google.com/artifact-registry/docs/docker/authentication#token) as the secret value.
66+
For example, to configure authentication for a repository in `us-west2` your `config.yaml` should include the following secret and placeholder:
67+
68+
```yaml config.yaml
69+
secrets:
70+
DOCKER_REGISTRY_us-west2-docker.pkg.dev: null
71+
```

docs/mint.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,8 @@
6767
{
6868
"group": "Guides",
6969
"pages": [
70-
"guides/secrets"
70+
"guides/secrets",
71+
"guides/base-images"
7172
]
7273
},
7374
{

pyproject.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "truss"
3-
version = "0.7.8"
3+
version = "0.7.7rc5"
44
description = "A seamless bridge from model development to model delivery"
55
license = "MIT"
66
readme = "README.md"

truss/templates/shared/secrets_resolver.py

+16-8
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import os
22
from collections.abc import Mapping
33
from pathlib import Path
4-
from typing import Dict
4+
from typing import Dict, Optional
5+
6+
SECRETS_DOC_LINK = "https://truss.baseten.co/docs/using-secrets"
57

68

79
class SecretNotFound(Exception):
@@ -17,7 +19,7 @@ def get_secrets(config: Dict):
1719
return Secrets(config.get("secrets", {}))
1820

1921
@staticmethod
20-
def _resolve_secret(secret_name: str, default_value: str):
22+
def _resolve_secret(secret_name: str, default_value: Optional[str]):
2123
secret_value = default_value
2224
secret_env_var_name = SecretsResolver.SECRET_ENV_VAR_PREFIX + secret_name
2325
if secret_env_var_name in os.environ:
@@ -39,15 +41,11 @@ def __init__(self, base_secrets: Dict[str, str]):
3941

4042
def __getitem__(self, key: str) -> str:
4143
if key not in self._base_secrets:
42-
# Note this is the case where the secrets are not specified in
43-
# config.yaml
44-
raise SecretNotFound(f"Secret '{key}' not specified in the config.")
44+
raise SecretNotFound(_secret_missing_error_message(key))
4545

4646
found_secret = SecretsResolver._resolve_secret(key, self._base_secrets[key])
4747
if not found_secret:
48-
raise SecretNotFound(
49-
f"Secret '{key}' not found. Please check available secrets."
50-
)
48+
raise SecretNotFound(_secret_missing_error_message(key))
5149

5250
return found_secret
5351

@@ -58,3 +56,13 @@ def __iter__(self):
5856

5957
def __len__(self):
6058
return len(self._base_secrets)
59+
60+
61+
def _secret_missing_error_message(key: str) -> str:
62+
return f"""
63+
Secret '{key}' not found. Please ensure that:
64+
* Secret '{key}' is defined in the 'secrets' section of the Truss config file
65+
* The model was pushed with the --trusted flag
66+
* Secret '{key}' is defined in the secret manager
67+
Read more about secrets here: {SECRETS_DOC_LINK}.
68+
"""

truss/tests/test_model_inference.py

+5-4
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,9 @@ def predict(self, request):
336336
"""
337337

338338
config_with_no_secret = "model_name: secrets-truss"
339+
missing_secret_error_message = """Secret 'secret' not found. Please ensure that:
340+
* Secret 'secret' is defined in the 'secrets' section of the Truss config file
341+
* The model was pushed with the --trusted flag"""
339342

340343
with ensure_kill_all(), tempfile.TemporaryDirectory(dir=".") as tmp_work_dir:
341344
truss_dir = Path(tmp_work_dir, "truss")
@@ -371,7 +374,7 @@ def predict(self, request):
371374

372375
assert "error" in response.json()
373376

374-
assert_logs_contain_error(container.logs(), "not specified in the config")
377+
assert_logs_contain_error(container.logs(), missing_secret_error_message)
375378
assert "Internal Server Error" in response.json()["error"]
376379

377380
with ensure_kill_all(), tempfile.TemporaryDirectory(dir=".") as tmp_work_dir:
@@ -390,9 +393,7 @@ def predict(self, request):
390393
response = requests.post(full_url, json={})
391394
assert response.status_code == 500
392395

393-
assert_logs_contain_error(
394-
container.logs(), "'secret' not found. Please check available secrets."
395-
)
396+
assert_logs_contain_error(container.logs(), missing_secret_error_message)
396397
assert "Internal Server Error" in response.json()["error"]
397398

398399

0 commit comments

Comments
 (0)