linuxlab.io
Учебники▾
  • Линукс и сети
    Файловая система, процессы, TCP/IP, BGP и OSPF
    →
  • Terraform и IaC
    HCL, state, plan/apply на sandbox LocalStack
    →
  • Git и GitHub
    Объектная модель, plumbing, ветвление, GitHub Actions
    →
Все учебники →
ЦеныО платформеВойтиСоздать аккаунт
/
  • Введение
  • Уроки
  • How it works
  • Симулятор
  • База знаний
  • Собеседование
Index
Categories
All entries
Footer
linuxlab-УчебникиЦеныО платформеКонфиденциальность и куки
Copyright © 2026 LinuxLab. Все права защищены.
home/linux/kb/Контейнеры (бонус)/oci-spec

kb/containers ── Контейнеры (бонус) ── intermediate

OCI spec - стандарт контейнеров

OCI - три спеки: Image (слои + manifest), Runtime (config.json + rootfs для runc), Distribution (registry API). Стандарт после Docker'а; runc, podman, containerd, CRI-O - всё OCI-compatible.

view as markdownaka: oci, oci-image, oci-runtime, oci-distribution, container-spec

Зачем OCI

В 2015 Docker согласился вынести спеки контейнеров из своего проекта, чтобы экосистема не зависела от одного вендора. Появилась Open Container Initiative (под Linux Foundation), которая поддерживает три отдельные спецификации:

СпекаЧто описывает
OCI Imageформат image на диске: layers + manifest + config
OCI Runtimeкак runtime запускает контейнер из rootfs + config.json
OCI DistributionHTTP API registry для push/pull

Сегодня "Docker container", почти синоним "OCI container": Dockerfile → image → registry → runtime, каждый шаг по OCI. Альтернативные runtime'ы ([[runc-and-runsc|runc/runsc]]), registry'и (Harbor, GHCR, Quay), build-tool'ы (buildah, kaniko) все работают с одним форматом.

OCI Image, что это на диске

Image, это набор файлов на диске, не tarball. Структура:

myimage/
├── oci-layout                        ← {"imageLayoutVersion": "1.0.0"}
├── index.json                        ← root, ссылается на manifest'ы
└── blobs/
    └── sha256/
        ├── <hash-config>             ← config (JSON)
        ├── <hash-layer1>             ← layer (tar или tar.gz)
        ├── <hash-layer2>
        └── <hash-manifest>           ← manifest (связывает config + layers)

index.json

json
{
  "schemaVersion": 2,
  "manifests": [
    {
      "mediaType": "application/vnd.oci.image.manifest.v1+json",
      "digest": "sha256:abc...",
      "size": 1234,
      "platform": { "architecture": "amd64", "os": "linux" }
    },
    {
      "digest": "sha256:def...",
      "platform": { "architecture": "arm64", "os": "linux" }
    }
  ]
}

→ multi-arch index. Один manifest на платформу.

Manifest

json
{
  "schemaVersion": 2,
  "mediaType": "application/vnd.oci.image.manifest.v1+json",
  "config": {
    "mediaType": "application/vnd.oci.image.config.v1+json",
    "digest": "sha256:abc...",
    "size": 7000
  },
  "layers": [
    { "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
      "digest": "sha256:111...", "size": 5000000 },
    { "digest": "sha256:222...", "size": 1000000 },
    { "digest": "sha256:333...", "size":   50000 }
  ]
}

Manifest содержит:

  • config, environment, entrypoint, ENV, USER, WORKDIR
  • layers, упорядоченный список tarball'ов

Config

json
{
  "architecture": "amd64",
  "os": "linux",
  "config": {
    "User": "1000:1000",
    "Env": ["PATH=/usr/bin:/bin"],
    "Entrypoint": ["/app/server"],
    "Cmd": ["--port=8080"],
    "WorkingDir": "/app",
    "ExposedPorts": { "8080/tcp": {} }
  },
  "rootfs": {
    "type": "layers",
    "diff_ids": [
      "sha256:layer1-uncompressed-hash",
      "sha256:layer2-uncompressed-hash"
    ]
  },
  "history": [ ... ]
}

Layers, основа image-deduplication

Каждый слой, diff относительно предыдущего: добавленные/изменённые файлы как tar-archive. Удалённые файлы, .wh.<filename> whiteout-маркеры.

При deploy registry загружает только отсутствующие слои. Если 100 image'ей основаны на одном ubuntu:22.04, слой Ubuntu хранится один раз. На клиенте/registry storage экономия колоссальная.

