<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Infrastructure on kenji.blog</title><link>http://kenji.blog/ru/categories/infrastructure/</link><description>Recent content in Infrastructure on kenji.blog</description><generator>Hugo -- gohugo.io</generator><language>ru</language><copyright>kenjinote</copyright><lastBuildDate>Sun, 13 Sep 2026 07:00:00 +0900</lastBuildDate><atom:link href="http://kenji.blog/ru/categories/infrastructure/index.xml" rel="self" type="application/rss+xml"/><item><title>Hyper-V против WSL2: сравнение технологий виртуализации на Windows</title><link>http://kenji.blog/ru/p/hyper-v-vs-wsl2-windows-virtualization/</link><pubDate>Sun, 13 Sep 2026 07:00:00 +0900</pubDate><guid>http://kenji.blog/ru/p/hyper-v-vs-wsl2-windows-virtualization/</guid><description>&lt;img src="http://kenji.blog/p/hyper-v-vs-wsl2-windows-virtualization/img/eyecatch.jpg" alt="Featured image of post Hyper-V против WSL2: сравнение технологий виртуализации на Windows" />&lt;h2 id="1-введение-эволюция-виртуализации-в-windows">1. Введение: Эволюция виртуализации в Windows
&lt;/h2>&lt;p>Технологии виртуализации на платформе Windows претерпели кардинальные изменения за последние несколько десятилетий. Ранее доминировали гипервизоры 2-го типа от сторонних разработчиков (такие как VMware Workstation и VirtualBox), но с момента внедрения Microsoft «Hyper-V» в Windows Server 2008 гипервизоры 1-го типа стали встраиваться и в настольные ОС Windows 10/11.&lt;/p>
&lt;p>В последние годы наибольшее внимание разработчиков привлекает «WSL2 (Windows Subsystem for Linux 2)». В то время как WSL1 полагалась на трансляцию системных вызовов, WSL2 использует технологию Hyper-V для создания «легковесной служебной ВМ» (Lightweight Utility VM), обеспечивая полную совместимость с Linux и значительный прирост производительности.&lt;/p>
&lt;p>В этой статье мы подробно сравним и объясним с глубокими техническими деталями две мощные технологии виртуализации: полнофункциональную «Hyper-V» и ориентированную на разработчиков «WSL2», рассматривая их архитектуру, производительность (CPU, память, дисковый ввод-вывод), сетевую конфигурацию и оптимальные сценарии использования.&lt;/p>
&lt;hr>
&lt;h2 id="2-базовая-теория-гипервизоров-и-сравнение-архитектур">2. Базовая теория гипервизоров и сравнение архитектур
&lt;/h2>&lt;p>Для понимания технологий виртуализации необходимо разобраться в классификации типов гипервизоров (мониторов виртуальных машин, VMM).&lt;/p>
&lt;h3 id="21-различия-между-гипервизорами-1-го-и-2-го-типа">2.1. Различия между гипервизорами 1-го и 2-го типа
&lt;/h3>&lt;p>Гипервизор — это программный слой, который абстрагирует доступ к оборудованию и позволяет нескольким операционным системам (гостевым ОС) выполняться одновременно на одной физической машине.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Тип 1 (Bare-metal, автономный)&lt;/strong>: Выполняется непосредственно на оборудовании. Концепции хост-ОС не существует (строго говоря, может существовать управляющая ОС с привилегиями), имеет крайне низкие накладные расходы и обеспечивает высокую производительность и безопасность. Примеры: Hyper-V, VMware ESXi, Xen.&lt;/li>
&lt;li>&lt;strong>Тип 2 (Хостовый)&lt;/strong>: Выполняется как приложение в хост-ОС (например, Windows или macOS). Все обращения к оборудованию проходят через хост-ОС, что увеличивает накладные расходы. Примеры: VMware Workstation, Oracle VirtualBox.&lt;/li>
&lt;/ul>
&lt;p>Hyper-V в Windows является чистым &lt;strong>гипервизором 1-го типа&lt;/strong>. При включении Hyper-V фактически сама операционная система Windows, с которой взаимодействует пользователь, начинает работать внутри специальной виртуальной машины, называемой «корневым разделом» (Root Partition).&lt;/p>
&lt;h3 id="22-детали-архитектуры-hyper-v">2.2. Детали архитектуры Hyper-V
&lt;/h3>&lt;p>Архитектура Hyper-V использует дизайн микроядра и основана на логических единицах изоляции, называемых разделами (Partitions).&lt;/p>
&lt;pre class="mermaid">
graph TD
A[&amp;#34;Оборудование (CPU, RAM, Диск, NIC)&amp;#34;] --&amp;gt; B[&amp;#34;Windows Hypervisor (Кольцо -1)&amp;#34;]
B --&amp;gt; C[&amp;#34;Корневой раздел (Windows OS)&amp;#34;]
B --&amp;gt; D[&amp;#34;Дочерний раздел 1 (Windows VM)&amp;#34;]
B --&amp;gt; E[&amp;#34;Дочерний раздел 2 (Linux VM)&amp;#34;]
C --&amp;gt; F[&amp;#34;VMBus (Шина виртуальных машин)&amp;#34;]
D --&amp;gt; F
E --&amp;gt; F
C --&amp;gt; G[&amp;#34;VID (Драйвер инфраструктуры виртуализации)&amp;#34;]
C --&amp;gt; H[&amp;#34;VMWP.exe (Рабочий процесс)&amp;#34;]
&lt;/pre>
&lt;ul>
&lt;li>&lt;strong>Windows Hypervisor&lt;/strong>: Работает в состоянии процессора с наивысшим уровнем привилегий (Кольцо -1 или VMX Root Mode) и отвечает только за выделение памяти и планирование CPU. Он не содержит драйверов устройств.&lt;/li>
&lt;li>&lt;strong>Корневой раздел (Root Partition)&lt;/strong>: Раздел, в котором работает хост-ОС Windows. Содержит все драйверы устройств и напрямую управляет оборудованием. Также предоставляет функции управления дочерними разделами (WMI-провайдеры, VMWP.exe и др.).&lt;/li>
&lt;li>&lt;strong>Дочерний раздел (Child Partition)&lt;/strong>: Раздел, в котором работает гостевая ОС. Прямой доступ к оборудованию не разрешен, запросы на ввод-вывод (Synthetic I/O) отправляются в корневой раздел через логическую шину совместного использования памяти, называемую «VMBus».&lt;/li>
&lt;/ul>
&lt;h3 id="23-wsl2-и-устройство-lightweight-utility-vm">2.3. WSL2 и устройство Lightweight Utility VM
&lt;/h3>&lt;p>WSL2 использует ту же базовую технологию гипервизора 1-го типа, что и Hyper-V, но задействует подмножество функций, называемое «Платформа виртуальных машин» (Virtual Machine Platform, VMP), которое отличается от полнофункциональных виртуальных машин Hyper-V.&lt;/p>
&lt;p>«Легковесная служебная ВМ» (Lightweight Utility VM), применяемая в WSL2, полностью исключает эмуляцию устаревшего оборудования (такого как виртуальный BIOS или виртуальная материнская плата), присутствующую в традиционных ВМ.&lt;/p>
&lt;pre class="mermaid">
graph TD
A[&amp;#34;Хост-ОС Windows (Пользовательское пространство)&amp;#34;]
B[&amp;#34;Файловая система NTFS&amp;#34;]
C[&amp;#34;Сервер протокола 9P (Plan 9)&amp;#34;]
D[&amp;#34;Легковесная служебная ВМ (Ядро Linux)&amp;#34;]
E[&amp;#34;ext4.vhdx (Виртуальный диск)&amp;#34;]
F[&amp;#34;Пользовательское пространство Linux (Дистрибутивы WSL2)&amp;#34;]
A --&amp;gt; C
C --&amp;gt;| Кросс-ОС обмен файлами | D
D --&amp;gt; E
D --&amp;gt; F
&lt;/pre>
&lt;p>Главная особенность WSL2 — это &lt;strong>скорость запуска&lt;/strong> и &lt;strong>бесшовная интеграция с хост-ОС&lt;/strong>. Ядро Linux загружается менее чем за секунду, а доступ к файловой системе Windows (NTFS) осуществляется через протокол сетевой файловой системы &lt;code>9P&lt;/code> из Plan 9.&lt;/p>
&lt;hr>
&lt;h2 id="3-подробный-анализ-производительности-вычислительные-ресурсы-и-ввод-вывод">3. Подробный анализ производительности: Вычислительные ресурсы и ввод-вывод
&lt;/h2>&lt;p>Производительность виртуальной машины выражается как сумма накладных расходов в компонентах CPU, памяти и дискового ввода-вывода.&lt;/p>
&lt;h3 id="31-cpu-и-накладные-расходы-на-переключение-контекста">3.1. CPU и накладные расходы на переключение контекста
&lt;/h3>&lt;p>Hyper-V и WSL2 используют аппаратную виртуализацию (Intel VT-x / AMD-V). Инструкции процессора в основном выполняются с нативной скоростью, но при выполнении привилегированных инструкций или обработке ввода-вывода возникает прерывание «VM Exit», приводящее к переключению контекста на гипервизор.&lt;/p>
&lt;p>Накладные расходы CPU $T_{overhead}$ в этот момент можно описать следующей математической моделью:&lt;/p>
$$ T_{overhead} = \sum_{i=1}^{N} (t_{vm\_exit} + t_{hypercall\_process} + t_{vm\_entry}) $$&lt;p>Где:&lt;/p>
&lt;ul>
&lt;li>$N$: Количество возникновений VM Exit за единицу времени&lt;/li>
&lt;li>$t_{vm\_exit}$: Время перехода от гостя к гипервизору&lt;/li>
&lt;li>$t_{hypercall\_process}$: Время обработки ввода-вывода или прерываний через VMBus&lt;/li>
&lt;li>$t_{vm\_entry}$: Время возврата от гипервизора к гостю&lt;/li>
&lt;/ul>
&lt;p>Поскольку в WSL2 отсутствует эмуляция устаревшего оборудования, параметр $t_{hypercall\_process}$ крайне мал и оптимизирован. Поэтому при чистых вычислениях на CPU (например, компиляция ядра или вывод моделей машинного обучения) снижение производительности по сравнению с bare-metal средой составляет в пределах нескольких процентов.&lt;/p>
&lt;h3 id="32-механизм-выделения-памяти">3.2. Механизм выделения памяти
&lt;/h3>&lt;p>В методах управления памятью у двух систем есть четкие различия в философии проектирования.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Hyper-V (Динамическая память)&lt;/strong>: В зависимости от потребностей гостевой ВМ в памяти корневой раздел динамически выделяет и возвращает память. Однако память, зарезервированная как страничный кэш (page cache) внутри гостевой ОС, обычно не освобождается, пока система не столкнется с нехваткой ресурсов.&lt;/li>
&lt;li>&lt;strong>WSL2 (Динамическое освобождение памяти)&lt;/strong>: WSL2 имеет собственный механизм, который регулярно возвращает (Reclaim) ненужную память (включая кэш) из ВМ Linux хосту Windows. В ранних версиях WSL2 существовала проблема, когда страничный кэш Linux съедал память Windows (разрастание процесса Vmmem), но сейчас это исправлено патчами ядра.&lt;/li>
&lt;/ul>
&lt;h3 id="33-характеристики-дискового-ввода-вывода-vhdx-против-ext4vhdx">3.3. Характеристики дискового ввода-вывода (VHDX против ext4.vhdx)
&lt;/h3>&lt;p>Дисковый ввод-вывод чаще всего становится узким местом в производительности виртуальных машин.&lt;/p>
&lt;p>Задержка ввода-вывода (Latency) $L_{total}$ рассчитывается следующим образом:&lt;/p>
$$ L_{total} = L_{guest\_fs} + L_{vmbus} + L_{host\_fs} + L_{physical\_disk} $$&lt;p>&lt;strong>В случае Hyper-V&lt;/strong>:
Типичный гость Hyper-V использует виртуальный диск в формате &lt;code>VHDX&lt;/code>. Запросы на ввод-вывод от файловой системы внутри гостевой ОС (ext4 или NTFS) проходят через драйвер блочного устройства хранилища VMBus (storvsc) и обрабатываются как доступ к файлу VHDX на NTFS на стороне Windows.&lt;/p>
&lt;p>&lt;strong>В случае WSL2&lt;/strong>:
Дистрибутивы Linux в WSL2 работают на нативной файловой системе ext4, созданной внутри выделенного файла &lt;code>ext4.vhdx&lt;/code>. Файловые операции внутри Linux (например, в директории &lt;code>~&lt;/code>) демонстрируют нативную производительность, эквивалентную Hyper-V, описанному выше.
Однако &lt;strong>когда осуществляется доступ из Linux в WSL2 к файлам на стороне Windows (например, &lt;code>/mnt/c/&lt;/code>)&lt;/strong>, или наоборот, процесс значительно отличается. Для этого кросс-ОС доступа используется &lt;code>9P (Plan 9 File System Protocol)&lt;/code>.&lt;/p>
$$ L_{cross\_os} = L_{9p\_client} + L_{socket\_transfer} + L_{9p\_server} + L_{ntfs} $$&lt;p>Доступ через этот протокол 9P имеет высокие накладные расходы на сериализацию, поэтому производительность значительно падает (иногда в 10 и более раз) в сценариях, требующих массового чтения и записи небольших файлов (например, &lt;code>npm install&lt;/code> или операции Git в проекте Node.js, расположенном в каталоге Windows).
Поэтому &lt;strong>при использовании WSL2 железное правило — всегда размещать файлы проекта в нативной файловой системе Linux (внутри &lt;code>~/&lt;/code>)&lt;/strong>.&lt;/p>
&lt;hr>
&lt;h2 id="4-сетевая-структура-nat-default-switch-bridged">4. Сетевая структура: NAT, Default Switch, Bridged
&lt;/h2>&lt;p>Гибкость сетевых функций — одно из главных отличий между Hyper-V и WSL2.&lt;/p>
&lt;h3 id="41-сеть-wsl2-на-базе-nat">4.1. Сеть WSL2 (На базе NAT)
&lt;/h3>&lt;p>Сеть WSL2 по умолчанию настроена как «NAT (Network Address Translation)», используя технологию виртуальных коммутаторов Hyper-V.
ВМ Linux автоматически получает приватный IP-адрес (например, &lt;code>172.20.x.x&lt;/code>), отличный от адреса хоста Windows. Предусмотрен встроенный механизм переадресации, благодаря которому сервисы (порты), запущенные в WSL2, доступны с хоста Windows через &lt;code>localhost&lt;/code>, что позволяет разработчикам тестировать веб-серверы, не задумываясь о сети.&lt;/p>
&lt;p>Недавно в предварительных версиях WSL2 был представлен новый сетевой режим «Mirrored» (Зеркальный). Он улучшает поддержку IPv6 и совместимость с VPN-соединениями (настраивается в &lt;code>.wslconfig&lt;/code>).&lt;/p>
&lt;h3 id="42-виртуальный-коммутатор-hyper-v-virtual-switch">4.2. Виртуальный коммутатор Hyper-V (Virtual Switch)
&lt;/h3>&lt;p>Hyper-V позволяет создавать сложные сети корпоративного уровня. Через «Диспетчер виртуальных коммутаторов» доступны три основных режима:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Внешняя (External)&lt;/strong>: Привязывает физическую сетевую карту (NIC) хост-машины к виртуальному коммутатору, подключая гостевую ВМ напрямую к физической сети (мостовое соединение). ВМ получает IP-адрес от DHCP-сервера в той же подсети, что и физическая сеть.&lt;/li>
&lt;li>&lt;strong>Внутренняя (Internal)&lt;/strong>: Разрешает связь только между хост-ОС и ВМ, а также между самими ВМ. Прямой выход во внешнюю сеть невозможен.&lt;/li>
&lt;li>&lt;strong>Частная (Private)&lt;/strong>: Разрешает связь только между ВМ и блокирует связь с хост-ОС. Используется для создания изолированных тестовых сред.&lt;/li>
&lt;/ol>
&lt;h3 id="43-продвинутая-настройка-сети-hyper-v-с-помощью-powershell">4.3. Продвинутая настройка сети Hyper-V с помощью PowerShell
&lt;/h3>&lt;p>Если вам нужно создать кастомизированную NAT-сеть для ВМ в среде разработки или тестирования, PowerShell предоставляет детальный контроль. Ниже приведен пример скрипта для создания внутреннего виртуального коммутатора, настройки на нем NAT и предоставления ВМ доступа в Интернет.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-powershell" data-lang="powershell">&lt;span class="line">&lt;span class="cl">&lt;span class="c"># 1. Создание внутреннего виртуального коммутатора&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$SwitchName&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;HyperV-NatSwitch&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">New-VMSwitch&lt;/span> &lt;span class="n">-SwitchName&lt;/span> &lt;span class="nv">$SwitchName&lt;/span> &lt;span class="n">-SwitchType&lt;/span> &lt;span class="n">Internal&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c"># 2. Настройка IP-адреса для виртуальной NIC хоста (IP, который станет шлюзом)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$GatewayIP&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;192.168.100.1&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$NetPrefix&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="mf">24&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$InterfaceAlias&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;vEthernet (&lt;/span>&lt;span class="nv">$SwitchName&lt;/span>&lt;span class="s2">)&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">New-NetIPAddress&lt;/span> &lt;span class="n">-IPAddress&lt;/span> &lt;span class="nv">$GatewayIP&lt;/span> &lt;span class="n">-PrefixLength&lt;/span> &lt;span class="nv">$NetPrefix&lt;/span> &lt;span class="n">-InterfaceAlias&lt;/span> &lt;span class="nv">$InterfaceAlias&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c"># 3. Настройка сети NAT&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$NatName&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;HyperV-NatNetwork&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">$NatSubnet&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;192.168.100.0/24&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">New-NetNat&lt;/span> &lt;span class="n">-Name&lt;/span> &lt;span class="nv">$NatName&lt;/span> &lt;span class="n">-InternalIPInterfaceAddressPrefix&lt;/span> &lt;span class="nv">$NatSubnet&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c"># Команда для проверки&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">Get-NetNat&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>С помощью этой конфигурации вы можете создать собственный сегмент NAT, который может взаимодействовать с внешним миром через хост, вручную назначив указанному гостю Hyper-V IP-адрес &lt;code>192.168.100.x&lt;/code> и шлюз &lt;code>192.168.100.1&lt;/code>.&lt;/p>
&lt;hr>
&lt;h2 id="5-варианты-использования-и-практическое-руководство-по-выбору">5. Варианты использования и практическое руководство по выбору
&lt;/h2>&lt;p>Учитывая различия в архитектуре и производительности, описанные выше, определим, в каких ситуациях следует применять каждую из технологий.&lt;/p>
&lt;h3 id="51-сценарии-когда-следует-выбрать-wsl2">5.1. Сценарии, когда следует выбрать WSL2
&lt;/h3>&lt;p>WSL2 специально разработан для «повышения продуктивности разработчиков». Он идеально подходит для следующих целей:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Веб-разработка и облачная (cloud-native) разработка&lt;/strong>: Разработка контейнеров с использованием Docker Desktop (бэкенд WSL2) или Podman.&lt;/li>
&lt;li>&lt;strong>Использование инструментов только для Linux&lt;/strong>: Если вы повседневно используете bash, grep, awk, sed или компиляторы GCC / Clang для Linux.&lt;/li>
&lt;li>&lt;strong>GUI-приложения (WSLg)&lt;/strong>: Если вы хотите бесшовно запускать приложения X11/Wayland для Linux на рабочем столе Windows.&lt;/li>
&lt;li>&lt;strong>Машинное обучение и разработка ИИ&lt;/strong>: Быстрое обучение в TensorFlow или PyTorch с использованием функции проброса GPU (NVIDIA CUDA на WSL).&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Примечание&lt;/strong>: Вы можете столкнуться с ограничениями, если вам нужно глубоко настроить ядро или если вы развертываете сложные сервисы, сильно зависящие от systemd (в настоящее время systemd поддерживается, но по умолчанию может быть отключен или иметь ограничения).&lt;/p>
&lt;h3 id="52-сценарии-когда-следует-выбрать-hyper-v">5.2. Сценарии, когда следует выбрать Hyper-V
&lt;/h3>&lt;p>Hyper-V предназначен для «виртуализации инфраструктуры и полной изоляции». Он обязателен для следующих задач:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Запуск ВМ Windows&lt;/strong>: При запуске различных версий Windows (например, Windows Server или старой Windows 10) в качестве среды тестирования.&lt;/li>
&lt;li>&lt;strong>Вложенная виртуализация (Nested Virtualization)&lt;/strong>: Если вам нужно запустить виртуальную машину (Hyper-V или KVM) внутри другой виртуальной машины. Это незаменимо для сред тестирования инженеров инфраструктуры.&lt;/li>
&lt;li>&lt;strong>Сложные сетевые требования&lt;/strong>: Если вам нужен строгий контроль над конфигурацией сети, например, подключение внешнего моста (подключение к той же локальной сети), тегирование VLAN или назначение нескольких NIC.&lt;/li>
&lt;li>&lt;strong>Снапшоты (Контрольные точки)&lt;/strong>: Функция сохранения состояния ВМ в определенный момент времени и возможности мгновенного отката в любой момент. Чрезвычайно полезно для разрушительного тестирования программного обеспечения или анализа вредоносных программ.&lt;/li>
&lt;li>&lt;strong>Фиксированное распределение ресурсов&lt;/strong>: Если вам нужно строго зафиксировать количество ядер CPU и объем памяти для минимизации влияния на хост-ОС.&lt;/li>
&lt;/ul>
&lt;hr>
&lt;h2 id="6-рассмотрение-пропускной-способности-ввода-вывода-с-использованием-математических-моделей-приложение">6. Рассмотрение пропускной способности ввода-вывода с использованием математических моделей (Приложение)
&lt;/h2>&lt;p>Для системного инженера при оценке пределов производительности ввода-вывода обеих систем важно теоретически понимать взаимосвязь между пропускной способностью $S$ и размером блока $B$.&lt;/p>
&lt;p>Пропускная способность передачи данных $S$ — это объем данных, переданных за единицу времени, которая моделируется следующим образом:&lt;/p>
$$ S(B) = \frac{B}{L_{setup} + \frac{B}{R_{max}}} $$&lt;ul>
&lt;li>$B$: Размер блока (Байты)&lt;/li>
&lt;li>$L_{setup}$: Фиксированная задержка, связанная с настройкой запроса ввода-вывода и переключением контекста&lt;/li>
&lt;li>$R_{max}$: Максимальная пропускная способность оборудования при копировании или передаче на устройство&lt;/li>
&lt;/ul>
&lt;p>При доступе к файлам через протокол 9P в WSL2 этот параметр $L_{setup}$ становится очень большим (из-за сокет-коммуникаций и сериализации/десериализации протокола). Таким образом, когда размер блока $B$ мал (массовое чтение/запись мелких файлов размером в несколько килобайт), влияние $L_{setup}$ в знаменателе становится доминирующим, и пропускная способность $S$ резко падает.
И наоборот, при доступе к VHDX через VMBus в Hyper-V, параметр $L_{setup}$ оптимизирован почти до уровня аппаратных прерываний, что позволяет поддерживать высокие значения IOPS даже для небольших блоков.&lt;/p>
&lt;p>Эта математическая реальность является логическим обоснованием лучшей практики, которая гласит: «В WSL2 нельзя размещать файлы проекта на стороне Windows».&lt;/p>
&lt;hr>
&lt;h2 id="7-заключение-две-сосуществующие-технологии-виртуализации">7. Заключение: Две сосуществующие технологии виртуализации
&lt;/h2>&lt;p>Hyper-V и WSL2 — это не ситуация, когда одна технология превосходит другую; это &lt;strong>«два решения с разными целями»&lt;/strong>.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>WSL2&lt;/strong> — это «лучший инструмент интеграции», позволяющий пробить оболочку ОС Windows и бесшовно и быстро предоставить экосистему Linux пользователям Windows. Не будет преувеличением сказать, что это идеальная CLI-среда для разработчиков.&lt;/li>
&lt;li>&lt;strong>Hyper-V&lt;/strong> — это «полноценный гипервизор», который привносит на рабочий стол надежную изоляцию и возможности управления, отточенные в корпоративных центрах обработки данных. Ему нет равных в построении сетей, тестировании ОС Windows и симуляции инфраструктурных сред.&lt;/li>
&lt;/ul>
&lt;p>В современных средах Windows эти две технологии не конкурируют друг с другом, а прекрасно сосуществуют на одной платформе ВМ. Используя их по назначению в зависимости от задачи, вы сделаете Windows самой мощной и гибкой инженерной рабочей станцией в мире.&lt;/p></description></item><item><title>Создание воспроизводимой локальной среды разработки с использованием Docker</title><link>http://kenji.blog/ru/p/docker-reproducible-local-dev-environment/</link><pubDate>Sun, 13 Sep 2026 01:00:00 +0900</pubDate><guid>http://kenji.blog/ru/p/docker-reproducible-local-dev-environment/</guid><description>&lt;img src="http://kenji.blog/p/docker-reproducible-local-dev-environment/img/eyecatch.jpg" alt="Featured image of post Создание воспроизводимой локальной среды разработки с использованием Docker" />&lt;h2 id="1-введение-избавление-от-проблемы-на-моей-машине-это-работает">1. Введение: Избавление от проблемы «на моей машине это работает»
&lt;/h2>&lt;p>В сфере разработки программного обеспечения проблема «на моей машине это работает (It works on my machine)», вызванная различиями в средах разработчиков, долгое время была причиной потери времени во многих проектах. Из-за различий в ОС, установленных версиях языков, зависимостях библиотек и конфликтах глобально установленных инструментов локальная среда постоянно подвергается «неопределенности состояния».&lt;/p>
&lt;p>Эту проблему в корне решают контейнерные технологии, такие как &lt;strong>Docker&lt;/strong>, и парадигма &lt;strong>Infrastructure as Code (IaC)&lt;/strong>. Контейнеризация локальной среды разработки обеспечивает изоляцию на уровне ОС и позволяет контролировать версии самой среды вместе с кодовой базой.&lt;/p>
&lt;p>В этой статье мы подробно, в том числе с математической точки зрения, рассмотрим глубокие технические механизмы и шаги для создания &lt;strong>«воспроизводимой локальной среды разработки, которая будет абсолютно одинаковой независимо от того, кто, когда и на какой машине ее запускает»&lt;/strong>, используя Docker, Docker Compose и VSCode DevContainers.&lt;/p>
&lt;hr>
&lt;h2 id="2-совместимость-infrastructure-as-code-iac-и-контейнерных-технологий">2. Совместимость Infrastructure as Code (IaC) и контейнерных технологий
&lt;/h2>&lt;h3 id="принципы-iac-и-их-применение-к-локальной-среде">Принципы IaC и их применение к локальной среде
&lt;/h3>&lt;p>Infrastructure as Code (IaC) — это подход, при котором настройка и выделение ресурсов инфраструктуры управляются не через ручные процессы, а через машиночитаемые конфигурационные файлы. Ключевые принципы IaC включают следующие элементы:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Декларативный подход (Declarative Approach)&lt;/strong>: Определяется не «как изменить состояние», а «каким должно быть конечное состояние».&lt;/li>
&lt;li>&lt;strong>Идемпотентность (Idempotency)&lt;/strong>: Независимо от того, сколько раз выполняется скрипт, всегда гарантируется один и тот же результат (состояние).&lt;/li>
&lt;li>&lt;strong>Контроль версий (Version Control)&lt;/strong>: Состояние инфраструктуры сохраняется в виде кода в VCS, таких как Git, что позволяет отслеживать историю изменений и проводить код-ревью (peer review).&lt;/li>
&lt;/ol>
&lt;p>Применение IaC в локальной среде разработки означает кодирование «идеального состояния» среды разработки с использованием &lt;code>Dockerfile&lt;/code>, &lt;code>docker-compose.yml&lt;/code> и &lt;code>devcontainer.json&lt;/code>. Это позволяет новым участникам команды моментально приступить к разработке, просто клонировав репозиторий и выполнив одну команду, обеспечивая идеальный опыт онбординга.&lt;/p>
&lt;h3 id="функции-ядра-лежащие-в-основе-контейнерных-технологий">Функции ядра, лежащие в основе контейнерных технологий
&lt;/h3>&lt;p>Технология контейнеров — это легковесная технология виртуализации, которая изолирует процессы, разделяя ядро хост-ОС, в отличие от виртуализации на основе гипервизора, такой как виртуальные машины (VM). Для достижения этого в основном используются следующие функции ядра Linux:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Namespaces&lt;/strong>: Обеспечивают независимое представление системных ресурсов (PID, сеть, точки монтирования, пользователи и т. д.) для каждого процесса.&lt;/li>
&lt;li>&lt;strong>Cgroups (Control Groups)&lt;/strong>: Ограничивают и распределяют физические ресурсы (процессор, память, дисковый ввод-вывод и т. д.), доступные процессам.&lt;/li>
&lt;li>&lt;strong>UnionFS (Union File System)&lt;/strong>: Технология, которая прозрачно накладывает несколько деревьев каталогов (слоев) друг на друга, представляя их как единую файловую систему. Слои образов Docker опираются на эту технологию.&lt;/li>
&lt;/ul>
&lt;p>Давайте рассмотрим математическую модель ограничения ресурсов. Пусть $M_{\text{total}}$ — общий объем памяти хост-машины, а $m_i$ — ограничение памяти для $n$ контейнеров, работающих на хосте. Необходимое условие для стабильной работы системы, с учетом базовой памяти $M_{\text{os}}$, потребляемой хост-ОС и другими процессами, можно выразить следующим неравенством:&lt;/p>
$$ \sum_{i=1}^{n} m_i \le M_{\text{total}} - M_{\text{os}} $$&lt;p>Строго определяя $m_i$ для каждого контейнера с помощью Cgroups, даже если в конкретном контейнере происходит утечка памяти, механизм OOM (Out Of Memory) Killer предотвратит сбой других контейнеров или всей хост-системы.&lt;/p>
&lt;hr>
&lt;h2 id="3-эффективное-проектирование-dockerfile-освоение-многоэтапных-сборок">3. Эффективное проектирование Dockerfile: Освоение многоэтапных сборок
&lt;/h2>&lt;p>Первым шагом к воспроизводимой среде является проектирование &lt;code>Dockerfile&lt;/code>, определяющего среду выполнения приложения. Здесь на примере Python (FastAPI) мы рассмотрим лучшие практики создания безопасного и легковесного Dockerfile с использованием &lt;strong>многоэтапных сборок (multi-stage builds)&lt;/strong>.&lt;/p>
&lt;p>Многоэтапная сборка — это метод, при котором в одном &lt;code>Dockerfile&lt;/code> используется несколько инструкций &lt;code>FROM&lt;/code>, разделяя среду сборки (тяжелую среду, содержащую компиляторы и инструменты разработки) и среду выполнения (легковесную среду, содержащую только необходимые артефакты).&lt;/p>
&lt;h3 id="практический-dockerfile-для-python-fastapi">Практический Dockerfile для Python FastAPI
&lt;/h3>&lt;p>Ниже приведен пример продвинутого &lt;code>Dockerfile&lt;/code>, объединяющего управление зависимостями с помощью Poetry и многоэтапную сборку.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;span class="lnt">36
&lt;/span>&lt;span class="lnt">37
&lt;/span>&lt;span class="lnt">38
&lt;/span>&lt;span class="lnt">39
&lt;/span>&lt;span class="lnt">40
&lt;/span>&lt;span class="lnt">41
&lt;/span>&lt;span class="lnt">42
&lt;/span>&lt;span class="lnt">43
&lt;/span>&lt;span class="lnt">44
&lt;/span>&lt;span class="lnt">45
&lt;/span>&lt;span class="lnt">46
&lt;/span>&lt;span class="lnt">47
&lt;/span>&lt;span class="lnt">48
&lt;/span>&lt;span class="lnt">49
&lt;/span>&lt;span class="lnt">50
&lt;/span>&lt;span class="lnt">51
&lt;/span>&lt;span class="lnt">52
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-dockerfile" data-lang="dockerfile">&lt;span class="line">&lt;span class="cl">&lt;span class="c"># ---------------------------------------------------------&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># Stage 1: Builder (Среда сборки)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># ---------------------------------------------------------&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">FROM&lt;/span>&lt;span class="s"> python:3.11-slim AS builder&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># Настройка необходимых переменных окружения&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">ENV&lt;/span> &lt;span class="nv">PYTHONUNBUFFERED&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">1&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> &lt;span class="nv">PYTHONDONTWRITEBYTECODE&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">1&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> &lt;span class="nv">POETRY_VERSION&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">1&lt;/span>.6.1 &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> &lt;span class="nv">POETRY_HOME&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;/opt/poetry&amp;#34;&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> &lt;span class="nv">POETRY_VIRTUALENVS_IN_PROJECT&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="nb">true&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> &lt;span class="nv">POETRY_NO_INTERACTION&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">1&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># Установка зависимостей&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">RUN&lt;/span> apt-get update &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> apt-get install -y --no-install-recommends &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> curl build-essential &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> curl -sSL https://install.python-poetry.org &lt;span class="p">|&lt;/span> python3 - &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> apt-get clean &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> rm -rf /var/lib/apt/lists/*&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">ENV&lt;/span> &lt;span class="nv">PATH&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="nv">$POETRY_HOME&lt;/span>&lt;span class="s2">/bin:&lt;/span>&lt;span class="nv">$PATH&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">WORKDIR&lt;/span>&lt;span class="s"> /app&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># Копирование файлов зависимостей и установка&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">COPY&lt;/span> pyproject.toml poetry.lock ./&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">RUN&lt;/span> poetry install --no-root --only main&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># ---------------------------------------------------------&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># Stage 2: Runtime (Среда выполнения)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># ---------------------------------------------------------&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">FROM&lt;/span>&lt;span class="s"> python:3.11-slim AS runtime&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">ENV&lt;/span> &lt;span class="nv">PYTHONUNBUFFERED&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">1&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> &lt;span class="nv">PYTHONDONTWRITEBYTECODE&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="m">1&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> &lt;span class="nv">PATH&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;/app/.venv/bin:&lt;/span>&lt;span class="nv">$PATH&lt;/span>&lt;span class="s2">&amp;#34;&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># Создание минимального непривилегированного пользователя&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">RUN&lt;/span> groupadd -r appuser &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> useradd -r -g appuser appuser&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">WORKDIR&lt;/span>&lt;span class="s"> /app&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># Копирование только виртуальной среды (зависимостей) из сборщика&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">COPY&lt;/span> --from&lt;span class="o">=&lt;/span>builder --chown&lt;span class="o">=&lt;/span>appuser:appuser /app/.venv /app/.venv&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># Копирование кода приложения&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">COPY&lt;/span> --chown&lt;span class="o">=&lt;/span>appuser:appuser ./src /app/src&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># Переключение на непривилегированного пользователя&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">USER&lt;/span>&lt;span class="s"> appuser&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="c"># Команда по умолчанию при запуске контейнера&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">ENTRYPOINT&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;uvicorn&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;src.main:app&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;--host&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;0.0.0.0&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;--port&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;8000&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="математическая-оценка-размера-образа-благодаря-многоэтапной-сборке">Математическая оценка размера образа благодаря многоэтапной сборке
&lt;/h3>&lt;p>Пусть размер образа при одноэтапной сборке равен $S_{\text{single}}$, а при многоэтапной — $S_{\text{multi}}$. Коэффициент уменьшения размера $R$ вычисляется следующим образом:&lt;/p>
$$ R = \left( 1 - \frac{S_{\text{multi}}}{S_{\text{single}}} \right) \times 100 \ (\%) $$&lt;p>Например, предположим, что $S_{\text{single}}$ включает базовый образ ОС (около 110 МБ), пакеты для разработки (например, gcc, около 150 МБ), сам Poetry (около 40 МБ), библиотеки зависимостей проекта (около 80 МБ) и исходный код (около 5 МБ), что в сумме составляет 385 МБ.
С другой стороны, в $S_{\text{multi}}$ в базовый образ (110 МБ) копируются только библиотеки зависимостей (80 МБ) и исходный код (5 МБ), что дает в сумме 195 МБ.&lt;/p>
$$ R = \left( 1 - \frac{195}{385} \right) \times 100 \approx 49.35\% $$&lt;p>Таким образом, внедрение многоэтапной сборки позволяет сократить размер образа примерно вдвое. Уменьшение размера образа напрямую связано с сокращением времени загрузки (Pull) из реестра, экономией дискового пространства и повышением безопасности за счет уменьшения поверхности атаки (Attack Surface).&lt;/p>
&lt;hr>
&lt;h2 id="4-оркестрация-нескольких-контейнеров-с-помощью-docker-compose">4. Оркестрация нескольких контейнеров с помощью Docker Compose
&lt;/h2>&lt;p>В современной веб-разработке обычно используется микросервисная архитектура, где взаимодействуют несколько компонентов, таких как веб-серверы, базы данных и кэш-серверы. Для централизованного управления ими в локальной среде используется &lt;code>docker-compose.yml&lt;/code>.&lt;/p>
&lt;p>В этот раз мы создадим трехуровневую систему, состоящую из «Web (FastAPI)», «Database (PostgreSQL)» и «Cache (Redis)» в локальной среде.&lt;/p>
&lt;h3 id="схема-архитектуры-mermaid">Схема архитектуры (Mermaid)
&lt;/h3>&lt;p>На следующей схеме представлена блочная диаграмма, показывающая взаимосвязь каждого контейнера, сети и томов на локальной машине.&lt;/p>
&lt;pre class="mermaid">
graph TD
User[&amp;#34;Хост-машина (Браузер/curl)&amp;#34;] --&amp;gt;|Localhost:8000| Web[&amp;#34;Веб-контейнер FastAPI&amp;#34;]
subgraph &amp;#34;Мостовая сеть Docker (app-network)&amp;#34;
Web --&amp;gt;|Port 5432| DB[&amp;#34;Контейнер PostgreSQL&amp;#34;]
Web --&amp;gt;|Port 6379| Redis[&amp;#34;Контейнер Redis&amp;#34;]
end
DB --&amp;gt; Volume1[&amp;#34;Именованный том (postgres_data)&amp;#34;]
Redis --&amp;gt; Volume2[&amp;#34;Именованный том (redis_data)&amp;#34;]
HostDir[&amp;#34;Исходный код хоста (./src)&amp;#34;] -.-&amp;gt;|Bind Mount| Web
&lt;/pre>
&lt;h3 id="реализация-и-подробное-объяснение-docker-composeyml">Реализация и подробное объяснение docker-compose.yml
&lt;/h3>&lt;p>Ниже приведен пример надежного &lt;code>docker-compose.yml&lt;/code>, подходящего для практического создания среды.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;span class="lnt">36
&lt;/span>&lt;span class="lnt">37
&lt;/span>&lt;span class="lnt">38
&lt;/span>&lt;span class="lnt">39
&lt;/span>&lt;span class="lnt">40
&lt;/span>&lt;span class="lnt">41
&lt;/span>&lt;span class="lnt">42
&lt;/span>&lt;span class="lnt">43
&lt;/span>&lt;span class="lnt">44
&lt;/span>&lt;span class="lnt">45
&lt;/span>&lt;span class="lnt">46
&lt;/span>&lt;span class="lnt">47
&lt;/span>&lt;span class="lnt">48
&lt;/span>&lt;span class="lnt">49
&lt;/span>&lt;span class="lnt">50
&lt;/span>&lt;span class="lnt">51
&lt;/span>&lt;span class="lnt">52
&lt;/span>&lt;span class="lnt">53
&lt;/span>&lt;span class="lnt">54
&lt;/span>&lt;span class="lnt">55
&lt;/span>&lt;span class="lnt">56
&lt;/span>&lt;span class="lnt">57
&lt;/span>&lt;span class="lnt">58
&lt;/span>&lt;span class="lnt">59
&lt;/span>&lt;span class="lnt">60
&lt;/span>&lt;span class="lnt">61
&lt;/span>&lt;span class="lnt">62
&lt;/span>&lt;span class="lnt">63
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="line">&lt;span class="cl">&lt;span class="nt">version&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s1">&amp;#39;3.8&amp;#39;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">services&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">web&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">build&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">context&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">.&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">target&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">runtime&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">container_name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">dev_web&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">ports&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="s2">&amp;#34;8000:8000&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">volumes&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">./src:/app/src:ro &lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="c"># Монтирование кода хоста только для чтения (для горячей перезагрузки)&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">environment&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">DATABASE_URL=postgresql://postgres:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">REDIS_URL=redis://redis:6379/0&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">env_file&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">.env&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">depends_on&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">db&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">condition&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">service_healthy&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">redis&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">condition&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">service_started&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">networks&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">app-network&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">command&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;uvicorn&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;src.main:app&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;--host&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;0.0.0.0&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;--port&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;8000&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;--reload&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">db&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">image&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">postgres:15-alpine&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">container_name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">dev_db&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">ports&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="s2">&amp;#34;5432:5432&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">environment&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">POSTGRES_USER&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">postgres&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">POSTGRES_PASSWORD&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">${POSTGRES_PASSWORD}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">POSTGRES_DB&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">${POSTGRES_DB}&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">volumes&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">postgres_data:/var/lib/postgresql/data&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">networks&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">app-network&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">healthcheck&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">test&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;CMD-SHELL&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;pg_isready -U postgres -d ${POSTGRES_DB}&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">interval&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">5s&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">timeout&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">5s&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">retries&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="m">5&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">redis&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">image&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">redis:7-alpine&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">container_name&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">dev_redis&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">ports&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="s2">&amp;#34;6379:6379&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">volumes&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">redis_data:/data&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">networks&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>- &lt;span class="l">app-network&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">command&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;redis-server&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;--appendonly&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="s2">&amp;#34;yes&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">volumes&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">postgres_data&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">redis_data&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w">&lt;/span>&lt;span class="nt">networks&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">app-network&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="w"> &lt;/span>&lt;span class="nt">driver&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">bridge&lt;/span>&lt;span class="w">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="тома-volumes-и-сохранение-данных">Тома (Volumes) и сохранение данных
&lt;/h3>&lt;p>Контейнеры, как правило, «не имеют состояния (stateless)» и «эфемерны (ephemeral)». При уничтожении контейнера данные внутри него также теряются. Чтобы сохранить данные базы данных или кэш, необходимо смонтировать область файловой системы хост-машины в контейнер.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Bind Mount (Привязка монтирования)&lt;/strong>: Этому соответствует &lt;code>./src:/app/src:ro&lt;/code> в службе &lt;code>web&lt;/code> выше. Он напрямую отображает определенный каталог на хосте в контейнер. Используется для немедленного отражения локального редактирования кода в контейнере (горячая перезагрузка). С точки зрения безопасности рекомендуется добавить опцию &lt;code>:ro&lt;/code> (Read-Only), чтобы код на хосте нельзя было изменить со стороны контейнера.&lt;/li>
&lt;li>&lt;strong>Named Volume (Именованный том)&lt;/strong>: К ним относятся &lt;code>postgres_data&lt;/code> и &lt;code>redis_data&lt;/code>. Это области, внутренне управляемые Docker (например, &lt;code>/var/lib/docker/volumes/&lt;/code>), которые превосходят Bind Mount по производительности ввода-вывода и сглаживают различия файловых систем между ОС. Их всегда следует использовать для сохранения баз данных.&lt;/li>
&lt;/ul>
&lt;h3 id="сеть-networking-и-обнаружение-сервисов">Сеть (Networking) и обнаружение сервисов
&lt;/h3>&lt;p>По умолчанию Docker Compose создает собственную мостовую сеть для каждого проекта. В данном случае это &lt;code>app-network&lt;/code>.
Контейнеры, принадлежащие одной сети, могут разрешать имена (DNS-разрешение) друг друга, используя «имя службы (например: &lt;code>db&lt;/code>, &lt;code>redis&lt;/code>)» в качестве имени хоста вместо IP-адреса.
Например, из веб-контейнера можно получить доступ к базе данных по URL &lt;code>postgresql://postgres:password@db:5432/mydb&lt;/code>. Это позволяет прозрачно переключать места подключения через переменные окружения как в локальной, так и в рабочей (production) среде.&lt;/p>
&lt;h3 id="проверка-работоспособности-healthcheck-и-контроль-порядка-запуска">Проверка работоспособности (Healthcheck) и контроль порядка запуска
&lt;/h3>&lt;p>Директива &lt;code>depends_on&lt;/code> управляет порядком запуска контейнеров, но простое указание &lt;code>depends_on&lt;/code> запустит веб-контейнер на этапе, когда «запущен контейнер БД». На самом деле, процесс инициализации БД (запуск процесса PostgreSQL и подготовка таблиц) занимает несколько секунд, поэтому подключение к БД из веб-контейнера может привести к ошибке.
Чтобы предотвратить это, можно определить &lt;code>healthcheck&lt;/code> и указать &lt;code>condition: service_healthy&lt;/code>, что позволит запускать веб-контейнер только после того, как будет подтверждено, что «БД готова принимать запросы на подключение».&lt;/p>
&lt;hr>
&lt;h2 id="5-управление-переменными-окружения-и-безопасность-env">5. Управление переменными окружения и безопасность (.env)
&lt;/h2>&lt;p>Жесткое кодирование (hardcoding) конфиденциальной информации, такой как пароли баз данных или ключи API, в &lt;code>docker-compose.yml&lt;/code> является антипаттерном, которого следует абсолютно избегать. Вместо этого для внедрения этих значений используется файл переменных окружения &lt;code>.env&lt;/code>.&lt;/p>
&lt;p>Создайте файл &lt;code>.env&lt;/code> в корне проекта.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-ini" data-lang="ini">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Файл .env (обязательно добавьте в .gitignore, чтобы исключить из-под контроля Git)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">POSTGRES_PASSWORD&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">supersecretpassword&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">POSTGRES_DB&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">devdb&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="na">API_SECRET_KEY&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">dev_secret_key_12345&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>По умолчанию Docker Compose считывает файл &lt;code>.env&lt;/code> в рабочем каталоге и разворачивает заполнители вида &lt;code>${VAR_NAME}&lt;/code> в YAML-файле. Этот метод позволяет безопасно управлять различными настройками для разных сред, таких как локальная, staging и production, без изменения кода инфраструктуры.&lt;/p>
&lt;hr>
&lt;h2 id="6-vscode-devcontainers-идеальный-опыт-разработки">6. VSCode DevContainers: Идеальный опыт разработки
&lt;/h2>&lt;p>На данный момент мы создали надежную бэкенд-среду с использованием Docker. Однако можно пойти еще дальше. Используя функцию &lt;strong>VSCode DevContainers (Remote - Containers)&lt;/strong>, можно запускать бэкенд самого редактора (VSCode) внутри контейнера.&lt;/p>
&lt;p>Благодаря этому отпадает необходимость устанавливать даже Python или Node.js на локальную машину. Линтеры (flake8/eslint), форматтеры (black/prettier) и даже расширения IDE — всё это можно определить в кодовой базе и использовать совместно со всей командой.&lt;/p>
&lt;h3 id="настройка-devcontainerjson">Настройка devcontainer.json
&lt;/h3>&lt;p>Создайте каталог &lt;code>.devcontainer&lt;/code> в корне проекта и поместите в него файл конфигурации.&lt;/p>
&lt;p>&lt;code>.devcontainer/devcontainer.json&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="line">&lt;span class="cl">&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;Python FastAPI Dev Environment&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;dockerComposeFile&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;../docker-compose.yml&amp;#34;&lt;/span>&lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;service&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;web&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;workspaceFolder&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;/app&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;customizations&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;vscode&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;settings&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;python.defaultInterpreterPath&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;/app/.venv/bin/python&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;python.formatting.provider&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;black&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;editor.formatOnSave&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">true&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;extensions&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;ms-python.python&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;ms-python.vscode-pylance&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;ms-python.black-formatter&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;tamasfe.even-better-toml&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">},&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;forwardPorts&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="mi">8000&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">5432&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">6379&lt;/span>&lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;remoteUser&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;appuser&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nt">&amp;#34;postCreateCommand&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;poetry install&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Включив этот файл в репозиторий, при открытии проекта в VSCode сразу же появится запрос «Reopen in Container» (Открыть в контейнере). По одному щелчку мыши запустятся все необходимые контейнеры, установятся расширения, и вы будете готовы немедленно начать писать код. Это поистине волшебный опыт.&lt;/p>
&lt;hr>
&lt;h2 id="7-последовательность-обработки-запросов-и-моделирование-производительности">7. Последовательность обработки запросов и моделирование производительности
&lt;/h2>&lt;p>Давайте рассмотрим жизненный цикл обработки запросов веб-приложения в созданной локальной среде разработки с помощью диаграммы последовательности и обсудим математическую модель ее производительности.&lt;/p>
&lt;h3 id="диаграмма-последовательности-поток-запроса">Диаграмма последовательности (Поток запроса)
&lt;/h3>&lt;pre class="mermaid">
sequenceDiagram
participant Client as &amp;#34;Браузер / VSCode&amp;#34;
participant Web as &amp;#34;FastAPI (Web)&amp;#34;
participant Redis as &amp;#34;Кэш Redis&amp;#34;
participant DB as &amp;#34;PostgreSQL&amp;#34;
Client-&amp;gt;&amp;gt;Web: &amp;#34;GET /api/users/123&amp;#34;
activate Web
Web-&amp;gt;&amp;gt;Redis: &amp;#34;Проверка кэша для user:123&amp;#34;
activate Redis
alt &amp;#34;Попадание в кэш (Данные есть)&amp;#34;
Redis--&amp;gt;&amp;gt;Web: &amp;#34;Возврат кэшированных данных пользователя&amp;#34;
Web--&amp;gt;&amp;gt;Client: &amp;#34;200 OK (Быстрый ответ)&amp;#34;
else &amp;#34;Промах кэша (Данных нет)&amp;#34;
Redis--&amp;gt;&amp;gt;Web: &amp;#34;Null (Не найдено)&amp;#34;
deactivate Redis
Web-&amp;gt;&amp;gt;DB: &amp;#34;SELECT * FROM users WHERE id = 123&amp;#34;
activate DB
DB--&amp;gt;&amp;gt;Web: &amp;#34;Возврат строки базы данных&amp;#34;
deactivate DB
Web-&amp;gt;&amp;gt;Redis: &amp;#34;SET данные user:123 (TTL: 60s)&amp;#34;
activate Redis
Redis--&amp;gt;&amp;gt;Web: &amp;#34;OK&amp;#34;
deactivate Redis
Web--&amp;gt;&amp;gt;Client: &amp;#34;200 OK (Стандартный ответ)&amp;#34;
end
deactivate Web
&lt;/pre>
&lt;h3 id="математическая-модель-задержки-обработки-latency">Математическая модель задержки обработки (Latency)
&lt;/h3>&lt;p>Математически смоделируем среднее время обработки запроса $T_{\text{total}}$ в вышеописанной системе.
Задержки для каждой операции определены следующим образом:&lt;/p>
&lt;ul>
&lt;li>$T_{\text{net}}$: Сетевая задержка между клиентом и веб-контейнером&lt;/li>
&lt;li>$T_{\text{app}}$: Чистое время обработки на стороне приложения (сериализация и т. д.)&lt;/li>
&lt;li>$T_{\text{cache}}$: Время, затрачиваемое на чтение и запись из Redis&lt;/li>
&lt;li>$T_{\text{db}}$: Время, затрачиваемое на выполнение запроса к PostgreSQL&lt;/li>
&lt;li>$p_{\text{miss}}$: Вероятность промаха кэша ($0 \le p_{\text{miss}} \le 1$)&lt;/li>
&lt;/ul>
&lt;p>Тогда среднее время отклика выражается следующей формулой ожидаемого значения:&lt;/p>
$$ T_{\text{total}} = T_{\text{net}} + T_{\text{app}} + T_{\text{cache}} + p_{\text{miss}} \times (T_{\text{db}} + T_{\text{cache\_write}}) $$&lt;p>В локальной среде разработки (внутри Docker) $T_{\text{net}}$ близко к 0, но стоит обратить внимание на &lt;strong>производительность ввода-вывода (I/O) при Bind Mount&lt;/strong>. В частности, при использовании Docker Desktop на Windows/macOS из-за накладных расходов на совместное использование файлов между хост-ОС и виртуальной машиной (контейнером) $T_{\text{app}}$ (например, время загрузки кода) имеет тенденцию увеличиваться. Чтобы устранить это узкое место, настоятельно рекомендуется архитектура, в которой используется описанный ранее DevContainers для размещения всего исходного кода внутри именованного тома, или работа движка Docker нативно в среде WSL2 (Windows Subsystem for Linux 2).&lt;/p>
&lt;hr>
&lt;h2 id="8-оптимизация-производительности-сборки-docker-стратегия-кэширования-слоев">8. Оптимизация производительности сборки Docker: Стратегия кэширования слоев
&lt;/h2>&lt;p>Понимание механизма «кэширования слоев» при написании Dockerfile кардинально меняет время сборки.
Docker создает разницу в файловой системе (слой) для каждой инструкции Dockerfile (например, &lt;code>FROM&lt;/code>, &lt;code>RUN&lt;/code>, &lt;code>COPY&lt;/code>) и сохраняет ее в виде кэша. При повторной сборке повторно используется кэш для слоев, которые не изменились.&lt;/p>
&lt;p>Важный принцип заключается в том, чтобы &lt;strong>«описывать, начиная с того, что меняется реже всего»&lt;/strong>.&lt;/p>
&lt;p>Давайте смоделируем влияние изменений исходного кода на время сборки. Пусть общее время сборки равно $T_{\text{build}}$, время выполнения каждого шага — $T_{\text{layer}_i}$, а наличие попадания в кэш — логическое значение $c_i \in \{0, 1\}$ (1 при попадании в кэш).&lt;/p>
$$ T_{\text{build}} = T_{\text{init}} + \sum_{i=1}^{n} (1 - c_i) \times T_{\text{layer}_i} $$&lt;p>Как только происходит промах кэша на слое $k$ ($c_k = 0$), кэш инвалидируется ($c_j = 0$) для всех последующих слоев $j > k$.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-dockerfile" data-lang="dockerfile">&lt;span class="line">&lt;span class="cl">&lt;span class="c"># Плохой пример (исходный код копируется первым)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">COPY&lt;/span> ./src /app/src&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">COPY&lt;/span> pyproject.toml poetry.lock ./&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">RUN&lt;/span> poetry install&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>В приведенном выше случае даже изменение одной строки кода приведет к промаху кэша на первой инструкции &lt;code>COPY&lt;/code>, и каждый раз будет выполняться ресурсоемкая команда &lt;code>RUN poetry install&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-dockerfile" data-lang="dockerfile">&lt;span class="line">&lt;span class="cl">&lt;span class="c"># Хороший пример (сначала решаются зависимости)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">COPY&lt;/span> pyproject.toml poetry.lock ./&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">RUN&lt;/span> poetry install&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="k">COPY&lt;/span> ./src /app/src&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>При такой записи, даже при изменении исходного кода, сработает кэш слоя &lt;code>poetry install&lt;/code> ($c_i = 1$), поэтому время сборки резко сократится с нескольких минут до нескольких секунд.&lt;/p>
&lt;hr>
&lt;h2 id="9-устранение-неполадок-troubleshooting-и-советы-tips">9. Устранение неполадок (Troubleshooting) и советы (Tips)
&lt;/h2>&lt;p>Ниже приведены часто встречающиеся проблемы при работе с локальной средой и способы их решения.&lt;/p>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>Ошибка конфликта портов&lt;/strong>
Если вы получаете ошибку вроде &lt;code>Bind for 0.0.0.0:8000 failed: port is already allocated&lt;/code>, это означает, что другой процесс на локальной машине использует этот порт. Этого можно избежать, изменив номер порта на стороне хоста, например &lt;code>ports: - &amp;quot;8080:8000&amp;quot;&lt;/code>.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Нехватка дискового пространства&lt;/strong>
При длительном использовании Docker накапливаются неиспользуемые образы и тома (Dangling Images / Volumes), что может занять десятки гигабайт дискового пространства. Рекомендуется регулярно очищать систему с помощью следующей команды:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">docker system prune -a --volumes
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;/li>
&lt;li>
&lt;p>&lt;strong>Проблемы с правами доступа к файлам&lt;/strong>
При использовании Bind Mount в среде Linux файлы, созданные внутри контейнера, могут принадлежать пользователю &lt;code>root&lt;/code>, что сделает невозможным их редактирование на стороне хоста. Эту проблему можно решить, создав непривилегированного пользователя в Dockerfile и сопоставив его с вашим собственным UID/GID хост-ОС (например, 1000:1000).&lt;/p>
&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h2 id="10-заключение-как-воспроизводимость-ускоряет-разработку">10. Заключение: Как воспроизводимость ускоряет разработку
&lt;/h2>&lt;p>Сочетание Docker, Docker Compose и VSCode DevContainers позволяет создать надежную локальную среду разработки, в которой «состояние будет абсолютно одинаковым независимо от того, кто ее запускает».&lt;/p>
&lt;p>Внедрение парадигмы IaC в локальную среду не просто сокращает время первоначальной настройки. Это значительно повышает скорость и качество всего цикла разработки, устраняя опасения по поводу изменений конфигурации инфраструктуры, облегчая эксперименты с новыми технологическими стеками и обеспечивая плавный переход к конвейерам (pipelines) CI/CD.&lt;/p>
&lt;p>Используя передовые методы (best practices), описанные в этой статье, такие как оптимизация размера образа с помощью многоэтапных сборок, управление зависимостями с помощью проверок работоспособности (healthcheck) и написание Dockerfile с учетом кэширования слоев, обязательно внедрите лучший опыт разработки (DX: Developer Experience) и в свои проекты.&lt;/p></description></item></channel></rss>