konduktor-nightly 0.1.0.dev20250915104603__py3-none-any.whl → 0.1.0.dev20251107104752__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- konduktor/__init__.py +2 -2
- konduktor/backends/constants.py +1 -0
- konduktor/backends/deployment.py +27 -10
- konduktor/backends/deployment_utils.py +594 -358
- konduktor/backends/jobset_utils.py +6 -6
- konduktor/backends/pod_utils.py +133 -18
- konduktor/cli.py +61 -29
- konduktor/manifests/aibrix-setup.yaml +430 -0
- konduktor/manifests/apoxy-setup.yaml +42 -9
- konduktor/manifests/apoxy-setup2.yaml +69 -5
- konduktor/resource.py +9 -2
- konduktor/serving.py +10 -6
- konduktor/task.py +8 -5
- konduktor/templates/deployment.yaml.j2 +96 -47
- konduktor/templates/pod.yaml.j2 +123 -9
- konduktor/utils/base64_utils.py +2 -0
- konduktor/utils/schemas.py +1 -1
- konduktor/utils/validator.py +12 -0
- {konduktor_nightly-0.1.0.dev20250915104603.dist-info → konduktor_nightly-0.1.0.dev20251107104752.dist-info}/METADATA +1 -1
- {konduktor_nightly-0.1.0.dev20250915104603.dist-info → konduktor_nightly-0.1.0.dev20251107104752.dist-info}/RECORD +23 -23
- konduktor/templates/apoxy-deployment.yaml.j2 +0 -33
- {konduktor_nightly-0.1.0.dev20250915104603.dist-info → konduktor_nightly-0.1.0.dev20251107104752.dist-info}/LICENSE +0 -0
- {konduktor_nightly-0.1.0.dev20250915104603.dist-info → konduktor_nightly-0.1.0.dev20251107104752.dist-info}/WHEEL +0 -0
- {konduktor_nightly-0.1.0.dev20250915104603.dist-info → konduktor_nightly-0.1.0.dev20251107104752.dist-info}/entry_points.txt +0 -0
|
@@ -5,15 +5,12 @@ import json
|
|
|
5
5
|
import tempfile
|
|
6
6
|
import time
|
|
7
7
|
import typing
|
|
8
|
-
from datetime import datetime, timezone
|
|
8
|
+
from datetime import datetime, timedelta, timezone
|
|
9
9
|
from typing import Any, Dict, Optional, Tuple
|
|
10
10
|
|
|
11
11
|
import click
|
|
12
12
|
import colorama
|
|
13
13
|
|
|
14
|
-
if typing.TYPE_CHECKING:
|
|
15
|
-
from datetime import timedelta
|
|
16
|
-
|
|
17
14
|
import konduktor
|
|
18
15
|
from konduktor import kube_client, logging
|
|
19
16
|
from konduktor.backends import constants as backend_constants
|
|
@@ -428,7 +425,9 @@ def _parse_timestamp_filter(timestamp_str: str) -> datetime:
|
|
|
428
425
|
seconds=abs(local_offset)
|
|
429
426
|
)
|
|
430
427
|
else:
|
|
431
|
-
|
|
428
|
+
# Handle date-only format (local midnight --> UTC)
|
|
429
|
+
local_tz = datetime.now().astimezone().tzinfo
|
|
430
|
+
return dt.replace(tzinfo=local_tz).astimezone(timezone.utc)
|
|
432
431
|
return dt
|
|
433
432
|
except ValueError:
|
|
434
433
|
continue
|
|
@@ -450,7 +449,8 @@ def _format_timestamp(timestamp: str) -> str:
|
|
|
450
449
|
|
|
451
450
|
|
|
452
451
|
def _get_job_start_time(job: Dict[str, Any]) -> str:
|
|
453
|
-
|
|
452
|
+
status = job.get('status', {})
|
|
453
|
+
for condition in status.get('conditions', []):
|
|
454
454
|
if condition['reason'] == 'ResumeJobs':
|
|
455
455
|
return condition.get('lastTransitionTime', '')
|
|
456
456
|
return '-'
|
konduktor/backends/pod_utils.py
CHANGED
|
@@ -153,7 +153,9 @@ def create_pod_spec(task: 'konduktor.Task') -> Dict[str, Any]:
|
|
|
153
153
|
git_ssh_secret_name = None
|
|
154
154
|
env_secret_envs = []
|
|
155
155
|
default_secrets = []
|
|
156
|
+
basename_by_k8s: Dict[str, str] = {}
|
|
156
157
|
|
|
158
|
+
# only get own secrets
|
|
157
159
|
user_hash = common_utils.get_user_hash()
|
|
158
160
|
label_selector = f'{backend_constants.SECRET_OWNER_LABEL}={user_hash}'
|
|
159
161
|
user_secrets = kubernetes_utils.list_secrets(
|
|
@@ -162,19 +164,36 @@ def create_pod_spec(task: 'konduktor.Task') -> Dict[str, Any]:
|
|
|
162
164
|
|
|
163
165
|
for secret in user_secrets:
|
|
164
166
|
kind = kubernetes_utils.get_secret_kind(secret)
|
|
167
|
+
|
|
168
|
+
# incase the user modified their secret to have no key:value data
|
|
169
|
+
if secret.data is None:
|
|
170
|
+
secret.data = {}
|
|
171
|
+
|
|
172
|
+
# fill the map for *all* secrets we see
|
|
173
|
+
k8s_name = secret.metadata.name
|
|
174
|
+
lbls = secret.metadata.labels or {}
|
|
175
|
+
base = lbls.get(
|
|
176
|
+
backend_constants.SECRET_BASENAME_LABEL,
|
|
177
|
+
# fallback: strip trailing "-<something>" once if present
|
|
178
|
+
k8s_name.rsplit('-', 1)[0] if '-' in k8s_name else k8s_name,
|
|
179
|
+
)
|
|
180
|
+
basename_by_k8s[k8s_name] = base
|
|
181
|
+
|
|
165
182
|
if kind == 'git-ssh' and git_ssh_secret_name is None:
|
|
166
183
|
git_ssh_secret_name = secret.metadata.name
|
|
167
184
|
elif kind == 'env':
|
|
168
185
|
env_secret_name = secret.metadata.name
|
|
169
|
-
key
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
'
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
186
|
+
# iterate ALL keys, not just one (ex. if user made a multi-key env secret)
|
|
187
|
+
for key, _ in secret.data.items():
|
|
188
|
+
# wire the env var to read its value from a k8s secret
|
|
189
|
+
env_secret_envs.append(
|
|
190
|
+
{
|
|
191
|
+
'name': key,
|
|
192
|
+
'valueFrom': {
|
|
193
|
+
'secretKeyRef': {'name': env_secret_name, 'key': key}
|
|
194
|
+
},
|
|
195
|
+
}
|
|
196
|
+
)
|
|
178
197
|
elif kind == 'default':
|
|
179
198
|
default_secret_name = secret.metadata.name
|
|
180
199
|
basename = secret.metadata.labels.get(
|
|
@@ -184,6 +203,22 @@ def create_pod_spec(task: 'konduktor.Task') -> Dict[str, Any]:
|
|
|
184
203
|
{'k8s_name': default_secret_name, 'mount_name': basename}
|
|
185
204
|
)
|
|
186
205
|
|
|
206
|
+
# Check if the task references KONDUKTOR_DEFAULT_SECRETS and that it exists
|
|
207
|
+
uses_default_secret_var = (
|
|
208
|
+
'KONDUKTOR_DEFAULT_SECRETS' in (task.run or '')
|
|
209
|
+
or 'KONDUKTOR_DEFAULT_SECRETS' in (task.setup or '')
|
|
210
|
+
or '/konduktor/default-secrets/' in (task.run or '')
|
|
211
|
+
or '/konduktor/default-secrets/' in (task.setup or '')
|
|
212
|
+
)
|
|
213
|
+
if uses_default_secret_var and not default_secrets:
|
|
214
|
+
raise exceptions.MissingSecretError(
|
|
215
|
+
f'Task references KONDUKTOR_DEFAULT_SECRETS or '
|
|
216
|
+
f'/konduktor/default-secrets but '
|
|
217
|
+
f'user {common_utils.get_cleaned_username()} '
|
|
218
|
+
f'has no default secrets. Paths like '
|
|
219
|
+
f'$KONDUKTOR_DEFAULT_SECRETS/<secret_name>/... will not exist.'
|
|
220
|
+
)
|
|
221
|
+
|
|
187
222
|
# Inject --served-model-name, --host, and --port into serving run command
|
|
188
223
|
if task.serving and task.run and 'vllm.entrypoints.openai.api_server' in task.run:
|
|
189
224
|
if '--served-model-name' and '--host' and '--port' not in task.run:
|
|
@@ -262,31 +297,111 @@ def create_pod_spec(task: 'konduktor.Task') -> Dict[str, Any]:
|
|
|
262
297
|
},
|
|
263
298
|
temp.name,
|
|
264
299
|
)
|
|
300
|
+
|
|
301
|
+
# Capture the template env names BEFORE user config is merged
|
|
302
|
+
pod_config_template = common_utils.read_yaml(temp.name)
|
|
303
|
+
tmpl_envs = pod_config_template['kubernetes']['pod_config']['spec'][
|
|
304
|
+
'containers'
|
|
305
|
+
][0].get('env', [])
|
|
306
|
+
tmpl_env_names = {e['name'] for e in tmpl_envs}
|
|
307
|
+
|
|
265
308
|
pod_config = common_utils.read_yaml(temp.name)
|
|
266
|
-
# merge with `~/.konduktor/config.yaml``
|
|
309
|
+
# merge with `~/.konduktor/config.yaml`` (config.yaml overrides template)
|
|
267
310
|
kubernetes_utils.combine_pod_config_fields(temp.name, pod_config)
|
|
268
311
|
pod_config = common_utils.read_yaml(temp.name)
|
|
269
312
|
|
|
270
|
-
#
|
|
271
|
-
|
|
313
|
+
# Find what came from user config (appeared after combine, not in template)
|
|
314
|
+
premerge_envs = pod_config['kubernetes']['pod_config']['spec']['containers'][0].get(
|
|
272
315
|
'env', []
|
|
273
316
|
)
|
|
274
|
-
|
|
317
|
+
premerge_names = {e['name'] for e in premerge_envs}
|
|
318
|
+
config_env_names0 = premerge_names - tmpl_env_names
|
|
275
319
|
|
|
276
|
-
#
|
|
320
|
+
# Build final env list
|
|
321
|
+
env_map = {env['name']: env for env in premerge_envs}
|
|
322
|
+
|
|
323
|
+
# Inject secret envs (env secrets override config.yaml)
|
|
277
324
|
for env in env_secret_envs:
|
|
278
325
|
env_map[env['name']] = env
|
|
279
326
|
|
|
280
|
-
# Inject task
|
|
327
|
+
# Inject task envs
|
|
328
|
+
# CLI+task.yaml overrides everything else
|
|
329
|
+
# CLI already overrode task.yaml in other code
|
|
281
330
|
for k, v in task.envs.items():
|
|
282
331
|
env_map[k] = {'name': k, 'value': v}
|
|
283
332
|
|
|
284
|
-
|
|
285
|
-
pod_config['kubernetes']['pod_config']['spec']['containers'][0]['env'] =
|
|
286
|
-
|
|
333
|
+
final_envs_list = list(env_map.values())
|
|
334
|
+
pod_config['kubernetes']['pod_config']['spec']['containers'][0]['env'] = (
|
|
335
|
+
final_envs_list
|
|
287
336
|
)
|
|
337
|
+
container = pod_config['kubernetes']['pod_config']['spec']['containers'][0]
|
|
338
|
+
final_envs = container['env']
|
|
339
|
+
final_names = {e['name'] for e in final_envs}
|
|
340
|
+
|
|
288
341
|
logger.debug(f'rendered pod spec: \n\t{json.dumps(pod_config, indent=2)}')
|
|
289
342
|
|
|
343
|
+
# 1) Get secret envs actually used in the final env list
|
|
344
|
+
secret_details = sorted(
|
|
345
|
+
(e['name'], e['valueFrom']['secretKeyRef']['name'])
|
|
346
|
+
for e in final_envs
|
|
347
|
+
if isinstance(e, dict)
|
|
348
|
+
and e.get('valueFrom', {})
|
|
349
|
+
and e['valueFrom'].get('secretKeyRef')
|
|
350
|
+
)
|
|
351
|
+
secret_names = [n for n, _ in secret_details]
|
|
352
|
+
|
|
353
|
+
# 2) Get task-sourced (CLI+task.yaml) envs actually used in the final env list
|
|
354
|
+
task_all_names = sorted(
|
|
355
|
+
n
|
|
356
|
+
for n in (task.envs or {}).keys()
|
|
357
|
+
if n in final_names and n not in secret_names
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
# 3) Get Config.yaml envs actually used in the final env list
|
|
361
|
+
config_names = sorted(
|
|
362
|
+
n
|
|
363
|
+
for n in config_env_names0
|
|
364
|
+
if n in final_names and n not in secret_names and n not in task_all_names
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
# 4) Get other envs (template/system) actually used in the final env list
|
|
368
|
+
other_names = sorted(
|
|
369
|
+
final_names - set(secret_names) - set(task_all_names) - set(config_names)
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
# Export helper envs for the startup script (names only)
|
|
373
|
+
def _append_helper(name: str, values):
|
|
374
|
+
container['env'].append({'name': name, 'value': ','.join(values)})
|
|
375
|
+
|
|
376
|
+
# to show user basenames of k8s secrets instead of actual
|
|
377
|
+
# k8s secret names (which have added suffixes)
|
|
378
|
+
secret_map_pairs = [
|
|
379
|
+
f'{var}={basename_by_k8s.get(secret_k8s, secret_k8s)}'
|
|
380
|
+
for (var, secret_k8s) in secret_details
|
|
381
|
+
]
|
|
382
|
+
|
|
383
|
+
# Priority order: CLI > task.yaml > env secret > config > template/system
|
|
384
|
+
_append_helper(
|
|
385
|
+
'KONDUKTOR_ENV_SECRETS_HOPEFULLY_NO_NAME_COLLISION',
|
|
386
|
+
secret_names,
|
|
387
|
+
)
|
|
388
|
+
_append_helper(
|
|
389
|
+
'KONDUKTOR_ENV_SECRETS_MAP_HOPEFULLY_NO_NAME_COLLISION',
|
|
390
|
+
secret_map_pairs,
|
|
391
|
+
)
|
|
392
|
+
_append_helper(
|
|
393
|
+
'KONDUKTOR_ENV_TASK_ALL_HOPEFULLY_NO_NAME_COLLISION',
|
|
394
|
+
task_all_names,
|
|
395
|
+
)
|
|
396
|
+
_append_helper(
|
|
397
|
+
'KONDUKTOR_ENV_CONFIG_HOPEFULLY_NO_NAME_COLLISION',
|
|
398
|
+
config_names,
|
|
399
|
+
)
|
|
400
|
+
_append_helper(
|
|
401
|
+
'KONDUKTOR_ENV_OTHER_HOPEFULLY_NO_NAME_COLLISION',
|
|
402
|
+
other_names,
|
|
403
|
+
)
|
|
404
|
+
|
|
290
405
|
# validate pod spec using json schema
|
|
291
406
|
try:
|
|
292
407
|
validator.validate_pod_spec(pod_config['kubernetes']['pod_config']['spec'])
|
konduktor/cli.py
CHANGED
|
@@ -54,6 +54,7 @@ from konduktor import logging
|
|
|
54
54
|
from konduktor.backends import constants as backend_constants
|
|
55
55
|
from konduktor.backends import deployment_utils, jobset_utils
|
|
56
56
|
from konduktor.utils import (
|
|
57
|
+
base64_utils,
|
|
57
58
|
common_utils,
|
|
58
59
|
kubernetes_utils,
|
|
59
60
|
log_utils,
|
|
@@ -161,7 +162,9 @@ def _make_task_with_overrides(
|
|
|
161
162
|
if workdir is not None:
|
|
162
163
|
task.workdir = workdir
|
|
163
164
|
|
|
164
|
-
|
|
165
|
+
# perform overrides from CLI
|
|
166
|
+
if override_params:
|
|
167
|
+
task.set_resources_override(override_params)
|
|
165
168
|
if task.serving:
|
|
166
169
|
task.set_serving_override(serving_override_params)
|
|
167
170
|
|
|
@@ -653,28 +656,23 @@ def status(
|
|
|
653
656
|
all_users: bool, limit: Optional[int], after: Optional[str], before: Optional[str]
|
|
654
657
|
):
|
|
655
658
|
# NOTE(dev): Keep the docstring consistent between the Python API and CLI.
|
|
656
|
-
"""Shows list of all the jobs with optional filtering and pagination
|
|
657
|
-
|
|
658
|
-
Args:
|
|
659
|
-
all_users (bool): whether to show all jobs for all users
|
|
660
|
-
limit (Optional[int]): maximum number of jobs to display
|
|
661
|
-
after (Optional[str]): show jobs created after this timestamp
|
|
662
|
-
before (Optional[str]): show jobs created before this timestamp
|
|
659
|
+
"""Shows list of all the jobs with optional filtering and pagination.
|
|
663
660
|
|
|
661
|
+
\b
|
|
664
662
|
Examples:
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
663
|
+
konduktor status --limit 10
|
|
664
|
+
konduktor status --before "08/06/25 03:53PM"
|
|
665
|
+
konduktor status --all-users --limit 10 --after "08/06/25 03:53PM"
|
|
666
|
+
|
|
667
|
+
\b
|
|
668
|
+
Notes:
|
|
669
|
+
• When using --before or --after timestamps, "08/06/25"
|
|
670
|
+
is equivalent to "08/06/25 00:00".
|
|
671
|
+
• "03:53PM" is equivalent to "03:53:00PM".
|
|
672
|
+
• Timestamps shown in "konduktor status" are truncated
|
|
673
|
+
and are in the local timezone.
|
|
674
|
+
Example: "03:53:55PM" → "03:53PM" — would show up in
|
|
675
|
+
--after "03:53PM" but not in --before "03:53PM".
|
|
678
676
|
"""
|
|
679
677
|
context = kubernetes_utils.get_current_kube_config_context_name()
|
|
680
678
|
namespace = kubernetes_utils.get_kube_config_context_namespace(context)
|
|
@@ -802,6 +800,13 @@ def logs(
|
|
|
802
800
|
# pylint: disable=bad-docstring-quotes
|
|
803
801
|
help='Skip confirmation prompt.',
|
|
804
802
|
)
|
|
803
|
+
@click.option(
|
|
804
|
+
'--skip-image-check',
|
|
805
|
+
'-s',
|
|
806
|
+
is_flag=True,
|
|
807
|
+
default=False,
|
|
808
|
+
help='Skip Docker image validation checks for faster startup.',
|
|
809
|
+
)
|
|
805
810
|
def launch(
|
|
806
811
|
entrypoint: Tuple[str, ...],
|
|
807
812
|
dryrun: bool,
|
|
@@ -820,6 +825,7 @@ def launch(
|
|
|
820
825
|
env: List[Tuple[str, str]],
|
|
821
826
|
disk_size: Optional[int],
|
|
822
827
|
yes: bool,
|
|
828
|
+
skip_image_check: bool,
|
|
823
829
|
):
|
|
824
830
|
"""Launch a task.
|
|
825
831
|
|
|
@@ -829,6 +835,9 @@ def launch(
|
|
|
829
835
|
# NOTE(dev): Keep the docstring consistent between the Python API and CLI.
|
|
830
836
|
env = _merge_env_vars(env_file, env)
|
|
831
837
|
|
|
838
|
+
if skip_image_check:
|
|
839
|
+
os.environ['KONDUKTOR_SKIP_IMAGE_CHECK'] = '1'
|
|
840
|
+
|
|
832
841
|
task = _make_task_with_overrides(
|
|
833
842
|
entrypoint=entrypoint,
|
|
834
843
|
name=name,
|
|
@@ -973,7 +982,9 @@ def down(
|
|
|
973
982
|
|
|
974
983
|
if all:
|
|
975
984
|
assert jobs_specs is not None, f'No jobs found in namespace {namespace}'
|
|
976
|
-
|
|
985
|
+
if len(jobs_specs) == 0:
|
|
986
|
+
click.secho(f'No jobs found in namespace {namespace}', fg='yellow')
|
|
987
|
+
return
|
|
977
988
|
jobs = [job['metadata']['name'] for job in jobs_specs]
|
|
978
989
|
elif jobs:
|
|
979
990
|
# Get all available jobs to match against patterns
|
|
@@ -1481,12 +1492,21 @@ def create(kind, from_file, from_directory, inline, name):
|
|
|
1481
1492
|
data = {}
|
|
1482
1493
|
if from_directory:
|
|
1483
1494
|
click.echo(f'Creating secret from directory: {from_directory}')
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1495
|
+
# Use ABSOLUTE directory path so the top-level folder name is preserved
|
|
1496
|
+
base_dir_abs = os.path.abspath(os.path.expanduser(from_directory))
|
|
1497
|
+
if not os.path.isdir(base_dir_abs):
|
|
1498
|
+
raise click.BadParameter(
|
|
1499
|
+
f"--from-directory {from_directory} doesn't exist or is not a directory"
|
|
1500
|
+
)
|
|
1501
|
+
# Ensure there is at least one file inside
|
|
1502
|
+
if not any(p.is_file() for p in pathlib.Path(base_dir_abs).rglob('*')):
|
|
1503
|
+
raise click.BadParameter(f'--from-directory {from_directory} is empty.')
|
|
1504
|
+
|
|
1505
|
+
# Zip + base64 the WHOLE directory (this preserves the inner structure)
|
|
1506
|
+
archive_b64 = base64_utils.zip_base64encode([base_dir_abs])
|
|
1507
|
+
|
|
1508
|
+
# Store as a single key; pod will unzip to the expanded path
|
|
1509
|
+
data = {'payload.zip': archive_b64}
|
|
1490
1510
|
elif from_file:
|
|
1491
1511
|
click.echo(f'Creating secret from file: {from_file}')
|
|
1492
1512
|
key = os.path.basename(from_file)
|
|
@@ -1630,7 +1650,7 @@ def list_secrets(all_users: bool):
|
|
|
1630
1650
|
|
|
1631
1651
|
@cli.group(cls=_NaturalOrderGroup)
|
|
1632
1652
|
def serve():
|
|
1633
|
-
"""Manage
|
|
1653
|
+
"""Manage deployment serving with Konduktor.
|
|
1634
1654
|
|
|
1635
1655
|
USAGE: konduktor serve COMMAND
|
|
1636
1656
|
|
|
@@ -1692,6 +1712,13 @@ def serve():
|
|
|
1692
1712
|
# pylint: disable=bad-docstring-quotes
|
|
1693
1713
|
help='Skip confirmation prompt.',
|
|
1694
1714
|
)
|
|
1715
|
+
@click.option(
|
|
1716
|
+
'--skip-image-check',
|
|
1717
|
+
'-s',
|
|
1718
|
+
is_flag=True,
|
|
1719
|
+
default=False,
|
|
1720
|
+
help='Skip Docker image validation checks for faster startup.',
|
|
1721
|
+
)
|
|
1695
1722
|
def serve_launch(
|
|
1696
1723
|
entrypoint: Tuple[str, ...],
|
|
1697
1724
|
dryrun: bool,
|
|
@@ -1714,6 +1741,7 @@ def serve_launch(
|
|
|
1714
1741
|
ports: Optional[int],
|
|
1715
1742
|
probe: Optional[str],
|
|
1716
1743
|
yes: bool,
|
|
1744
|
+
skip_image_check: bool = False,
|
|
1717
1745
|
):
|
|
1718
1746
|
"""Launch a deployment to serve.
|
|
1719
1747
|
|
|
@@ -1723,6 +1751,9 @@ def serve_launch(
|
|
|
1723
1751
|
# NOTE(dev): Keep the docstring consistent between the Python API and CLI.
|
|
1724
1752
|
env = _merge_env_vars(env_file, env)
|
|
1725
1753
|
|
|
1754
|
+
if skip_image_check:
|
|
1755
|
+
os.environ['KONDUKTOR_SKIP_IMAGE_CHECK'] = '1'
|
|
1756
|
+
|
|
1726
1757
|
task = _make_task_with_overrides(
|
|
1727
1758
|
entrypoint=entrypoint,
|
|
1728
1759
|
name=name,
|
|
@@ -1737,6 +1768,7 @@ def serve_launch(
|
|
|
1737
1768
|
image_id=image_id,
|
|
1738
1769
|
env=env,
|
|
1739
1770
|
disk_size=disk_size,
|
|
1771
|
+
# serving stuff
|
|
1740
1772
|
min_replicas=min_replicas,
|
|
1741
1773
|
max_replicas=max_replicas,
|
|
1742
1774
|
ports=ports,
|