Kubernetes
April 3, 2024

Kubernetes. Контейнеры

Зачем?

Зачем и почему придумали контейнеризацию? Какую проблему это решает?

Конфликты зависимостей

Перенесёмся на 10 лет назад. Мы хотим разместить наш веб сервис на сервере. Устанавливаем java/php/python/perl кто что любит. Дистрибутив интерпретатора или JVM тянет за собой на сервер десятки зависимостей (библиотеки, утилиты, и т. д.). Затем нужно установить СУБД. Например postgres/mysql/oracle. Установка базы тоже тянет за собой ворох библиотек. Скорее всего всё успешно встаёт. Но вот наступает момент, нам нужно установить что-то менее популярное. Например, какой-то особый парсер. И у него в зависимостях есть новая версия python 3.6. А все остальные сервисы требуют версию не выше 3.5. И мы получаем ошибку:

$ sudo apt-get install super-parser
Reading package lists... Done
Building dependency tree       
Reading state information... Done
Some packages could not be installed. This may mean that you have
requested an impossible situation or if you are using the unstable
distribution that some required packages have not yet been created
or been moved out of Incoming.
The following information may help to resolve the situation:

The following packages have unmet dependencies:
 super-parser : Depends: python3.6 (>= 3.6.9-1~) but it is not going to be installed
E: Unable to correct problems, you have held broken packages.

Пример достаточно синтетический, но отражает проблемы, которые случаются при установке разнообразного ПО в одну операционную систему.

Изолированная среда

PHP мастера очень знакомы с такой ситуацией. Поставили сайтик на любимом Wordpress. А через неделю сервер взломан, майнит крипту и заблокировал все доступы. Тут есть две проблемы.

Первая, что PHP из коробки никак не мешает модифицировать исполняемый код. Например, злоумышленник заливает через формочку своего профиля на сайте фотографию. А это оказывается вовсе не фотография, а PHP скрипт. И его можно выполнить извне руками самого злоумышленника. А в этом скрипте может быть вообще всё что угодно.

И тут подключается вторая проблема: теоретически из такого скрипта можно завладеть доступом ко всему серверу. А дальше как угодно: украсть данные, удалить данные, спрятать вредоносный код, который будет незаметно годами делать тёмные дела, и т. д.

Виртуализация

Когда появились виртуальные машины, они начали частично решать первые две проблемы. Но создали новую. Особенно она становится заметна, когда мы пытаемся изолировать каждый маленький сервис в свою виртуальную машину.

Виртуальная машина для своего существования требует некоторое количество ресурсов (диск, оперативная память, процессор). И чем меньше сам сервис потребляет ресурсов, тем расточительнее становятся затраты виртуальной машины.

Решение

Контейнеризация — запуск приложения в изолированной среде, использующий ресурсы ядра операционной системы.

Хоть до Docker уже были созданы другие решения, но именно Docker стал паровозом контейнеризации.

Сравнение с виртуализацией

Виртуализация
Контейнеризация

Главное отличие контейнеризации от виртуализации — контейнеры пользуются ресурсами ядра родительской операционной системы, тогда как виртуальные машины запускают полноценные операционные системы с собственными ядрами.

Что такое Docker?

Под этим термином обычно понимают три сущности:

Сервис

Фоновое приложение, которое управляет контейнерами. Взаимодействие с ним происходит через HTTP API.

API

HTTP API для управления сервисом. Спецификацию можно найти на официальном сайте: https://docs.docker.com/engine/api/v1.43/. Через команды этого API осуществляется всё взаимодействие с сервисом Docker: создание контейнеров, запуск, просмотр логов и всё остальное.

Терминальный клиент

Терминальный (или консольный) клиент, который преобразует консольные команды в API запросы и отправляет их Docker сервису. На экран же выводит результаты таких команд.

Например:

$ docker info
Client: Docker Engine - Community
 Version:    24.0.5
 Context:    default
 Debug Mode: falcker/cli-plugins/docker-scout

Server:
 Containers: 0
  Running: 0
  Paused: 0
  Stopped: 0
 Images: 0
 Server Vers
...

Под капотом клиент отправляет HTTP запрос к Docker сервису:

