Как ускорить docker на windows

На днях столкнулся с тем, что Docker под Windows очень медленно работает и тормозит при открытии самой простой страницы по 3-4 секунды.

Погуглив, а также пообщавшись с коллегами, я нашел целую кучу решений, которая по итогу решила вопрос, поехали!

Честно говоря, основные советы в интернете сводятся в переквалификации в DevOps, например рекомендуется «Docker Volumes для директорий кеша» и т.д., но мы же привыкли «херак и в продакшен», верно 🙂 ?

Итак вариант №1 (подсказали коллеги) — не работать через хваленый WSL2.

Для этого нужно перейти в настройки и снять галочку с «Use the WSL 2 based engine (Windows Home can only run the WSL 2 backend)», а затем установить компонент Windows «Hyper-V». После чего Docker должен перестать так сильно тормозить и ускорится в работе.

Однако … у меня этот вариант не сработал, потому что такая фишка доступна только Windows 10 Pro, а если у вас Home, как и у меня — то такой вариант не не сработает (ну или надо будет апнуть версию Windows, что не всегда возможно)

Вариант №2 — перекинуть весь проект в файловую систему WSL2

Как написали в стаке — такая медленная работа и тормоза из за того, что WSL используют файловую систему линукса, а тома винды просто подмонтированы, и процесс чтения с NTFS намного медленнее. На единичном файле это незаметно, но если у вас сотня файлов (а во фреймворках это так и есть), то сей процесс и занимает несколько секунд.

Решение простое — перекидываем весь проект в WSL2, для этого открываем сетевую шару \\WSL$ в проводнике, там будет находится папка с названием вашего WSL дистрибутива, например у меня это Ubuntu

Затем перекидываем туда проект, например в папку /home/, и запускаем docker-compose up, скорость ответа вас приятно удивит.

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

Так что смело перекидывайте проекты Docker в файловую систему WLS и тормоза пропадут.

CHALLENGE: Incorrectly configured Docker leads to slow Docker on Windows performance 

SOLUTION: Run Docker Windows from Ubuntu 20.04 LTS with WSL2 enabled 

Docker Windows performance problems 

If you want to use Docker on Windows, the Docker Desktop application needs to be installed. However, installation with default settings usually causes Docker performance poor and not very fast.  

One of the more common problems for Developers that use Windows is that the projects with Docker configuration work really slowly, to a point when sometimes a single browser request needs to wait 30-60 seconds to be completed. This is obviously a problem, one that negatively affects project progression and generally makes the life of developers more difficult. 

Why is Docker slow on Windows?  

The root of the issue is that Windows 10 is (was) using WSL (Windows Subsystem for Linux), which is a layer between Windows and Linux. Communication between these two (Hard Drive operations) can be quite slow 

Solution for slow Docker on Windows performance improvement 

The suggested solution is: 

  • use WSL 2 in Windows 10 
  • and start Docker directly from Ubuntu in Windows 

Check out the step-by-step tutorial below. The “WSL 2” feature, released as Windows Update in mid 2020, was designed to increase file system performance and support full system call compatibility. Properly configured Docker and Windows WSL2 will give you really good performance in terms of speed. 

Explanation: Windows Subsystem for Linux (WSL) 2 introduces a significant architectural change as it is a full Linux kernel, allowing Linux containers to run natively without emulation. To get the best out of the file system performance when bind-mounting files, we recommend storing the source code in the Linux file system, rather than the Windows file system. 

Step 1: ENABLE WSL2 on Windows 

A. Enable WSL 2 on your Windows 
B. Install Linux on Windows 10 (Ubuntu 20.04 LTS from Microsoft store) 
C. Enable WSL 2 on Ubuntu 20.04 
D. Install Docker Desktop 
E. Enable WSL 2 for Docker 
More info: https://www.padok.fr/en/blog/docker-windows-10 

Step 2: Install SSH on Ubuntu 

We want to enable SSH connection to Ubuntu and we will be using it for local files’ “deployment”. Our goal here is: 

– We have a GIT project cloned on the main Windows hard drive (SSD) 
– We’re developing using PHPStorm 
– PHPStorm Files’ deployment is configured in a way to automatically send files to Ubuntu. 
– With this setup, we will instantly see the changes to our code in the browser. 

How to configure SSH on Ubuntu: 
https://linuxize.com/post/how-to-enable-ssh-on-ubuntu-18-04/ 

Step 3: Configure PHPStorm Deployment 