Layers применяются через [[tmpfs-overlayfs|overlayfs]]: lower-stack из read-only слоёв + upper для container-writes.

Build vs Pull

bash
# Build из Dockerfile
docker build -t myimage:v1 .
# Push в registry
docker push registry.example.com/myimage:v1
# Pull
docker pull registry.example.com/myimage:v1
# Без Docker, buildah / podman
buildah bud -t myimage:v1 .
buildah push myimage:v1 docker://registry.example.com/myimage:v1

buildah/podman не требуют daemon'а, работают как обычные CLI и пишут OCI-совместимые images.

OCI Runtime, config.json + rootfs

Runtime берёт bundle: директорию с

bundle/
├── config.json           ← всё про контейнер (mounts, namespaces, args)
└── rootfs/               ← extracted layers, готовая FS-tree
    ├── bin/
    ├── etc/
    └── usr/

и запускает контейнер. Это не image, это разобранный image плюс runtime-config. Image нужно "распаковать" в bundle прежде чем runtime может с ним работать (это делает runtime supervisor containerd, CRI-O).

config.json, что внутри

json
{
  "ociVersion": "1.2.0",
  "process": {
    "args": ["/app/server", "--port=8080"],
    "cwd": "/app",
    "env": ["PATH=/usr/bin:/bin"],
    "user": { "uid": 1000, "gid": 1000 },
    "capabilities": {
      "bounding": ["CAP_NET_BIND_SERVICE"],
      "effective": ["CAP_NET_BIND_SERVICE"],
      "permitted": ["CAP_NET_BIND_SERVICE"]
    },
    "noNewPrivileges": true,
    "rlimits": [ { "type": "RLIMIT_NOFILE", "hard": 65535, "soft": 65535 } ]
  },
  "root": { "path": "rootfs", "readonly": false },
  "mounts": [
    { "destination": "/proc", "type": "proc", "source": "proc" },
    { "destination": "/dev", "type": "tmpfs", "source": "tmpfs", "options": ["mode=755", "size=65536k"] },
    { "destination": "/data", "type": "bind", "source": "/var/lib/myapp/data", "options": ["bind", "ro"] }
  ],
  "linux": {
    "namespaces": [
      { "type": "pid" },
      { "type": "network" },
      { "type": "mount" },
      { "type": "uts" },
      { "type": "ipc" },
      { "type": "user" }
    ],
    "cgroupsPath": "system.slice:myapp:abc123",
    "resources": {
      "memory": { "limit": 268435456 },
      "cpu":    { "shares": 1024, "quota": 50000, "period": 100000 }
    },
    "seccomp": { "defaultAction": "SCMP_ACT_ALLOW", ... }
  }
}

Это полное описание контейнера: что запустить, какие namespaces, какие cgroups лимиты, какие capabilities, какой seccomp-profile.

OCI Distribution, registry API

HTTP API registry'ев. Главные endpoint'ы:

GET  /v2/                                   ← ping
GET  /v2/<name>/tags/list                   ← список тегов
GET  /v2/<name>/manifests/<reference>        ← manifest по tag/digest
GET  /v2/<name>/blobs/<digest>               ← скачать слой/config
POST /v2/<name>/blobs/uploads/               ← начать upload
PUT  /v2/<name>/manifests/<reference>        ← залить manifest
  • <name>, image name (library/ubuntu, myorg/myapp)
  • <reference>, tag (v1.0) или digest (sha256:...)

Любой OCI-registry (Docker Hub, GHCR, Harbor, Quay, ECR, GCR, ACR, GitLab Registry) реализует это API. Pull/push кросс-совместим.

Аутентификация, Bearer-token, обычно через OAuth2 token-server.

bash
# Сырой запрос к registry
curl -H "Accept: application/vnd.oci.image.manifest.v1+json" \
     https://registry-1.docker.io/v2/library/alpine/manifests/latest

skopeo, низкоуровневая работа с OCI

bash
# Скопировать image между registry'ями без локальной разборки
skopeo copy docker://registry.example.com/app:v1 \
            docker://other-registry.com/app:v1
# Inspect manifest без pull
skopeo inspect docker://nginx:latest
# Сохранить как OCI-layout
skopeo copy docker://nginx:latest oci:/tmp/nginx-oci:latest
ls /tmp/nginx-oci/                          # классическая OCI-структура

Tags vs digest, immutability

  • Tag (nginx:1.25), мутабельный pointer; latest особенно
  • Digest (nginx@sha256:abc...), immutable, хеш manifest'а