GET /info HTTP/1.1
Accept: */*
Host: 0.0.0.0:2375
Connection: keep-alive
 
HTTP/1.1 200 OK
Api-Version: 1.43
Content-Type: application/json
Docker-Experimental: false
Ostype: linux
Server: Docker/24.0.5 (linux)
Transfer-Encoding: chunked
 
{"ID":"d4114630-d857-44cd-b47c-e36ebbcb4bea","Containers":0,"ContainersRunning":0,"ContainersPaused":0,"ContainersStopped":0,"Images":0,"Driver":"overlay2","DriverStatus":[["Backing Filesystem","extfs"],["Supports d_type","true"],["Using metacopy","false"],["Native Overlay Diff","true"],["userxattr","false"]],"Plugins":{"Volume":["local"],"Network":["bridge","host","ipvlan","macvlan","null","overlay"],"Authorization":null,"Log":["awslogs","fluentd","gcplogs","gelf","journald","json-file","local","logentries","splunk","syslog"]},"MemoryLimit":true,"SwapLimit":true,"KernelMemoryTCP":true,"CpuCfsPeriod":true,"CpuCfsQuota":true,"CPUShares":true,"CPUSet":true,"PidsLimit":true,"IPv4Forwarding":true,"BridgeNfIptables":true,"BridgeNfIp6tables":true,"Debug":false,"NFd":161,"OomKillDisable":true,"NGoroutines":301,"SystemTime":"2024-04-03T13:02:44.179502329Z","LoggingDriver":"json-file","CgroupDriver":"cgroupfs","CgroupVersion":"1","NEventsListener":127,"KernelVersion":"5.15.146.1-microsoft-standard-WSL2","OperatingSystem":"Docker Desktop","OSVersion":"","OSType":"linux","Architecture":"x86_64","IndexServerAddress":"https://index.docker.io/v1/","RegistryConfig":{"AllowNondistributableArtifactsCIDRs":null,"AllowNondistributableArtifactsHostnames":null,"InsecureRegistryCIDRs":["127.0.0.0/8"],"IndexConfigs":{"docker.io":{"Name":"docker.io","Mirrors":[],"Secure":true,"Official":true},"hubproxy.docker.internal:5555":{"Name":"hubproxy.docker.internal:5555","Mirrors":[],"Secure":false,"Official":false}},"Mirrors":null},"NCPU":8,"MemTotal":33541914624,"GenericResources":null,"DockerRootDir":"/var/lib/docker","HttpProxy":"http.docker.internal:3128","HttpsProxy":"http.docker.internal:3128","NoProxy":"hubproxy.docker.internal","Name":"docker-desktop","Labels":[],"ExperimentalBuild":false,"ServerVersion":"24.0.5","Runtimes":{"io.containerd.runc.v2":{"path":"runc"},"runc":{"path":"runc"}},"DefaultRuntime":"runc","Swarm":{"NodeID":"","NodeAddr":"","LocalNodeState":"inactive","ControlAvailable":false,"Error":"","RemoteManagers":null},"LiveRestoreEnabled":false,"Isolation":"","InitBinary":"docker-init","ContainerdCommit":{"ID":"3dce8eb055cbb6872793272b4f20ed16117344f8","Expected":"3dce8eb055cbb6872793272b4f20ed16117344f8"},"RuncCommit":{"ID":"v1.1.7-0-g860f061","Expected":"v1.1.7-0-g860f061"},"InitCommit":{"ID":"de40ad0","Expected":"de40ad0"},"SecurityOptions":["name=seccomp,profile=unconfined"],"Warnings":["WARNING: No blkio throttle.read_bps_device support","WARNING: No blkio throttle.write_bps_device support","WARNING: No blkio throttle.read_iops_device support","WARNING: No blkio throttle.write_iops_device support","WARNING: daemon is not using the default seccomp profile"]}

Термины

Image (образ)

Это "снимок системы, из которого создаются контейнеры. Можно ещё представить его как шаблон для создания контейнеров.

Container (контейнер)

Изолированная среда для запуска какого-либо сервиса или команды. Напоминает виртуальную машину, котороя использует ресурсы ядра родительской операционной системы.

Registry

Это репозиторий Docker образов. Разработчик публикует в него свой образ, а пользователь из него образ скачивает.