О том, как реализовать докер на 100 строк

Linux контейнер Командная строка Docker
О том, как реализовать докер на 100 строк

Недавно случайно нашел на гитхабе очень хороший проект.bocker, был реализован простой докер с сотнями строк кода.Потом я его посмотрел и нашел очень интересным.Я просто поиграл в него и внес некоторые изменения(проект давно не обновлялся и есть места которые не поддерживается), Просто проанализируйте и поделитесь.

предисловие

я посмотрел на100Написать докер в одну строку точно невозможно, раньше это выглядело как самый простой питон плюс зависимости требовали бы сотен строк кода, напримерmoker, и есть тысячи строк для идеальной реализации gomydocker, но, взглянув на этот проект, в нем действительно всего более 100 строк, но он используетshell, Но помните, что более 100 строк должны быть сделаны только с оболочкой.Если вы не знакомы с оболочкой, вы можете прочитать некоторые базовые знания о оболочке.

В настоящее время этот проект в основном реализуется вИзвлечение изображения, просмотр изображения, запуск контейнера, удаление контейнера, представление контейнера, ограничение ресурсов контейнера, удаление изображения,функции одни из самых основных,а недоделок много.Здесь я грубо разберу принципы их реализации,разберу каждый процесс,и разберу их нормально по порядку операций.В первую очередь обсуждаемая здесь ситуация это linux рекомендуется использовать centos7 и для систем выше ubuntu14 процесс на самом деле относительно прост.базовая реализация опирается на некоторые основные компоненты linux, iptables, cgroup и пространство имен linux для полной сети, ограничения ресурсов, изоляции ресурсов и используйте оболочку для управления этими ресурсами.

Начать!!

Среда конфигурации

предпочтительноvagrant(Если это mac и windows, то рекомендуется использовать эту среду, если linux, если ядро ​​системы выше, можно напрямую управлять),vagrantЭто может помочь нам реализовать облегченную среду разработки.Очень нравится.Он работает и управляет vm,и с более тяжелыми средами иметь дело удобнее.Здесь среду нужно настроить заранее.Официальный адрес я прикрепил в ссылке.

Есть проблема с источником данных epel официального Vagrantfile, и сеть зависима.Весь процесс автоматизирован, но для отладки не удобен.Для облегчения личной отладки я написал процесс пошагово, которым будет удобнее работать.

Загрузите виртуальную среду (бродячий файл конфигурации)

Создать файл конфигурации Vagrant

Запуск бродячей конфигурации

$script = <<SCRIPT
(
echo "echo start---config"
) 2>&1
SCRIPT
Vagrant.configure(2) do |config|
config.vm.box = 'puppetlabs/centos-7.0-64-nocm'
config.ssh.username = 'root'
config.ssh.password = 'puppet'
config.ssh.insert_key = 'true'
config.vm.provision 'shell', inline: $script
end

拷贝上边的文件Vim为保存到一个文件中Vagrantfile中
vagrant up (直接启动,这里会去源拉去centos的镜像,时长主要根据个人网络)

vagrant ssh (直接进入)

Установить зависимости

  • Установите источник rpm:
wget https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm
rpm -ivh epel-release-latest-7.noarch.rpm(官方用的eprl源不存在了)
  • Тогда соответствующие зависимости:

Ядро cgourp, btrfs-progs


yum install -y -q autoconf automake btrfs-progs docker gettext-devel git libcgroup-tools libtool python-pip
jq
  • Создайте смонтированную файловую систему: (файловая структура, поддерживаемая образом докера) Подробнее см. по ссылке btrfs wiki


fallocate -l 10G ~/btrfs.img
mkdir /var/bocker
mkfs.btrfs ~/btrfs.img
mount -o loop ~/btrfs.img /var/bocker
  • Установить базу:
pip install git+https://github.com/larsks/undocker
systemctl start docker.service
docker pull centos
docker save centos | undocker -o base-image
  • Установите linux-utils инструмент для Linux
git clone https://github.com/karelzak/util-linux.git
cd util-linux
git checkout tags/v2.25.2
./autogen.sh
./configure --without-ncurses --without-python
make
mv unshare /usr/bin/unshare
  • Настройка сетевой карты и сетевой переадресации