A. File / Settings / Build, Execution, Deployment – Deployment 
B. Click + (to add a new deployment config) – choose SFTP 
C. Fill in ‘SSH configuration’ 
– Host: your Ubuntu public IP 
(to find the proper IP address, type the following command in your Ubuntu: ip a) 
– User name: your ubuntu username 
– Authentication type: Password 
– Password: your ubuntu pass 
D. Set proper directories mapping in: Deployment / Mappings 
E. Deployment / Options: 
– find the option ‘Upload change files automatically to the default server’ and set to ‘Always’ 

Step 4: ssh to Ubuntu, run Docker 

A. Open your SSH Client (ex: Cmder ) and connect to Ubuntu using SSH connection: 
$ ssh myusername@111.222.33.44 
(use your Ubuntu credentials here, we were using the ones in PHPStorm Deployment configuration) 
B. Log in to the root account: 
$ sudo su 
C. Run Docker: 
$ docker-compose up -d 

Step 5: Connect to the database 

  1. Log into your Docker container: 
    $ docker exec -it myproject_php bash 
    $ cd /var/www/html/public/ 
    B. Download MYSQL Client: 
    $ wget https://github.com/vrana/adminer/releases/download/v4.7.7/adminer-4.7.7-mysql.php 
    C. Find database credentials (in Docker configuration files), ex: 
    DATABASE_URL=mysql://root:mypass@mysql:3306/mydatabasename 
    C. Open the following link in the browser: http://localhost/adminer-4.7.7-mysql.php 
    Server: mysql 
    Username: root 
    Password: mypass 
    Database name: mydatabasename 

Docker WordPress with 50ms response 

Docker on Windows where commands are run from the Ubuntu App has a really good response time. Instead of using emulation, the container runs natively on Linux. A single blog post page in WordPress response (TTFB) is 50ms, which is quite a good result! 

wordpress on windows docker fast

This Docker solution is not only for WordPress 

This solution for fast Docker in Windows is not only restricted to WordPress CMS. There is no difference if you’re using PHP Symfony, React Node server or Ruby on Rails – you will see the difference in performance speed. 

Summary 

That’s it. We’ve configured: 
– WSL2 on Windows 
– Ubuntu Linux on Windows 
– PHPStorm Deployment 
– Running Docker from Ubuntu 
– Have ability to access Docker database 
This configuration allows Docker to run really fast. Instead of waiting 60 seconds, now the browser request will run faster than 1 second! 

Troubleshooting 

* Error after trying to start Docker, ex: ERROR: Encountered errors while bringing up the project. 
– always use Ubuntu being logged in as a root: 
$ sudo su 
* I see an error related to “not sufficient permissions” 
– Run Powershell or the SSH Client using the ‘Run as administrator’ option 
* I can’t install SSH on Win Ubuntu: 
sudo su 
apt install openssh-server 
service ssh start 
service ssh status 

If you get the following error: sshd: no hostkeys available – exiting : 
https://www.garron.me/en/linux/sshd-no-hostkeys-available-exiting.html 

If you get the following error: Permission denied (publickey): 
nano /etc/ssh/sshd_config 

Find: PasswordAuthentication 
Set it to: yes 
#and then restart the ssh service: 
service ssh restart 

https://askubuntu.com/questions/311558/ssh-permission-denied-publickey/881518#881518

* hyper-v is not enabled 

To Enable Hyper-V 
Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V –All 

To Disable Hyper-V 
Disable-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V-All 

https://www.poweronplatforms.com/enable-disable-hyper-v-windows-10-8/ 1 

* Check if hyper-v is enabled: 
Run WIN Powershell (with Admin rights) and execute: 