В production-deploy всегда по digest, не по tag. Tag можно переписать в registry; digest, нет (изменишь content → изменится hash).

bash
# Получить digest текущего tag'а
docker inspect --format='{{index .RepoDigests 0}}' nginx:1.25

▸nginx@sha256:abcdef...

# Зафиксировать в Dockerfile / k8s manifest
FROM nginx@sha256:abcdef...

Когда что-то пошло не так

  • manifest unknown при pull, tag не существует или удалён из registry. skopeo list-tags docker://registry/repo.
  • Multi-arch image не подтянулся, container runtime не нашёл подходящего platform-manifest. docker pull --platform=linux/arm64.
  • unauthorized, нет токена или expire. docker login, проверь credentials в ~/.docker/config.json / ~/.config/containers/auth.json.
  • Image build долгий каждый раз, нет layer caching. Build-cache invalidates при изменении любой строки выше; ставь RUN apt-get install после COPY package*.json чтобы переиспользовать слой.
  • OCI vs Docker manifest schema, старые registry отдают v1 manifest, modern, v2 OCI. Большинство клиентов умеют оба, но некоторые server-side validators могут падать.
  • Digest mismatch при air-gapped transfer, после gzip-перепаковки layer'а его SHA256 меняется → manifest invalid. Используй skopeo или сохраняй в OCI-layout.

Альтернативные форматы (на любителя)

  • AppImage / Snap / Flatpak, для desktop, не контейнеры в OCI-смысле
  • Singularity / Apptainer (.sif), научные кластеры, single-file image
  • WASM components, ещё не контейнеры в OCI, но движется в эту сторону (некоторые runtime'ы запускают WASM через OCI-config)

§ команды

bash
skopeo inspect docker://nginx:latest

Manifest и config image'а без pull - быстрый view

bash
skopeo copy docker://src/app:v1 oci:/tmp/app-oci:v1

Сохранить image в OCI-layout на диск - переносной формат

bash
docker manifest inspect --verbose nginx:latest

Multi-arch index и каждый platform-manifest

bash
docker inspect --format='{{index .RepoDigests 0}}' nginx:1.25

Digest текущего tag - для immutable-pinning в prod

bash
umoci unpack --image image:tag bundle/

Распаковать OCI-image в OCI-runtime-bundle (config.json + rootfs)

bash
buildah bud -t myapp:v1 .

Build OCI-image без Docker daemon - rootless-friendly

bash
podman pull --platform=linux/arm64 nginx:latest

Принудительная архитектура при pull - для cross-arch dev

§ см. также

  • runc-and-runscrunc, runsc, kata - container runtimesrunc - стандартный OCI-runtime, namespaces+cgroups+seccomp. runsc/gVisor - userspace-ядро для дополнительной изоляции. kata - облегчённая VM на контейнер. Performance ↔ isolation trade-off.
  • docker-storage-driversDocker storage drivers - overlay2, btrfs, zfsStorage driver - как Docker хранит image-layers и контейнер-changes на диске. overlay2 default (overlayfs над ext4/xfs), btrfs/zfs через subvolumes/snapshots, fuse-overlayfs для rootless.
  • tmpfs-overlayfstmpfs и overlayfs - RAM-disk и слоиtmpfs - ФС в RAM (с swap'ом). Дефолт для /run, /dev/shm, часто /tmp. overlayfs - lower + upper = merged: RO-база + RW-слой над ней. Основа Docker (overlay2), Live-ISO, immutable distro.
  • namespacesLinux namespacesNamespaces - механизм ядра, который даёт процессу собственный изолированный view на ресурс (сеть, mount-points, PID, UID, IPC, hostname, time). На них построены все контейнеры.
  • kubernetes-pod-lifecycleKubernetes pod lifecycle - от Pending до TerminatedPod проходит фазы Pending → Running → Succeeded/Failed/Unknown. Init-containers выполняются последовательно до основных. Probes: startup → readiness/liveness. SIGTERM + grace period при удалении.
  • image-signing-cosignCosign и подпись container images (sigstore)cosign подписывает container images. Sigstore = ekosistema: rekor (transparency log), fulcio (CA для keyless OIDC). Подписи хранятся как OCI-объекты рядом с image. Verify - часть admission control в k8s через policy-controller или Kyverno.
Footer
linuxlab-
Copyright © 2026 LinuxLab. Все права защищены.
Учебники
Цены
О платформе
Конфиденциальность и куки