echo 1 > /proc/sys/net/ipv4/ip_forward
iptables --flush
iptables -t nat -A POSTROUTING -o bridge0 -j MASQUERADE
iptables -t nat -A POSTROUTING -o enp0s3 -j MASQUERADE
ip link add bridge0 type bridge
ip addr add 10.0.0.1/24 dev bridge0
ip link set bridge0 up


Позвольте мне кратко объяснить описанный выше процесс.Поскольку базовая сеть докеров будет реализована с использованием iptables и пространства имен linux, это необходимо для нормальной работы сети контейнеров, которая в основном разделена на две части.

1 Сначала нужно создать виртуальную сетевую картуbridge0, а затем настройте преобразование nat-адреса сетевой карты bridge0, где мост эквивалентен адресу в докере.docker0, bridge0 эквивалентен устройству коммутатора уровня 2 в сети.Он может подключаться к различным сетевым устройствам.Когда запрос достигает устройства моста, он может транслировать и пересылать сообщение через MAC-адрес сообщения, поэтому весь контейнер виртуальный сетевые карты должны быть под мостом., который также является соединениемnamespaceсетевое оборудование ихост-сетьметод, здесь будет объяснение. (при необходимости реализацииoverlayи т. д., вам нужно переключиться на более продвинутый инструмент преобразования, такой как использование ovs для классовvxlan, greпреобразование протокола)

2 Включите переадресацию ядра и настройте iptablesMASQUERADE, это использование правила MASQUERADE для преобразования ip контейнера в ip выходной сетевой карты хоста.В пространстве имен linux при запросе внешнего адреса хоста замените исходный адрес в пространстве имен хостом как исходный адрес, так что вы можете адрес конвертируется нормально в пространстве имен.

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

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

Здесь я добавил некоторые китайские комментарии к коду для простоты понимания.Этот проект называется bocker, и меня также называют bocker.

  • порт входа в программу
[[ -z "${1-}" ]] && bocker_help "$0"
    # @1 执行与help
case $1 in
    pull|init|rm|images|ps|run|exec|logs|commit|cleanup) bocker_"$1" "${@:2}" ;;
    *) bocker_help "$0" ;;
esac

Справка относительно проста, вход в программу, логика аналогична основной функции в нашей программе, которая выполняет различные функции в соответствии с входящими параметрами.

  • Рабочая среда?Зеркальная тяга Бокер тяга ()
function bocker_pull() { #HELP Pull an image from Docker Hub:\nBOCKER pull <name> <tag>


    # @1 获取对应镜像进行拉去, 源代码老版本是v1的docker registry是无效的, 我更新为了v2版本
    token=$(curl "https://auth.docker.io/token?service=registry.docker.io&scope=repository:library/$1:pull"  | jq '.token'| sed 's/\"//g')
    registry_base='https://registry-1.docker.io/v2'
    tmp_uuid="$(uuidgen)" && mkdir /tmp/"$tmp_uuid"

    # @2 获取docker镜像每一层的layter,保存到数组中
    manifest=$(curl -sL -H "Authorization: Bearer $token" "$registry_base/library/$1/manifests/$2" | jq -r '.fsLayers' | jq -r '.[].blobSum' )
    [[ "${#manifest[@]}" -lt 1 ]] && echo "No image named '$1:$2' exists" && exit 1

    # @3 依次获取镜像每一层, 然后init
    for id in ${manifest[@]}; do
        curl -#L -H "Authorization: Bearer $token" "$registry_base/library/$1/blobs/$id" -o /tmp/"$tmp_uuid"/layer.tar
        tar xf /tmp/"$tmp_uuid"/layer.tar -C /tmp/"$tmp_uuid"
    done
    echo "$1:$2" > /tmp/"$tmp_uuid"/img.source
    bocker_init /tmp/"$tmp_uuid" && rm -rf /tmp/"$tmp_uuid"
}

В этом проекте просто реализован докер, поэтому репозиторий образов докеров точно не реализован.Репозиторий образов по-прежнему использует официальный исходник.Если вам нужно использовать свой приватный исходник, то нужно внести изменения в исходники и код образа.Логика здесь заключается в загрузке соответствующего изображения.Слоистый,Потомсохранитьна собственное файловое зеркало,Здесь я изменил его логику и использовал версию API реестра докеров v2., (т.к. у автора истек код версии исходников v1, корректные данные получить у официалов невозможно, автор три года не подавал, да и скорость разработки докеров слишком быстрая, это понятно), процесс первый для авторизации, для получения соответствующих разрешений для соответствующего образа Сделайте токен, а затем используйте токен для получения каждого слоя зеркала.Здесь я использую плагин jq json parsing, будет удобнее работать с Джейсоном, конвертировать его в переменные, связанные с оболочкой, а затем загрузите все слои и выгрузите их в их собственный уникальный зеркальный каталог, и одновременно сохраните изображение с именем файла.

  • бокер сохранить изображение


