Przejdź do głównej zawartości

Usługi HTTP

Użyj @app.inference(), aby uruchomić usługę przyjmującą żądania HTTP pod adresem udostępnionym przez CGC. SDK tworzy zasób, w razie potrzeby czeka na jego uruchomienie, konfiguruje porty i zwraca obiekt DeployedEndpoint.

Poniższy przykład uruchamia prostą usługę do analizy tekstu w kontenerze z Pythonem.

Zdefiniuj usługę​

from cgc.sdk import App, Image, ShellCommand


RESOURCE_NAME = "text-analysis-service"
PORT = 8000
PORT_NAME = "api"

app = App(
name="http-service-workflow",
image=Image.from_registry("python:3.11-slim"),
)


@app.inference(
entity="custom",
name=RESOURCE_NAME,
port=PORT,
port_name=PORT_NAME,
cpu=1,
memory=2,
)
def text_analysis_service():
code = r"""
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
import json


class Handler(BaseHTTPRequestHandler):
def _send_json(self, status_code, payload):
body = json.dumps(payload).encode("utf-8")
self.send_response(status_code)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)

def do_GET(self):
if self.path in {"/health", "/api/health"}:
self._send_json(200, {"status": "ok"})
return
self._send_json(404, {"error": "not found", "path": self.path})

def do_POST(self):
if self.path not in {"/analyze", "/api/analyze"}:
self._send_json(404, {"error": "not found", "path": self.path})
return

length = int(self.headers.get("Content-Length", "0"))
payload = json.loads(self.rfile.read(length) or b"{}")
text = payload.get("text", "")
words = text.split()
self._send_json(
200,
{
"input": text,
"characters": len(text),
"words": len(words),
"uppercase": text.upper(),
"first_five_words": words[:5],
},
)

def log_message(self, format, *args):
print(format % args, flush=True)


ThreadingHTTPServer(("0.0.0.0", 8000), Handler).serve_forever()
"""
return ShellCommand(executable=("python", "-c", code))

entity="custom" oznacza, że CGC uruchamia obraz wybrany w App. Dekorowana funkcja wykonuje się lokalnie i zwraca polecenie, które CGC uruchamia w kontenerze. ShellCommand przechowuje program i jego argumenty oraz zabezpiecza je przed niezamierzoną interpretacją przez powłokę.

Serwer musi nasłuchiwać na 0.0.0.0 oraz na tym samym porcie, który podano w port. W tym przykładzie jest to port 8000.

Zainstaluj zależności przy uruchamianiu​

Jeśli potrzebujesz dodatkowych pakietów, zainstaluj je w ShellCommand.prelude przed uruchomieniem głównego procesu:

return ShellCommand(
executable=("python", "-m", "my_service"),
arguments=("--host 0.0.0.0", "--port 8000"),
prelude="python -m pip install --quiet fastapi 'uvicorn[standard]'",
)

Polecenia z prelude wykonują się przy każdym uruchomieniu kontenera. Zainstalowane pakiety nie są zapisywane w obrazie. Stałe zależności najlepiej umieścić we własnym obrazie, a prelude wykorzystywać do instalacji dodatkowych pakietów podczas pracy nad aplikacją.

Uruchom usługę i wyślij żądanie​

endpoint = text_analysis_service.deploy(
wait=True,
reuse_existing=False,
resource_timeout_seconds=600,
)

print("Endpoint:", endpoint.url)
endpoint.wait_http("/health", timeout_seconds=300)

result = endpoint.post_json(
"/analyze",
{"text": "CGC SDK workflows can run HTTP services."},
)
print(result)

deploy() najpierw tworzy zasób, a następnie konfiguruje zadeklarowany port. wait=True czeka na gotowość zasobu i odświeża jego adres publiczny. Przy reuse_existing=False istniejący zasób o tej samej nazwie powoduje błąd. Wartość True pozwala go ponownie użyć, ale nie aktualizuje jego obrazu, polecenia ani środowiska.

