← До програми курсу

Модуль 18 / 18

Фінальний проєкт: production-ready контейнерна платформа

DOCKER

Зміст
01

1. Фінальний проєкт і Definition of Done

Спроєктуйте, зберіть, захистіть, запустіть і прийміть single-host platform із gateway та application. Результат відтворюється з файлів, не ручного стану host.

  • non-root image
  • назовні лише gateway
  • internal backend network
  • readiness керує startup
  • read-only і cap_drop
  • log limits
  • crash recovery
  • evidence, runbook і rollback
02

2. Архітектура та межі

Client звертається до loopback gateway. Gateway має frontend і backend networks; app — лише internal backend. DNS використовує ім’я app, не container IP.

Compose придатний для production на одному host, але не дає multi-host HA, scheduler чи rolling updates. Далі потрібні Swarm, Kubernetes або managed platform.

03

3. Build, configuration та release

Dockerfile створює UID 10001 та exec-form CMD. Pipeline фіксує base digest, створює SBOM/provenance, scan і signature release digest.

Deploy отримує перевірений immutable image. Secrets не входять до image, environment чи Git; їх доставляє secret manager.

04

4. Надійність та lifecycle

Healthcheck визначає readiness, depends_on усуває startup race, restart policy обробляє crash. Unhealthy сам не перезапускає standalone container.

App приймає SIGTERM. Під час restart можливий короткий 502; production HA потребує replicas та orchestrator.

05

5. Security та network policy

Сервіси non-root, read-only, no-new-privileges і cap_drop ALL. Writable paths — лише tmpfs. Loopback port не відкриває service зовнішній мережі.

Internal network не є повною firewall policy: перевіряйте routes, DNS, host rules і Docker socket.

06

6. Observability, backup та rollback

Evidence: resolved config, digest, health, restart count, resources, logs і networks. Alerts: SLO, saturation, restarts, disk bytes/inodes, certificates.

Rollback розгортає попередній digest. Stateful service потребує consistent backup, restore drill, RPO/RTO та сумісних migrations.

07

7. Методичні вказівки

  • Відтворіть файли точно
  • Не лікуйте privileged mode
  • Перевірте resolved config
  • Фіксуйте expected result
  • Evidence до cleanup
  • Owner/severity alerts
  • Deploy і rollback як команди
  • Обмеження single-host
08

8. Лабораторна робота: production-ready stack

Створіть app із health, controlled crash і SIGTERM, non-root image та gateway.

terminal
rm -rf /tmp/docker-m18
mkdir -p /tmp/docker-m18/app /tmp/docker-m18/nginx
cat > /tmp/docker-m18/app/server.py <<'PY'
import json, os, signal
from http.server import BaseHTTPRequestHandler, HTTPServer
class Handler(BaseHTTPRequestHandler):
    def do_GET(self):
        if self.path == "/health": body=b'{"status":"ok"}'
        elif self.path == "/crash":
            self.send_response(202); self.end_headers(); self.wfile.write(b"crashing\n"); self.wfile.flush(); os._exit(42)
        else: body=json.dumps({"service":"docker-capstone","pid":os.getpid()}).encode()
        if self.path != "/crash":
            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)
    def log_message(self, fmt, *args): print(fmt % args, flush=True)
server=HTTPServer(("0.0.0.0",8080),Handler); server.timeout=.5; stopping=False
def stop(signum, frame):
    global stopping; stopping=True