$hyperv = Get-WindowsOptionalFeature -FeatureName Microsoft-Hyper-V-All -Online 
if($hyperv.State -eq “Enabled”) { 
Write-Host “Hyper-V is enabled.” 
} else { 
Write-Host “Hyper-V is disabled.” 

Do you need someone to implement this solution for you? Check out our specialists for hire in the outsourcing section.

Are you considering a global project and need custom web application development? Check out our offer!

Tl;dr Increase Docker speed when developing on Linux containers in Windows by installing a preview version of Windows, installing WSL 2 and by using the Edge version of Docker.

Running Ubuntu on Windows using Windows Subsystem for Linux (WSL)

When working with Docker, even in a Windows development environment, the default option is to use Linux containers. Microsoft have made it possible to run Linux containers in Windows through what is called Windows Subsystem for Linux (WSL), which is a layer between Windows and Linux distros like Ubuntu.

All calls to from Windows to the Docker container are translated on the way, and especially operations that read and write to disk have been quite slow. This causes a significant overhead compared to running Linux containers on a Linux machine.

Earlier this year Microsoft released version 2 of WSL, which was a major rewrite of the WSL architecture. WSL 2 works by running a custom Linux kernel inside Windows. This kernel has direct access to Windows’ file system, which means the file handling is a lot faster in WSL 2 compared to WSL.

Read more about the release of WSL 2 on Microsoft’s developer blog:
https://devblogs.microsoft.com/commandline/announcing-wsl-2/

The Docker developers have since released a preview (Edge) version of their Windows application, to take advantage of WSL 2.

As a developer working in Windows, there are 3 steps you need to take to use the faster Docker version:

  1. Install Windows 10 Preview
  2. Install WSL 2
  3. Install Docker Edge

Install Windows 10 preview

To install preview versions of Windows, you need to join the insider program:

  1. Open Settings from the start menu
  2. Open Update & Security
  3. Open Windows Insider Program

Follow the wizard to join the insider program, you need to have a personal Microsoft account to join. To be able to install WSL 2, you only need the «Slow» setting, meaning you will get a pretty stable preview version of Windows.

After you have joined the insider program, go to Windows Update to install the next preview version of Windows.

This takes a while, go grab a coffee while you wait.

Install WSL 2

In a command window, run the following command to see what version of WSL you have:

C:\> wsl --list --verbose

Command for displaying version of WSL distros

If you don’t get anything here, you probably don’t have WSL installed or enabled at all.

Follow this guide to set up WSL:
https://docs.microsoft.com/en-us/windows/wsl/install-win10

If you get a list with one or more Linux distros, like Ubuntu, and version 1, follow this guide to enable WSL version 2 and update your distro to take advantage of it:
https://docs.microsoft.com/en-us/windows/wsl/wsl2-install

When you run the command again, you should see something like this:

C:\> wsl -l -v
NAME      STATE           VERSION
Ubuntu    Stopped         2

Command line showing Ubuntu with WSL 2 installed

Install Docker Edge

  1. Install Docker Desktop for Windows
  2. Run it
  3. Right click the Docker icon on the taskbar and select Settings

On the settings page in Docker, you should have a link at the bottom, like this:

Text inside the Docker settings showing you are using the stable version.

Click the link to switch to another version.

Select «Docker Desktop Edge» and install it.

NB! If the installation of Edge through Docker doesn’t work (it didn’t for me), download Docker Edge directly and run the installer.

The easiest place to find the newest Edge release at the moment, is through the Docker Edge release notes here:
https://docs.docker.com/docker-for-windows/edge-release-notes/

If you run into any problems, have a look at the Docker documentation for enabling WSL 2:
https://docs.docker.com/docker-for-windows/wsl-tech-preview/

Enable WSL 2 support in Docker Edge

Open the Docker settings, by right-clicking the Docker icon on the taskbar.

Enable WSL 2:

Docker settings: Select Enable WSL 2 and click Apply & Restart

If you got this far, good job!

Lean back and enjoy faster startup times and faster file reading and writing when running your Docker Linux containers in Windows.

Прежде чем фича попадет на прод, в наше время сложных оркестраторов и CI/CD предстоит пройти долгий путь от коммита до тестов и доставки. Раньше можно было кинуть новые файлы по FTP (так больше никто не делает, верно?), и процесс «деплоя» занимал секунды. Теперь же надо создать merge request и ждать немалое время, пока фича доберётся до пользователей.

Часть этого пути — сборка Docker-образа. Иногда сборка длится минуты, иногда — десятки минут, что сложно назвать нормальным. В данной статье возьмём простое приложение, которое упакуем в образ, применим несколько методов для ускорения сборки и рассмотрим нюансы работы этих методов.

У нас неплохой опыт создания и поддержки сайтов СМИ: ТАСС, The Bell, «Новая газета», Republic… Не так давно мы пополнили портфолио, выпустив в прод сайт Reminder. И пока быстро допиливали новые фичи и чинили старые баги, медленный деплой стал большой проблемой.

Деплой мы делаем на GitLab. Собираем образы, пушим в GitLab Registry и раскатываем на проде. В этом списке самое долгое — это сборка образов. Для примера: без оптимизации каждая сборка бэкенда занимала 14 минут.

В конце концов стало понятно, что так жить больше нельзя, и мы сели разобраться, почему образы собираются столько времени. В итоге удалось сократить время сборки до 30 секунд!

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

ng n app

Добавляем в него PWA (мы же прогрессивные):

ng add @angular/pwa --project app

Пока скачивается миллион npm-пакетов, давайте разберемся, как устроен docker-образ. Docker предоставляет возможность упаковывать приложения и запускать их в изолированном окружении, которое называется контейнер. Благодаря изоляции можно запускать одновременно много контейнеров на одном сервере. Контейнеры значительно легче виртуальных машин, поскольку выполняются напрямую на ядре системы. Чтобы запустить контейнер с нашим приложением, нам нужно сначала создать образ, в котором мы упакуем всё, что необходимо для работы нашего приложения. По сути образ — это слепок файловой системы. К примеру, возьмём Dockerfile:

FROM node:12.16.2
WORKDIR /app
COPY . .
RUN npm ci
RUN npm run build --prod

Dockerfile — это набор инструкций; выполняя каждую из них, Docker будет сохранять изменения в файловой системе и накладывать их на предыдущие. Каждая команда создаёт свой слой. А готовый образ — это объединённые вместе слои.

Что важно знать: каждый слой докер умеет кэшировать. Если ничего не изменилось с прошлой сборки, то вместо выполнения команды докер возьмёт уже готовый слой. Поскольку основной прирост в скорости сборки будет за счет использования кэша, в замерах скорости сборки будем обращать внимание именно на сборку образа с готовым кэшем. Итак, по шагам:

  1. Удаляем образы локально, чтобы предыдущие запуски не влияли на тест.
    docker rmi $(docker images -q)
  2. Запускаем билд первый раз.
    time docker build -t app .
  3. Меняем файл src/index.html — имитируем работу программиста.
  4. Запускаем билд второй раз.
    time docker build -t app .

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

Положим Dockerfile, описанный выше, в папку с проектом и запустим сборку. Все приведённые листинги сокращены для удобства чтения.

$ time docker build -t app .
Sending build context to Docker daemon 409MB
Step 1/5 : FROM node:12.16.2
Status: Downloaded newer image for node:12.16.2
Step 2/5 : WORKDIR /app
Step 3/5 : COPY . .
Step 4/5 : RUN npm ci
added 1357 packages in 22.47s
Step 5/5 : RUN npm run build --prod
Date: 2020-04-16T19:20:09.664Z - Hash: fffa0fddaa3425c55dd3 - Time: 37581ms
Successfully built c8c279335f46
Successfully tagged app:latest

real 5m4.541s
user 0m0.000s
sys 0m0.000s

Меняем содержимое src/index.html и запускаем второй раз.

$ time docker build -t app .
Sending build context to Docker daemon 409MB
Step 1/5 : FROM node:12.16.2
Step 2/5 : WORKDIR /app
 ---> Using cache
Step 3/5 : COPY . .
Step 4/5 : RUN npm ci
added 1357 packages in 22.47s
Step 5/5 : RUN npm run build --prod
Date: 2020-04-16T19:26:26.587Z - Hash: fffa0fddaa3425c55dd3 - Time: 37902ms
Successfully built 79f335df92d3
Successfully tagged app:latest

real 3m33.262s
user 0m0.000s
sys 0m0.000s

Чтобы посмотреть, получился ли у нас образ, выполним команду docker images:

REPOSITORY   TAG      IMAGE ID       CREATED              SIZE
app          latest   79f335df92d3   About a minute ago   1.74GB

Перед сборкой докер берет все файлы в текущем контексте и отправляет их своему демону Sending build context to Docker daemon 409MB. Контекст для сборки указывается последним аргументом команды build. В нашем случае это текущая директория — «.», — и докер тащит всё, что есть у нас в этой папке. 409 Мбайт — это много: давайте думать, как это исправить.

Уменьшаем контекст

Чтобы уменьшить контекст, есть два варианта. Либо положить все файлы, нужные для сборки, в отдельную папку и указывать контекст докеру именно на эту папку. Это может быть не всегда удобно, поэтому есть возможность указать исключения: что не надо тащить в контекст. Для этого положим в проект файл .dockerignore и укажем, что не нужно для сборки:

.git
/node_modules

и запустим сборку ещё раз:

$ time docker build -t app .
Sending build context to Docker daemon 607.2kB
Step 1/5 : FROM node:12.16.2
Step 2/5 : WORKDIR /app
 ---> Using cache
Step 3/5 : COPY . .
Step 4/5 : RUN npm ci
added 1357 packages in 22.47s
Step 5/5 : RUN npm run build --prod
Date: 2020-04-16T19:33:54.338Z - Hash: fffa0fddaa3425c55dd3 - Time: 37313ms
Successfully built 4942f010792a
Successfully tagged app:latest

real 1m47.763s
user 0m0.000s
sys 0m0.000s

607.2 Кбайт — намного лучше, чем 409 Мбайт. А ещё мы уменьшили размер образа с 1.74 до 1.38Гбайт:

REPOSITORY   TAG      IMAGE ID       CREATED         SIZE
app          latest   4942f010792a   3 minutes ago   1.38GB

Давайте попробуем ещё уменьшить размер образа.

Используем Alpine

Ещё один способ сэкономить на размере образа — использовать маленький родительский образ. Родительский образ — это образ, на основе которого готовится наш образ. Нижний слой указывается командой FROM в Dockerfile. В нашем случае мы используем образ на основе Ubuntu, в котором уже стоит nodejs. И весит он …

$ docker images -a | grep node
node 12.16.2 406aa3abbc6c 17 minutes ago 916MB

… почти гигабайт. Изрядно сократить объем можно, используя образ на основе Alpine Linux. Alpine — это очень маленький линукс. Докер-образ для nodejs на основе alpine весит всего 88.5 Мбайт. Поэтому давайте заменим наш жиииирный вдомах образ:

FROM node:12.16.2-alpine3.11
RUN apk --no-cache --update --virtual build-dependencies add \
    python \
    make \
    g++
WORKDIR /app
COPY . .
RUN npm ci
RUN npm run build --prod

Нам пришлось установить некоторые штуки, которые необходимы для сборки приложения. Да, Angular не собирается без питона ¯(°_o)/¯

Но зато размер образа сбросил 619 Мбайт:

REPOSITORY   TAG      IMAGE ID       CREATED          SIZE
app          latest   aa031edc315a   22 minutes ago   761MB

Идём ещё дальше.

Мультистейдж сборка

Не всё, что есть в образе, нужно нам в продакшене.

$ docker run app ls -lah
total 576K
drwxr-xr-x 1 root root 4.0K Apr 16 19:54 .
drwxr-xr-x 1 root root 4.0K Apr 16 20:00 ..
-rwxr-xr-x 1 root root 19 Apr 17 2020 .dockerignore
-rwxr-xr-x 1 root root 246 Apr 17 2020 .editorconfig
-rwxr-xr-x 1 root root 631 Apr 17 2020 .gitignore
-rwxr-xr-x 1 root root 181 Apr 17 2020 Dockerfile
-rwxr-xr-x 1 root root 1020 Apr 17 2020 README.md
-rwxr-xr-x 1 root root 3.6K Apr 17 2020 angular.json
-rwxr-xr-x 1 root root 429 Apr 17 2020 browserslist
drwxr-xr-x 3 root root 4.0K Apr 16 19:54 dist
drwxr-xr-x 3 root root 4.0K Apr 17 2020 e2e
-rwxr-xr-x 1 root root 1015 Apr 17 2020 karma.conf.js
-rwxr-xr-x 1 root root 620 Apr 17 2020 ngsw-config.json
drwxr-xr-x 1 root root 4.0K Apr 16 19:54 node_modules
-rwxr-xr-x 1 root root 494.9K Apr 17 2020 package-lock.json
-rwxr-xr-x 1 root root 1.3K Apr 17 2020 package.json
drwxr-xr-x 5 root root 4.0K Apr 17 2020 src
-rwxr-xr-x 1 root root 210 Apr 17 2020 tsconfig.app.json
-rwxr-xr-x 1 root root 489 Apr 17 2020 tsconfig.json
-rwxr-xr-x 1 root root 270 Apr 17 2020 tsconfig.spec.json
-rwxr-xr-x 1 root root 1.9K Apr 17 2020 tslint.json

С помощью docker run app ls -lah мы запустили контейнер на основе нашего образа app и выполнили в нем команду ls -lah, после чего контейнер завершил свою работу.

На проде нам нужна только папка dist. При этом файлы как-то нужно отдавать наружу. Можно запустить какой-нибудь HTTP-сервер на nodejs. Но мы сделаем проще. Угадайте русское слово, в котором четыре буквы «ы». Правильно! Ынжыныксы. Возьмём образ с nginx, положим в него папку dist и небольшой конфиг:

server {
    listen 80 default_server;
    server_name localhost;
    charset utf-8;
    root /app/dist;

    location / {
        try_files $uri $uri/ /index.html;
    }
}

Это всё провернуть нам поможет multi-stage build. Изменим наш Dockerfile:

FROM node:12.16.2-alpine3.11 as builder
RUN apk --no-cache --update --virtual build-dependencies add \
    python \
    make \
    g++
WORKDIR /app
COPY . .
RUN npm ci
RUN npm run build --prod

FROM nginx:1.17.10-alpine
RUN rm /etc/nginx/conf.d/default.conf
COPY nginx/static.conf /etc/nginx/conf.d
COPY --from=builder /app/dist/app .

Теперь у нас две инструкции FROM в Dockerfile, каждая из них запускает свой этап сборки. Первый мы назвали builder, а вот начиная с последнего FROM будет готовиться наш итоговый образ. Последним шагом копируем артефакт нашей сборки в предыдущем этапе в итоговый образ с nginx. Размер образа существенно уменьшился:

REPOSITORY   TAG      IMAGE ID       CREATED          SIZE
app          latest   2c6c5da07802   29 minutes ago   36MB

Давайте запустим контейнер с нашим образом и убедимся, что всё работает:

docker run -p8080:80 app

Опцией -p8080:80 мы пробросили порт 8080 на нашей хостовой машине до порта 80 внутри контейнера, где крутится nginx. Открываем в браузере http://localhost:8080/ и видим наше приложение. Всё работает!

Уменьшение размера образа с 1.74 Гбайт до 36 Мбайт значительно сокращает время доставки вашего приложения в прод. Но давайте вернёмся ко времени сборки.

$ time docker build -t app .
Sending build context to Docker daemon 608.8kB
Step 1/11 : FROM node:12.16.2-alpine3.11 as builder
Step 2/11 : RUN apk --no-cache --update --virtual build-dependencies add python make g++
 ---> Using cache
Step 3/11 : WORKDIR /app
 ---> Using cache
Step 4/11 : COPY . .
Step 5/11 : RUN npm ci
added 1357 packages in 47.338s
Step 6/11 : RUN npm run build --prod
Date: 2020-04-16T21:16:03.899Z - Hash: fffa0fddaa3425c55dd3 - Time: 39948ms
 ---> 27f1479221e4
Step 7/11 : FROM nginx:stable-alpine
Step 8/11 : WORKDIR /app
 ---> Using cache
Step 9/11 : RUN rm /etc/nginx/conf.d/default.conf
 ---> Using cache
Step 10/11 : COPY nginx/static.conf /etc/nginx/conf.d
 ---> Using cache
Step 11/11 : COPY --from=builder /app/dist/app .
Successfully built d201471c91ad
Successfully tagged app:latest

real 2m17.700s
user 0m0.000s
sys 0m0.000s

Меняем порядок слоёв

Первые три шага у нас были закэшированы (подсказка Using cache). На четвёртом шаге копируются все файлы проекта и на пятом шаге ставятся зависимости RUN npm ci — целых 47.338s. Зачем каждый раз заново ставить зависимости, если они меняются очень редко? Давайте разберемся, почему они не закэшировались. Дело в том, что докер проверят слой за слоем, не поменялась ли команда и файлы, связанные с ней. На четвёртом шаге мы копируем все файлы нашего проекта, и среди них, конечно же, есть изменения, поэтому докер не только не берет из кэша этот слой, но и все последующие! Давайте внесём небольшие изменения в Dockerfile.

FROM node:12.16.2-alpine3.11 as builder
RUN apk --no-cache --update --virtual build-dependencies add \
    python \
    make \
    g++
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build --prod

FROM nginx:1.17.10-alpine
RUN rm /etc/nginx/conf.d/default.conf
COPY nginx/static.conf /etc/nginx/conf.d
COPY --from=builder /app/dist/app .

Сначала копируются package.json и package-lock.json, затем ставятся зависимости, а только после этого копируется весь проект. В результате:

$ time docker build -t app .
Sending build context to Docker daemon 608.8kB
Step 1/12 : FROM node:12.16.2-alpine3.11 as builder
Step 2/12 : RUN apk --no-cache --update --virtual build-dependencies add python make g++
 ---> Using cache
Step 3/12 : WORKDIR /app
 ---> Using cache
Step 4/12 : COPY package*.json ./
 ---> Using cache
Step 5/12 : RUN npm ci
 ---> Using cache
Step 6/12 : COPY . .
Step 7/12 : RUN npm run build --prod
Date: 2020-04-16T21:29:44.770Z - Hash: fffa0fddaa3425c55dd3 - Time: 38287ms
 ---> 1b9448c73558
Step 8/12 : FROM nginx:stable-alpine
Step 9/12 : WORKDIR /app
 ---> Using cache
Step 10/12 : RUN rm /etc/nginx/conf.d/default.conf
 ---> Using cache
Step 11/12 : COPY nginx/static.conf /etc/nginx/conf.d
 ---> Using cache
Step 12/12 : COPY --from=builder /app/dist/app .
Successfully built a44dd7c217c3
Successfully tagged app:latest

real 0m46.497s
user 0m0.000s
sys 0m0.000s

46 секунд вместо 3 минут — значительно лучше! Важен правильный порядок слоёв: сначала копируем то, что не меняется, затем то, что редко меняется, а в конце — то, что часто.

Далее немного слов о сборке образов в CI/CD системах.

Использование предыдущих образов для кэша

Если мы используем для сборки какое-то SaaS-решение, то локальный кэш докера может оказаться чист и свеж. Чтобы докеру было откуда взять испеченные слои, дайте ему предыдущий собранный образ.

Рассмотрим для примера сборку нашего приложения в GitHub Actions. Используем такой конфиг

on:
  push:
    branches:
      - master

name: Test docker build

jobs:
  deploy:
    name: Build
    runs-on: ubuntu-latest
    env:
      IMAGE_NAME: docker.pkg.github.com/${{ github.repository }}/app
      IMAGE_TAG: ${{ github.sha }}

    steps:
    - name: Checkout
      uses: actions/checkout@v2

    - name: Login to GitHub Packages
      env:
        TOKEN: ${{ secrets.GITHUB_TOKEN }}
      run: |
        docker login docker.pkg.github.com -u $GITHUB_ACTOR -p $TOKEN

    - name: Build
      run: |
        docker build \
          -t $IMAGE_NAME:$IMAGE_TAG \
          -t $IMAGE_NAME:latest \
          .

    - name: Push image to GitHub Packages
      run: |
        docker push $IMAGE_NAME:latest
        docker push $IMAGE_NAME:$IMAGE_TAG

    - name: Logout
      run: |
        docker logout docker.pkg.github.com

Образ собирается и пушится в GitHub Packages за две минуты и 20 секунд:

Теперь изменим сборку так, чтобы использовался кэш на основе предыдущих собранных образов:

on:
  push:
    branches:
      - master

name: Test docker build

jobs:
  deploy:
    name: Build
    runs-on: ubuntu-latest
    env:
      IMAGE_NAME: docker.pkg.github.com/${{ github.repository }}/app
      IMAGE_TAG: ${{ github.sha }}

    steps:
    - name: Checkout
      uses: actions/checkout@v2

    - name: Login to GitHub Packages
      env:
        TOKEN: ${{ secrets.GITHUB_TOKEN }}
      run: |
        docker login docker.pkg.github.com -u $GITHUB_ACTOR -p $TOKEN

    - name: Pull latest images
      run: |
        docker pull $IMAGE_NAME:latest || true
        docker pull $IMAGE_NAME-builder-stage:latest || true

    - name: Images list
      run: |
        docker images

    - name: Build
      run: |
        docker build \
          --target builder \
          --cache-from $IMAGE_NAME-builder-stage:latest \
          -t $IMAGE_NAME-builder-stage \
          .
        docker build \
          --cache-from $IMAGE_NAME-builder-stage:latest \
          --cache-from $IMAGE_NAME:latest \
          -t $IMAGE_NAME:$IMAGE_TAG \
          -t $IMAGE_NAME:latest \
          .

    - name: Push image to GitHub Packages
      run: |
        docker push $IMAGE_NAME-builder-stage:latest
        docker push $IMAGE_NAME:latest
        docker push $IMAGE_NAME:$IMAGE_TAG

    - name: Logout
      run: |
        docker logout docker.pkg.github.com

Для начала нужно рассказать, почему запускается две команды build. Дело в том, что в мультистейдж-сборке результирующим образом будет набор слоёв из последнего стейджа. При этом слои из предыдущих стейджей не попадут в образ. Поэтому при использовании финального образа с предыдущей сборки Docker не сможет найти готовые слои для сборки образа c nodejs (стейдж builder). Для того чтобы решить эту проблему, создаётся промежуточный образ $IMAGE_NAME-builder-stage и отправляется в GitHub Packages, чтобы его можно было использовать в последующей сборке как источник кэша.

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

Предварительное создание образов

Ещё один способ решить проблему чистого кэша докера — часть слоёв вынести в другой Dockerfile, собрать его отдельно, запушить в Container Registry и использовать как родительский.

Создаём свой образ nodejs для сборки Angular-приложения. Создаём в проекте Dockerfile.node

FROM node:12.16.2-alpine3.11
RUN apk --no-cache --update --virtual build-dependencies add \
    python \
    make \
    g++

Собираем и пушим публичный образ в Docker Hub:

docker build -t exsmund/node-for-angular -f Dockerfile.node .
docker push exsmund/node-for-angular:latest

Теперь в нашем основном Dockerfile используем готовый образ:

FROM exsmund/node-for-angular:latest as builder
...

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

Мы рассмотрели несколько методов ускорения сборки докер-образов. Если хочется, чтобы деплой проходил быстро, попробуйте применить в своём проекте:

  • уменьшение контекста;
  • использование небольших родительских образов;
  • мультистейдж-сборку;
  • изменение порядка инструкций в Dockerfile, чтобы эффективно использовать кэш;
  • настройку кэша в CI/CD-системах;
  • предварительное создание образов.

Надеюсь, на примере станет понятнее, как работает Docker, и вы сможете оптимально настроить ваш деплой. Для того, чтобы поиграться с примерами из статьи, создан репозиторий https://github.com/devopsprodigy/test-docker-build.

Multiple layers in a Windows Docker image can cause resource contention. Optimization requires you to limit the number of layers that you use and minimize each layer’s size.

Optimization is an important part of creating Docker images on Windows, and it’s a good idea to reduce the number and size of layers whenever possible.

On the surface, optimization might seem somewhat unimportant given the small size and low overhead of most containers. Although containers generally aren’t overly resource-intensive, container hosts often run large numbers of containers simultaneously. As you run more containers, the number of relatively small inefficiencies increases. As such, it makes sense to optimize your Docker images to ensure improved performance and better use of hardware resources.

Reduce the number of layers

Docker images contain a minimum of two layers: the base OS layer and a layer containing image instructions. Each time you use the Run command, you add another layer to the image. For example, a Dockerfile containing three Run instructions results in four separate layers: one layer for the base image and one layer for each of the three Run instructions.

There are valid reasons for creating additional layers, but it’s important to avoid creating layers haphazardly to better mitigate reduced system performance. If you want to know how many layers make up an image, simply enter the Docker History command, followed by the image name.

One way to reduce the number of image layers is to partner multiple instructions with each layer. Normally, the words PowerShell and command follow a Run command. A PowerShell command is then added. For example, if you want to install Internet Information Services (IIS), you can use the following command:

Run PowerShell -command Install-WindowsFeature -Name Web-Server

You can use subsequent Run commands to install additional Windows components or to configure components you previously installed. For example, if you want to install ASP.NET to the IIS server, you can use the command below:

Run PowerShell -command Add-WindowsFeature Web-asp-net

Although this approach does work, you’ll end up with a three-layered image. One layer is the base OS layer, and then there’s a layer for IIS and a layer for ASP.NET. A better option would be to combine IIS and ASP.NET into a single layer. This reduces the image size and improves performance.

Each time you use the Run command, you add another layer to the image.

The trick to combining multiple PowerShell commands into a single Run statement is to separate each command with a semicolon and add a backslash to the end of each line of code. The semicolon tells Windows that it has reached the end of one PowerShell command and the beginning of the next. The backslash indicates that Windows should perform a line wrap, which treats whatever instructions that appear on the next line as a continuation of the current line of code. Here is how to combine the previous instructions into a single layer:

Run PowerShell -command Install-WindowsFeature -Name Web-Server ; \
Add-WindowsFeature Web-asp-net

The first command ends with a semicolon, indicating that there are additional PowerShell commands to process. The backslash tells Windows to read the next command from the next line. Structuring the commands this way reduces the number of layers from three to two because all of the PowerShell commands are running as a part of a single Run command. Incidentally, you can use this technique to link as many PowerShell commands together as you need.

Remove unnecessary files

Another technique you can use to optimize Docker images is to remove anything you don’t need. For example, it’s relatively common for a Run command to download PHP from Windows.php.net. The PHP download consists of a zip file. To extract the zip file’s contents, use the Expand-Archive cmdlet. Then, you can use the Remove-Item cmdlet to remove the zip file from the image to reduce its size.

If you’re going to use the Remove-Item cmdlet, specify the Remove-Item cmdlet in the same Run command you use to download the zip file. Otherwise, you’ll end up with one layer containing the zip file and another layer in which the zip file has been removed. Here is what the actual code might look like:

Run PowerShell -command wget -Uri http://windows.php.net/downloads/releases/php-5.5.33-Win32-VC11-x86.zip -OutFile c:\php.zip ; \
Expand-Archive -Path c:\php.zip -DestinationPath c:\php ; \
Remove-Item c:\php.zip

If you want to make the code a bit easier to read, you can use a separate line for the Run PowerShell command portion. You simply need to place a backslash at the end of the text; a semicolon isn’t required:

Run PowerShell -command \
wget -Uri http://windows.php.net/downloads/releases/php-5.5.33-Win32-VC11-x86.zip -OutFile c:\php.zip ; \
Expand-Archive -Path c:\php.zip -DestinationPath c:\php ; \
Remove-Item c:\php.zip

Next Steps

Combine PowerShell and Docker to simplify testing across OSes

Dig Deeper on Containers and virtualization

  • How to copy files from source to destination in PowerShell

    By: Anthony Howell

  • How to fix Windows 11 desktops after CrowdStrike outage

    By: Brien Posey

  • How to deploy a LAMP stack

    By: Damon Garn

  • How to run a Jar file

    By: Cameron McKenzie

Понравилась статья? Поделить с друзьями:
0 0 голоса
Рейтинг статьи
Подписаться
Уведомить о
guest

0 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии
  • Как установить windows 7 пиратка
  • Настройка temp windows 10
  • Добавить пользователя в группу windows 10 командная строка
  • Windows message queuing что это
  • Зачем нужны обновления безопасности windows