function bocker_init() { #HELP Create an image from a directory:\nBOCKER init 
# @1 生成随机数镜像,就像生成docker images 唯一id
uuid="img_$(shuf -i 42002-42254 -n 1)"
if [[ -d "$1" ]]; then
    [[ "$(bocker_check "$uuid")" == 0 ]] && bocker_run "$@"

# @2 创建对应image文件 btrfs volume
    btrfs subvolume create "$btrfs_path/$uuid" > /dev/null
    cp -rf --reflink=auto "$1"/* "$btrfs_path/$uuid" > /dev/null
    [[ ! -f "$btrfs_path/$uuid"/img.source ]] && echo "$1" > "$btrfs_path/$uuid"/img.source
    echo "Created: $uuid"
else
    echo "No directory named '$1' exists"
fi
}

Вот собственно сохранено и вытащено с зеркального складаlayer, а затем создайте каталог.Здесь следует подчеркнуть, что каталог изображений, используемый докером, должен бытьbtrfsфайловую структуру, а затем сохранить соответствующее имя образа в файл img.source, где среда была создана с помощью команды btrfs при подготовкефайловая система 10g,Docker поддерживает различные системы хранения, подробности можно найти здесь.

Драйверы хранилища Docker​

docs.docker.com图标

.

  • С изображением вы можете выполнить важный прогон бокера (Часть 1)
function bocker_run() { #HELP Create a container:\nBOCKER run <image_id> <command>

    # @1 环境准备,生成唯一id,检查相关镜像,ip, mac地址
    uuid="ps_$(shuf -i 42002-42254 -n 1)"
    [[ "$(bocker_check "$1")" == 1 ]] && echo "No image named '$1' exists" && exit 1
    [[ "$(bocker_check "$uuid")" == 0 ]] && echo "UUID conflict, retrying..." && bocker_run "$@" && return
    cmd="${@:2}" && ip="$(echo "${uuid: -3}" | sed 's/0//g')" && mac="${uuid: -3:1}:${uuid: -2}"

    # @2 通过ip link && ip netns 实现隔离的网络namespace与网络通信
    ip link add dev veth0_"$uuid" type veth peer name veth1_"$uuid"
    ip link set dev veth0_"$uuid" up
    ip link set veth0_"$uuid" master bridge0
    ip netns add netns_"$uuid"
    ip link set veth1_"$uuid" netns netns_"$uuid"
    ip netns exec netns_"$uuid" ip link set dev lo up
    ip netns exec netns_"$uuid" ip link set veth1_"$uuid" address 02:42:ac:11:00"$mac"
    ip netns exec netns_"$uuid" ip addr add 10.0.0."$ip"/24 dev veth1_"$uuid"
    ip netns exec netns_"$uuid" ip link set dev veth1_"$uuid" up
    ip netns exec netns_"$uuid" ip route add default via 10.0.0.1
    btrfs subvolume snapshot "$btrfs_path/$1" "$btrfs_path/$uuid" > /dev/null

Разобрать:

При запуске bocker run будут выполняться некоторые конфигурации столбцов. Я также добавил и прокомментировал. Первая часть сначала сгенерирует соответствующую конфигурацию. Во-первых, уникальный идентификатор каждого bocker будет сгенерирован с помощью функции shuf, и соответствующая легальность будет проверяется, а затем в соответствии с сгенерированным случайным числом id перехватывается, и некоторые поля перехватываются для формирования ip-адреса и mac-адреса (Обратите внимание, что здесь могут быть вероятностные конфликты ip, которые следует оптимизировать позже).

Вторая часть, создающаяLinux vethПравильно (Veth появляется парами на виртуальных сетевых устройствах, и запрос на отправку виртуального устройства Veth будет отправлен с виртуального устройства на другом конце. В сценарии контейнерной виртуализации Veth часто используется для подключения к разным пространствам имен), используйте команда ip Создать пару veth0_xx, veth1_xx, создать уникальныйuuid namespace, привяжите veth1 к пространству имен, чтобысвяжи меняp, mac адрес, затем привязать маршрут, запустить сетевую карту, сетевой интерфейс, пара veth, используемая здесь, вы можете просто понять это как подключение к сетевому кабелю и проиллюстрировать это.



Тогда этот проводОба конца здесь являются устройствами в пространстве имен,Другой конец - хост, Давайте проанализируем структурную схему здесь.Вы можете видеть, что у докера есть eth0, а у хоста есть veth.Они являются парой veth.



Это позволит бокеру в контейнере нормально выходить в интернет.

  • Ограничения ресурсов bocker run (Часть 2)
# @3 更改nameserver, 保存cmd
    echo 'nameserver 8.8.8.8' > "$btrfs_path/$uuid"/etc/resolv.conf
    echo "$cmd" > "$btrfs_path/$uuid/$uuid.cmd"

    # @4 通过cgroup-tools工具配置cgroup资源组与调整资源限制
    cgcreate -g "$cgroups:/$uuid"
    : "${BOCKER_CPU_SHARE:=512}" && cgset -r cpu.shares="$BOCKER_CPU_SHARE" "$uuid"
    : "${BOCKER_MEM_LIMIT:=512}" && cgset -r memory.limit_in_bytes="$((BOCKER_MEM_LIMIT * 1000000))" "$uuid"

    # @5 执行
    cgexec -g "$cgroups:$uuid" \
        ip netns exec netns_"$uuid" \
        unshare -fmuip --mount-proc \
        chroot "$btrfs_path/$uuid" \
        /bin/sh -c "/bin/mount -t proc proc /proc && $cmd" \
        2>&1 | tee "$btrfs_path/$uuid/$uuid.log" || true
    ip link del dev veth0_"$uuid"
    ip netns del netns_"$uuid"


Здесь для удобства работы мы используемcgroupинструменты для ограничения ресурсов,cgroupЭто инструмент ограничения ресурсов процесса, который поставляется с linux, соответствующие подробности есть в ссылке. используется здесьcgroup-toolsИнструментальная операция cgroup будет относительно простой.Здесь cgcreate используется для увеличения CPU, set и mem для лимита, а группа cgroup создается случайно созданной библиотекой id.package)

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



Наконец, используйте cgroup exec для запуска исполнителя запуска и вывода вывода в каталог журнала через tee.

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

Здесь можно добиться бокерского пробега, ниже приведены некоторые детали.

  • чистый сетевой интерфейс
function bocker_cleanup() { #HELP Delete leftovers of improperly shutdown containers:\nBOCKER cleanup
    # @1 清楚所有的相关网络接口
    for ns in $(ip netns show | grep netns_ps_); do [[ ! -d "$btrfs_path/${ns#netns_}" ]] && ip netns del "$ns"; done
    for iface in $(ifconfig | grep veth0_ps_ | awk '{ print $1 }'); do [[ ! -d "$btrfs_path/${iface#veth0_}" ]] && ip link del dev "$iface"; done
}

ps извлеките соответствующую сетевую карту и удалите соответствующий сетевой интерфейс

  • Просмотр журналов контейнеров
function bocker_logs() { #HELP View logs from a container:\nBOCKER logs <container_id>

    # @1 查看日志
    [[ "$(bocker_check "$1")" == 1 ]] && echo "No container named '$1' exists" && exit 1
    cat "$btrfs_path/$1/$1.log"
}

Все журналы сохраняются в подкаталоге, соответствующем файловой системе BTRFS.$btrfs_path/$uuid, это соответствует btrfs_path, так что вам нужно только получить правильный каталог и указать файл.

Есть еще несколько простых команд, которые я не буду разбирать, они относительно просты, вы можете сами перейти по ссылке, указанной в начале, и скачать исходный код, соответствующий изменениям кода в моей статье.

Суммировать:

В целом, этот проект использует шелл и реализует небольшую часть основных функций докера, фреймворк есть, а 99% функций не реализованы, такие как межхостовое взаимодействие, переадресация портов, сопоставление портов, исключение обработка и т. д. Подождите, но как учебный проект, он может заставить людей блестеть глаза.Также вы можете реализовать простой докер по идеям этого проекта, я думаю, это не будет сложно.