signal.signal(signal.SIGTERM,stop); signal.signal(signal.SIGINT,stop)
while not stopping: server.handle_request()
server.server_close()
PY
cat > /tmp/docker-m18/app/Dockerfile <<'EOF'
# syntax=docker/dockerfile:1
FROM python:3.13-alpine
RUN addgroup -S app && adduser -S -G app -u 10001 app
WORKDIR /app
COPY --chown=10001:10001 server.py .
USER 10001:10001
EXPOSE 8080
CMD ["python","server.py"]
EOF
cat > /tmp/docker-m18/nginx/nginx.conf <<'EOF'
pid /tmp/nginx.pid;
events {}
http {
  access_log /dev/stdout;
  error_log /dev/stderr warn;
  client_body_temp_path /tmp/client_temp;
  proxy_temp_path /tmp/proxy_temp;
  fastcgi_temp_path /tmp/fastcgi_temp;
  uwsgi_temp_path /tmp/uwsgi_temp;
  scgi_temp_path /tmp/scgi_temp;
  server {
    listen 8080;
    location / { proxy_pass http://app:8080; proxy_set_header Host $host; }
  }
}
EOF

Опишіть hardening, networks, readiness, logs та lifecycle.

terminal
cat > /tmp/docker-m18/compose.yaml <<'EOF'
name: docker-m18
services:
  app:
    build: ./app
    image: docker-m18-app:1.0.0
    init: true
    read_only: true
    tmpfs: [/tmp]
    restart: on-failure:3
    stop_grace_period: 10s
    healthcheck:
      test: ["CMD","python","-c","import urllib.request; urllib.request.urlopen('http://127.0.0.1:8080/health')"]
      interval: 2s
      timeout: 1s
      retries: 5
    security_opt: [no-new-privileges:true]
    cap_drop: [ALL]
    networks: [backend]
    logging: &logs
      driver: json-file
      options: {max-size: "1m", max-file: "2"}
  gateway:
    image: nginx:1.29-alpine
    user: "101:101"
    entrypoint: ["nginx","-g","daemon off;"]
    read_only: true
    tmpfs: ["/tmp:mode=1777"]
    depends_on:
      app: {condition: service_healthy}
    ports: ["127.0.0.1:18080:8080"]
    volumes: ["./nginx/nginx.conf:/etc/nginx/nginx.conf:ro"]
    security_opt: [no-new-privileges:true]
    cap_drop: [ALL]
    restart: unless-stopped
    networks: [frontend, backend]
    logging: *logs
networks:
  frontend: {}
  backend: {internal: true}
EOF

Validate, build та wait readiness.

terminal
cd /tmp/docker-m18
docker compose config -q
docker compose build
docker compose up --detach --wait
curl --fail --silent http://127.0.0.1:18080/ | jq
docker compose ps
docker inspect docker-m18-app-1 --format 'user={{.Config.User}} readonly={{.HostConfig.ReadonlyRootfs}} caps={{json .HostConfig.CapDrop}} security={{json .HostConfig.SecurityOpt}}'

Ініціюйте exit 42 та доведіть recovery.

terminal
before=$(docker inspect --format '{{.RestartCount}}' docker-m18-app-1)
curl --silent http://127.0.0.1:18080/crash || true
for attempt in 1 2 3 4 5 6 7 8 9 10; do
  after=$(docker inspect --format '{{.RestartCount}}' docker-m18-app-1)
  status=$(docker inspect --format '{{.State.Status}}' docker-m18-app-1)
  [ "$after" -gt "$before" ] && [ "$status" = running ] && break
  sleep 1
done
printf 'before=%s after=%s status=%s\n' "$before" "$after" "$status"
test "$after" -gt "$before"
for attempt in 1 2 3 4 5; do
  curl --fail --silent http://127.0.0.1:18080/health && break
  sleep 1
done
09

9. Приймання та діагностика

Доведіть: який artifact запущено, UID, external surface, writable paths, health і recovery.

terminal
cd /tmp/docker-m18
docker compose config
docker compose images
docker compose ps
docker compose logs --since 10m
docker stats --no-stream
docker network inspect docker-m18_backend
docker inspect docker-m18-app-1 --format '{{json .State}}' | jq
curl -sS -D - -o /dev/null http://127.0.0.1:18080/health
  • config — resolved model
  • build — BuildKit vertex
  • unhealthy — health/app logs
  • 502 — backend DNS/readiness/restart
  • permission — path/UID/capability
  • no space — logs/cache/layers/volumes/inodes
10

10. Захист проєкту та cleanup

  • Architecture/data flow
  • Обґрунтування controls
  • Single-host failure domains
  • Crash/recovery evidence
  • Digest promotion
  • SLI/SLO та alerts
  • Backup/restore state
  • Rollback rehearsal
terminal
cd /tmp/docker-m18
docker compose down --remove-orphans
docker image rm docker-m18-app:1.0.0
rm -rf /tmp/docker-m18
docker network ls --filter name=docker-m18
docker ps -a --filter name=docker-m18

Курс завершено, якщо platform відтворюється з clean host, проходить acceptance, дає evidence та чесно описує limitations.