wait_http() czeka, aż sama aplikacja zacznie przyjmować żądania. Jest to coś innego niż wait=True: zasób może mieć stan Running, zanim serwer HTTP będzie gotowy.

URL endpointu może już zawierać prefiks ścieżki ingress wynikający z port_name. Metody wait_http() i post_json() dodają podaną ścieżkę do tego URL-a. Aplikacja musi udostępniać trasy zgodne z wynikową ścieżką. Pełny przykład przyjmuje trasy bez prefiksu, takie jak /health, oraz trasy z prefiksem, takie jak /api/health.

Argumenty entrypointu własnego obrazu​

Zamiast polecenia powłoki lub obiektu ShellCommand funkcja z entity="custom" może zwracać listę argumentów:

from cgc.sdk import App, Image

args_app = App("argument-demo", image=Image.from_registry("python:3.12-slim"))

@args_app.inference(name="argument-demo", port=8000, port_name="http")
def serve_with_arguments():
return ["python", "-m", "http.server", "8000"]

plan = serve_with_arguments.deploy(dry_run=True)

Listę przetwarza backend. Ograniczenia dotyczące spacji i powtarzających się kluczy opisano w opisie API. Dla złożonych poleceń użyj ShellCommand.

Dodaj więcej portów​

Użyj ServicePort, kiedy jeden zasób nasłuchuje na więcej niż jednym porcie:

from cgc.sdk import App, Image, ServicePort, ShellCommand

multi_app = App("multi-port-demo", image=Image.from_registry("python:3.12-slim"))

@multi_app.inference(
name="multi-port-service", port=8000, port_name="http",
additional_ports=[
ServicePort("metrics", 9090, ingress=False),
ServicePort("internal", 5557, ingress=False),
],
)
def service():
code = r"""
import json
import threading
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer

class Handler(BaseHTTPRequestHandler):
def do_GET(self):
body = json.dumps({"port": self.server.server_port, "status": "ok"}).encode()
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)

for port in (8000, 9090, 5557):
server = ThreadingHTTPServer(("0.0.0.0", port), Handler)
threading.Thread(target=server.serve_forever, daemon=True).start()
threading.Event().wait()
"""
return ShellCommand(executable=("python", "-u", "-c", code))

plan = service.deploy(dry_run=True)

SDK najpierw tworzy zasób, czeka na stan Running, a następnie dodaje port główny i porty dodatkowe. Nazwy i numery portów muszą być unikalne. Każdy port musi mieścić się w zakresie od 1 do 65535. Ustawienie ingress=False pozostawia port bez publicznego adresu. Samo dodanie portu nie wystarcza; aplikacja działająca w kontenerze musi na nim nasłuchiwać.

Jeśli konfiguracja portów zakończy się błędem, SDK zgłasza wyjątek i pozostawia zasób, aby można było go sprawdzić lub samodzielnie usunąć.

Przy dodatkowych portach SDK czeka na stan Running nawet wtedy, gdy ustawisz wait=False. Jeśli główny port ma ingress=False, nie używaj endpoint.url, wait_http() ani post_json(). Porty wewnętrzne są dostępne z zadań w tej samej przestrzeni nazw Kubernetes.

Użyj szablonu zasobu CGC​

Dla szablonu pomiń image i startup_command. Poniższa deklaracja używa ustawień szablonu Nginx:

from cgc.sdk import App

template_app = App("template-service")
resource = template_app.resource(
entity="nginx", name="template-nginx", cpu=1, memory=1,
)
plan = resource.create(dry_run=True)

Szablon wskazany w entity musi być dostępny w wybranym środowisku. Jego argumenty przekazuj przez App.resource(arguments=[...]) albo zwracaj jako listę w funkcji App.inference(entity=...).

Sprawdź status, odczytaj logi i usuń usługę​

print(endpoint.status())
print(endpoint.logs(timestamps=True))
endpoint.delete()

Uruchom pełny przykład z usunięciem usługi po zakończeniu:

python text_inference.py --delete

Pełny skrypt znajdziesz na stronie Przykłady workflow. Aby skorzystać z gotowych API modeli, przejdź do gotowych workflow.