Skip to content

Commit 70a32d3

Browse files
committed
feat: introduce execution entrypoint
Signed-off-by: thxCode <[email protected]>
1 parent 0afc3a4 commit 70a32d3

File tree

4 files changed

+148
-21
lines changed

4 files changed

+148
-21
lines changed

gpustack_runtime/cmds/deployer.py

Lines changed: 53 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import sys
77
import time
88
from argparse import REMAINDER
9+
from pathlib import Path
910
from typing import TYPE_CHECKING
1011

1112
from .. import envs
@@ -84,6 +85,7 @@ class CreateRunnerWorkloadSubCommand(SubCommand):
8485

8586
backend: str
8687
device: str
88+
entrypoint: str
8789
port: int
8890
host_network: bool
8991
check: bool
@@ -115,6 +117,12 @@ def register(parser: _SubParsersAction):
115117
default="all",
116118
)
117119

120+
deploy_parser.add_argument(
121+
"--entrypoint-file",
122+
type=str,
123+
help="Path of entrypoint for the workload",
124+
)
125+
118126
deploy_parser.add_argument(
119127
"--port",
120128
type=int,
@@ -184,6 +192,13 @@ def __init__(self, args: Namespace):
184192
msg = "The name and volume arguments are required."
185193
raise ValueError(msg)
186194

195+
if args.entrypoint_file:
196+
entrypoint_file = Path(args.entrypoint_file)
197+
if not entrypoint_file.is_file():
198+
msg = f"The entrypoint file '{entrypoint_file}' does not exist."
199+
raise ValueError(msg)
200+
self.entrypoint = entrypoint_file.read_text(encoding="utf-8").strip()
201+
187202
def run(self):
188203
env = [
189204
ContainerEnv(
@@ -215,19 +230,20 @@ def run(self):
215230
]
216231
execution = ContainerExecution(
217232
privileged=True,
233+
entrypoint=self.entrypoint,
234+
args=self.extra_args,
218235
)
219-
if self.extra_args:
220-
execution.command = self.extra_args
221-
ports = None
222-
if self.port:
223-
ports = [
236+
ports = (
237+
[
224238
ContainerPort(
225239
internal=self.port,
226240
),
227241
]
228-
checks = None
229-
if self.check and self.port:
230-
checks = [
242+
if self.port
243+
else None
244+
)
245+
checks = (
246+
[
231247
ContainerCheck(
232248
delay=60,
233249
interval=10,
@@ -237,6 +253,9 @@ def run(self):
237253
teardown=True,
238254
),
239255
]
256+
if self.check and self.port
257+
else None
258+
)
240259
plan = WorkloadPlan(
241260
name=self.name,
242261
namespace=self.namespace,
@@ -296,6 +315,7 @@ class CreateWorkloadSubCommand(SubCommand):
296315

297316
backend: str
298317
device: str
318+
entrypoint: str
299319
port: int
300320
host_network: bool
301321
check: bool
@@ -326,6 +346,12 @@ def register(parser: _SubParsersAction):
326346
default="all",
327347
)
328348

349+
deploy_parser.add_argument(
350+
"--entrypoint-file",
351+
type=str,
352+
help="Path of entrypoint for the workload",
353+
)
354+
329355
deploy_parser.add_argument(
330356
"--port",
331357
type=int,
@@ -394,6 +420,13 @@ def __init__(self, args: Namespace):
394420
msg = "The name, image, and volume arguments are required."
395421
raise ValueError(msg)
396422

423+
if args.entrypoint_file:
424+
entrypoint_file = Path(args.entrypoint_file)
425+
if not entrypoint_file.is_file():
426+
msg = f"The entrypoint file '{entrypoint_file}' does not exist."
427+
raise ValueError(msg)
428+
self.entrypoint = entrypoint_file.read_text(encoding="utf-8").strip()
429+
397430
def run(self):
398431
env = [
399432
ContainerEnv(
@@ -425,19 +458,20 @@ def run(self):
425458
]
426459
execution = ContainerExecution(
427460
privileged=True,
461+
entrypoint=self.entrypoint,
462+
args=self.extra_args,
428463
)
429-
if self.extra_args:
430-
execution.command = self.extra_args
431-
ports = None
432-
if self.port:
433-
ports = [
464+
ports = (
465+
[
434466
ContainerPort(
435467
internal=self.port,
436468
),
437469
]
438-
checks = None
439-
if self.check and self.port:
440-
checks = [
470+
if self.port
471+
else None
472+
)
473+
checks = (
474+
[
441475
ContainerCheck(
442476
delay=60,
443477
interval=10,
@@ -447,6 +481,9 @@ def run(self):
447481
teardown=True,
448482
),
449483
]
484+
if self.check and self.port
485+
else None
486+
)
450487
plan = WorkloadPlan(
451488
name=self.name,
452489
namespace=self.namespace,

gpustack_runtime/deployer/__types__.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
)
2222
from .__utils__ import (
2323
correct_runner_image,
24+
fnv1a_32_hex,
2425
fnv1a_64_hex,
2526
is_rfc1123_domain_name,
2627
safe_json,
@@ -169,6 +170,18 @@ class ContainerExecution(ContainerSecurity):
169170
Attributes:
170171
working_dir (str | None):
171172
Working directory for the container.
173+
entrypoint (str | None):
174+
Entrypoint content for the container,
175+
only work if `readonly_rootfs` is False.
176+
When it is set, it will override the `command` field.
177+
For example,
178+
179+
```
180+
#!/bin/sh
181+
182+
echo "Hello, World!"
183+
exec /path/to/bin "$@"
184+
```
172185
command (list[str] | None):
173186
Command to run in the container.
174187
args (list[str] | None):
@@ -191,6 +204,20 @@ class ContainerExecution(ContainerSecurity):
191204
"""
192205
Working directory for the container.
193206
"""
207+
entrypoint: str | None = None
208+
"""
209+
Entrypoint content for the container,
210+
only work if `readonly_rootfs` is False.
211+
When it is set, it will override the `command` field.
212+
For example,
213+
214+
```
215+
#!/bin/sh
216+
217+
echo "Hello, World!"
218+
exec /path/to/bin "$@"
219+
```
220+
"""
194221
command: list[str] | None = None
195222
"""
196223
Command to run in the container.
@@ -974,6 +1001,22 @@ def validate_and_default(self):
9741001
c.restart_policy = ContainerRestartPolicyEnum.NEVER
9751002
elif not c.restart_policy:
9761003
c.restart_policy = ContainerRestartPolicyEnum.ALWAYS
1004+
# Mutate container entrypoint.
1005+
if c.execution and c.execution.entrypoint:
1006+
if c.execution.readonly_rootfs:
1007+
msg = f"Readonly rootfs Container '{c.name}' cannot have an entrypoint."
1008+
raise ValueError(msg)
1009+
entrypoint_script_name = f"/gpustack-entrypoint-{fnv1a_32_hex(c.name)}"
1010+
entrypoint_file = ContainerFile(
1011+
path=entrypoint_script_name,
1012+
mode=0o755,
1013+
content=c.execution.entrypoint,
1014+
)
1015+
if not c.files:
1016+
c.files = []
1017+
c.files.append(entrypoint_file)
1018+
c.execution.command = [entrypoint_script_name] # Override command.
1019+
c.execution.entrypoint = None
9771020
# Add default registry if needed.
9781021
if (
9791022
envs.GPUSTACK_RUNTIME_DEPLOY_DEFAULT_REGISTRY

gpustack_runtime/deployer/docker.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -600,16 +600,14 @@ def _create_pause_container(
600600
# TODO(thxCode): check if the container matches the spec
601601
return container
602602

603-
security_opt = [
604-
"no-new-privileges",
605-
]
606603
privileged = any(
607604
c.execution.privileged
608605
for c in workload.containers
609606
if c.profile == ContainerProfileEnum.RUN and c.execution
610607
)
611-
if privileged:
612-
security_opt.append("label=disable")
608+
security_opt = [
609+
"no-new-privileges",
610+
]
613611

614612
create_options: dict[str, Any] = {
615613
"name": container_name,
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
#!/bin/sh
2+
# vim:sw=4:ts=4:et
3+
4+
set -e
5+
6+
entrypoint_log() {
7+
if [ -z "${NGINX_ENTRYPOINT_QUIET_LOGS:-}" ]; then
8+
echo "$@"
9+
fi
10+
}
11+
12+
if [ "$1" = "nginx" ] || [ "$1" = "nginx-debug" ]; then
13+
if /usr/bin/find "/docker-entrypoint.d/" -mindepth 1 -maxdepth 1 -type f -print -quit 2>/dev/null | read v; then
14+
entrypoint_log "$0: /docker-entrypoint.d/ is not empty, will attempt to perform configuration"
15+
16+
entrypoint_log "$0: Looking for shell scripts in /docker-entrypoint.d/"
17+
find "/docker-entrypoint.d/" -follow -type f -print | sort -V | while read -r f; do
18+
case "$f" in
19+
*.envsh)
20+
if [ -x "$f" ]; then
21+
entrypoint_log "$0: Sourcing $f";
22+
. "$f"
23+
else
24+
# warn on shell scripts without exec bit
25+
entrypoint_log "$0: Ignoring $f, not executable";
26+
fi
27+
;;
28+
*.sh)
29+
if [ -x "$f" ]; then
30+
entrypoint_log "$0: Launching $f";
31+
"$f"
32+
else
33+
# warn on shell scripts without exec bit
34+
entrypoint_log "$0: Ignoring $f, not executable";
35+
fi
36+
;;
37+
*) entrypoint_log "$0: Ignoring $f";;
38+
esac
39+
done
40+
41+
entrypoint_log "$0: Configuration complete; ready for start up"
42+
else
43+
entrypoint_log "$0: No files found in /docker-entrypoint.d/, skipping configuration"
44+
fi
45+
fi
46+
47+
echo "Hello GPUStack Runtime Deployer!"
48+
49+
exec "$@"

0 commit comments

Comments
 (0)