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

Приложение в этой главе не предназначено для использования в production без доработки. Готовое к работе в production приложение мы получим в конце руководства.

Подготовка окружения

Подготовьте рабочее окружение согласно инструкциями главы “Подготовка окружения”, если это ещё не сделано.

Рабочее окружение работало, но перестало? Инструкции из этой главы не работают? Может помочь:

Работает ли Docker?

Запустим приложение Docker Desktop. Приложению понадобится некоторое время для того, чтобы запустить Docker. Если никаких ошибок в процессе запуска не возникло, то проверим, что Docker запущен и корректно настроен:

docker run hello-world

Результат успешного выполнения команды:

Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
b8dfde127a29: Pull complete
Digest: sha256:9f6ad537c5132bcce57f7a0a20e317228d382c3cd61edae14650eec68b2b345c
Status: Downloaded newer image for hello-world:latest

Hello from Docker!
This message shows that your installation appears to be working correctly.

При возникновении проблем обратитесь к документации Docker для их устранения.

Запустим приложение Docker Desktop. Приложению понадобится некоторое время для того, чтобы запустить Docker. Если никаких ошибок в процессе запуска не возникло, то проверим, что Docker запущен и корректно настроен:

docker run hello-world

Результат успешного выполнения команды:

Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
b8dfde127a29: Pull complete
Digest: sha256:9f6ad537c5132bcce57f7a0a20e317228d382c3cd61edae14650eec68b2b345c
Status: Downloaded newer image for hello-world:latest

Hello from Docker!
This message shows that your installation appears to be working correctly.

При возникновении проблем обратитесь к документации Docker для их устранения.

Запустим Docker:

sudo systemctl restart docker

Убедимся, что Docker запустился:

sudo systemctl status docker

Результат выполнения команды, если Docker успешно запущен:

● docker.service - Docker Application Container Engine
     Loaded: loaded (/lib/systemd/system/docker.service; enabled; vendor preset: enabled)
     Active: active (running) since Thu 2021-06-24 13:05:17 MSK; 13s ago
TriggeredBy: ● docker.socket
       Docs: https://docs.docker.com
   Main PID: 2013888 (dockerd)
      Tasks: 36
     Memory: 100.3M
     CGroup: /system.slice/docker.service
             └─2013888 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock

dockerd[2013888]: time="2021-06-24T13:05:16.936197880+03:00" level=warning msg="Your kernel does not support CPU realtime scheduler"
dockerd[2013888]: time="2021-06-24T13:05:16.936219851+03:00" level=warning msg="Your kernel does not support cgroup blkio weight"
dockerd[2013888]: time="2021-06-24T13:05:16.936224976+03:00" level=warning msg="Your kernel does not support cgroup blkio weight_device"
dockerd[2013888]: time="2021-06-24T13:05:16.936311001+03:00" level=info msg="Loading containers: start."
dockerd[2013888]: time="2021-06-24T13:05:17.119938367+03:00" level=info msg="Loading containers: done."
dockerd[2013888]: time="2021-06-24T13:05:17.134054120+03:00" level=info msg="Daemon has completed initialization"
systemd[1]: Started Docker Application Container Engine.
dockerd[2013888]: time="2021-06-24T13:05:17.148493957+03:00" level=info msg="API listen on /run/docker.sock"

Теперь проверим, что Docker доступен и корректно настроен:

docker run hello-world

Результат успешного выполнения команды:

Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
b8dfde127a29: Pull complete
Digest: sha256:9f6ad537c5132bcce57f7a0a20e317228d382c3cd61edae14650eec68b2b345c
Status: Downloaded newer image for hello-world:latest

Hello from Docker!
This message shows that your installation appears to be working correctly.

При возникновении проблем обратитесь к документации Docker для их устранения.

Перезагружали компьютер после подготовки окружения?

Запустим кластер minikube, уже настроенный в начале главы “Подготовка окружения”:

minikube start

Выставим Namespace по умолчанию, чтобы не указывать его при каждом вызове kubectl:

kubectl config set-context minikube --namespace=werf-guide-app

Результат успешного выполнения команды:

😄  minikube v1.20.0 on Ubuntu 20.04
✨  Using the docker driver based on existing profile
👍  Starting control plane node minikube in cluster minikube
🚜  Pulling base image ...
🎉  minikube 1.21.0 is available! Download it: https://github.com/kubernetes/minikube/releases/tag/v1.21.0
💡  To disable this notice, run: 'minikube config set WantUpdateNotification false'

🔄  Restarting existing docker container for "minikube" ...
🐳  Preparing Kubernetes v1.20.2 on Docker 20.10.6 ...
🔎  Verifying Kubernetes components...
    ▪ Using image gcr.io/google_containers/kube-registry-proxy:0.4
    ▪ Using image k8s.gcr.io/ingress-nginx/controller:v0.44.0
    ▪ Using image registry:2.7.1
    ▪ Using image docker.io/jettech/kube-webhook-certgen:v1.5.1
    ▪ Using image docker.io/jettech/kube-webhook-certgen:v1.5.1
    ▪ Using image gcr.io/k8s-minikube/storage-provisioner:v5
🔎  Verifying registry addon...
🔎  Verifying ingress addon...
🌟  Enabled addons: storage-provisioner, registry, default-storageclass, ingress
🏄  Done! kubectl is now configured to use "minikube" cluster and "werf-guide-app" namespace by default

Убедитесь, что вывод команды содержит строку:

Restarting existing docker container for "minikube"

Если её нет, значит был создан новый кластер minikube вместо использования старого. В таком случае повторите установку рабочего окружения с minikube с самого начала.

Теперь запустите команду в фоновом PowerShell-терминале и не закрывайте его:

minikube tunnel --cleanup=true

Запустим кластер minikube, уже настроенный в начале главы “Подготовка окружения”:

minikube start --namespace werf-guide-app

Выставим Namespace по умолчанию, чтобы не указывать его при каждом вызове kubectl:

kubectl config set-context minikube --namespace=werf-guide-app

Результат успешного выполнения команды:

😄  minikube v1.20.0 on Ubuntu 20.04
✨  Using the docker driver based on existing profile
👍  Starting control plane node minikube in cluster minikube
🚜  Pulling base image ...
🎉  minikube 1.21.0 is available! Download it: https://github.com/kubernetes/minikube/releases/tag/v1.21.0
💡  To disable this notice, run: 'minikube config set WantUpdateNotification false'

🔄  Restarting existing docker container for "minikube" ...
🐳  Preparing Kubernetes v1.20.2 on Docker 20.10.6 ...
🔎  Verifying Kubernetes components...
    ▪ Using image gcr.io/google_containers/kube-registry-proxy:0.4
    ▪ Using image k8s.gcr.io/ingress-nginx/controller:v0.44.0
    ▪ Using image registry:2.7.1
    ▪ Using image docker.io/jettech/kube-webhook-certgen:v1.5.1
    ▪ Using image docker.io/jettech/kube-webhook-certgen:v1.5.1
    ▪ Using image gcr.io/k8s-minikube/storage-provisioner:v5
🔎  Verifying registry addon...
🔎  Verifying ingress addon...
🌟  Enabled addons: storage-provisioner, registry, default-storageclass, ingress
🏄  Done! kubectl is now configured to use "minikube" cluster and "werf-guide-app" namespace by default

Убедитесь, что вывод команды содержит строку:

Restarting existing docker container for "minikube"

Если её нет, значит был создан новый кластер minikube вместо использования старого. В таком случае повторите установку рабочего окружения с minikube с самого начала.

Случайно удаляли Namespace приложения?

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

kubectl create namespace werf-guide-app
kubectl create secret docker-registry registrysecret \
  --docker-server='https://index.docker.io/v1/' \
  --docker-username='<имя пользователя Docker Hub>' \
  --docker-password='<пароль пользователя Docker Hub>'

Результат успешного выполнения команды:

namespace/werf-guide-app created
secret/registrysecret created
Ничего не помогло, окружение или инструкции по-прежнему не работают?

Если ничего не помогло, то пройдите главу “Подготовка окружения” с начала, подготовив новое окружение с нуля. Если и это не помогло, тогда, пожалуйста, расскажите о своей проблеме в нашем Telegram или оставьте Issue на GitHub, и мы обязательно вам поможем.

Подготовка репозитория

Обновим существующий репозиторий с приложением:

Выполним следующий набор команд в PowerShell:

cd ~/werf-guide/app

# Чтобы увидеть, какие изменения мы собрались вносить далее в этой главе, заменим все файлы приложения
# в репозитории новыми, уже измененными файлами приложения, которые содержат описанные далее изменения.
git rm -r .
cp -Recurse -Force ~/werf-guide/guides/examples/laravel/030_assets/* .
git add .
git commit -m WIP
Посмотреть, какие именно изменения мы произведём
# Показать, какие файлы мы собираемся изменить
git show --stat
# Показать изменения
git show

Выполним следующий набор команд в Bash:

cd ~/werf-guide/app

# Чтобы увидеть, какие изменения мы собрались вносить далее в этой главе, заменим все файлы приложения
# в репозитории новыми, уже измененными файлами приложения, которые содержат описанные далее изменения.
git rm -r .
cp -rf ~/werf-guide/guides/examples/laravel/030_assets/. .
git add .
git commit -m WIP
Посмотреть, какие именно изменения мы произведём
# Показать, какие файлы мы собираемся изменить
git show --stat
# Показать изменения
git show

Не работает? Попробуйте инструкции на вкладке “Начинаю проходить руководство с этой главы” выше.

Подготовим новый репозиторий с приложением:

Выполним следующий набор команд в PowerShell:

# Склонируем репозиторий с примерами в ~/werf-guide/guides, если он ещё не был склонирован
if (-not (Test-Path ~/werf-guide/guides)) {
  git clone https://github.com/werf/werf-guides $env:HOMEPATH/werf-guide/guides
}

# Скопируем файлы приложения (пока без изменений) в ~/werf-guide/app
rm -Recurse -Force ~/werf-guide/app
cp -Recurse -Force ~/werf-guide/guides/examples/laravel/020_logging ~/werf-guide/app

# Сделаем из директории ~/werf-guide/app git-репозиторий
cd ~/werf-guide/app
git init
git add .
git commit -m initial

# Чтобы увидеть, какие изменения мы собрались вносить далее в этой главе, заменим все файлы приложения
# в репозитории новыми, уже измененными файлами приложения, которые содержат описанные далее изменения.
git rm -r .
cp -Recurse -Force ~/werf-guide/guides/examples/laravel/030_assets/* .
git add .
git commit -m WIP
Посмотреть, какие именно изменения мы произведём
# Показать, какие файлы мы собираемся изменить
git show --stat
# Показать изменения
git show

Выполним следующий набор команд в Bash:

# Склонируем репозиторий с примерами в ~/werf-guide/guides, если он ещё не был склонирован
test -e ~/werf-guide/guides || git clone https://github.com/werf/werf-guides ~/werf-guide/guides

# Скопируем файлы приложения (пока без изменений) в ~/werf-guide/app
rm -rf ~/werf-guide/app
cp -rf ~/werf-guide/guides/examples/laravel/020_logging ~/werf-guide/app

# Сделаем из директории ~/werf-guide/app git-репозиторий
cd ~/werf-guide/app
git init
git add .
git commit -m initial

# Чтобы увидеть, какие изменения мы собрались вносить далее в этой главе, заменим все файлы приложения
# в репозитории новыми, уже измененными файлами приложения, которые содержат описанные далее изменения.
git rm -r .
cp -rf ~/werf-guide/guides/examples/laravel/030_assets/. .
git add .
git commit -m WIP
Посмотреть, какие именно изменения мы произведём
# Показать, какие файлы мы собираемся изменить
git show --stat
# Показать изменения
git show

Добавление страницы /image в приложение

Добавим нашему приложению новый endpoint /image, который будет отдавать страницу, использующую набор статических файлов. Для сборки JS-, CSS- и медиафайлов будем использовать штатный laravel-mix, являющийся по сути надстройкой над webpack.

Создадим шаблон HTML-страницы, которая будет доступна по адресу /image и отображать кнопку Get image:

<!DOCTYPE html>
<html>
<head>
    <title>werf-guide-app</title>
    <meta name="viewport" content="width=device-width,initial-scale=1">

    <!-- Загрузим CSS-файлы. -->
    <link rel="stylesheet" href="{{ URL::asset('css/site.css') }}" />

    <!-- Загрузим JS-файлы. -->
    <script type="text/javascript" src="{{ URL::asset('js/image.js') }}"></script>
</head>

<body>
<div id="container">
    <!-- Кнопка "Get image", при нажатии на которую будет выполняться Ajax-запрос. -->
    <button type="button" id="show-image-btn">Get image</button>
</div>
</body>
</html>
<!DOCTYPE html> <html> <head> <title>werf-guide-app</title> <meta name="viewport" content="width=device-width,initial-scale=1"> <!-- Загрузим CSS-файлы. --> <link rel="stylesheet" href="{{ URL::asset('css/site.css') }}" /> <!-- Загрузим JS-файлы. --> <script type="text/javascript" src="{{ URL::asset('js/image.js') }}"></script> </head> <body> <div id="container"> <!-- Кнопка "Get image", при нажатии на которую будет выполняться Ajax-запрос. --> <button type="button" id="show-image-btn">Get image</button> </div> </body> </html>

При нажатии на кнопку Get image должен выполняться Ajax-запрос, с помощью которого мы получим и отобразим SVG-картинку:

window.onload=function(){
  var btn = document.getElementById("show-image-btn")
  btn.addEventListener("click", _ => {
      fetch("/images/werf-logo.svg")
        .then((data) => data.text())
        .then((html) => {
          const svgContainer = document.getElementById("container")
          svgContainer.insertAdjacentHTML("beforeend", html)
          var svg = svgContainer.getElementsByTagName("svg")[0]
          svg.setAttribute("id", "image")
          btn.remove()
        }
      )
    }
  )
}
window.onload=function(){ var btn = document.getElementById("show-image-btn") btn.addEventListener("click", _ => { fetch("/images/werf-logo.svg") .then((data) => data.text()) .then((html) => { const svgContainer = document.getElementById("container") svgContainer.insertAdjacentHTML("beforeend", html) var svg = svgContainer.getElementsByTagName("svg")[0] svg.setAttribute("id", "image") btn.remove() } ) } ) }

Также на странице будет использоваться CSS-файл resources/css/site.css.

JS-файл, CSS-файл и SVG-картинка будут собраны с Webpack и будут доступны в поддиректориях public/...:

$ tree public/
public/
├── css
│   ├── app.css
│   └── site.css
├── favicon.ico
├── images
│   └── werf-logo.svg
├── index.php
├── js
│   ├── app.js
│   ├── app.js.LICENSE.txt
│   └── image.js
├── mix-manifest.json
├── robots.txt
└── web.config

Обновим маршруты, добавив в них новый endpoint /image, обрабатывающий созданный выше HTML-шаблон:

<?php

use App\Http\Controllers\PingController;
use Illuminate\Support\Facades\Route;

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/

Route::get('/', function () {
    return view('welcome');
});

Route::get('/ping', [PingController::class, 'ping']);

Route::get('/image', function () {
    return view('image');
});
<?php use App\Http\Controllers\PingController; use Illuminate\Support\Facades\Route; /* |-------------------------------------------------------------------------- | Web Routes |-------------------------------------------------------------------------- | | Here is where you can register web routes for your application. These | routes are loaded by the RouteServiceProvider within a group which | contains the "web" middleware group. Now create something great! | */ Route::get('/', function () { return view('welcome'); }); Route::get('/ping', [PingController::class, 'ping']); Route::get('/image', function () { return view('image'); });

Приложение обновлено: теперь, в дополнение к уже знакомому по прошлым главам /ping, у приложения есть новый endpoint /image. На последнем отображается страница, использующая для работы разные типы статических файлов.

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

Организация раздачи статических файлов

Статические файлы отдаёт сам NGINX-контейнер, доставая их из своей файловой системы.

Приступим к непосредственной реализации.

Обновление сборки и деплоя

Начнём с реорганизации сборки приложения. Добавляем новую стадию для сборки статических файлы приложения:

...
# Временный образ, в котором произойдет сборка ассетов.
FROM base as assets

# Устанавливаем Node.js.
RUN apk add --no-cache nodejs npm

# Устанавливаем JS-зависимости приложения.
COPY package.json ./
RUN npm install

# Копируем в образ все остальные файлы приложения.
COPY . .

# Собираем ассеты.
RUN npm run prod
... # Временный образ, в котором произойдет сборка ассетов. FROM base as assets # Устанавливаем Node.js. RUN apk add --no-cache nodejs npm # Устанавливаем JS-зависимости приложения. COPY package.json ./ RUN npm install # Копируем в образ все остальные файлы приложения. COPY . . # Собираем ассеты. RUN npm run prod

Полученный результат подкладываем в контейнер с NGINX:

...
# NGINX-образ с публичными файлами.
FROM nginx:stable-alpine as frontend
WORKDIR /www

# Копируем публичные файлы из assets образа.
COPY --from=assets /app/public /www

# Копируем конфигурацию NGINX.
COPY .werf/nginx.conf /etc/nginx/nginx.conf

EXPOSE 8080
... # NGINX-образ с публичными файлами. FROM nginx:stable-alpine as frontend WORKDIR /www # Копируем публичные файлы из assets образа. COPY --from=assets /app/public /www # Копируем конфигурацию NGINX. COPY .werf/nginx.conf /etc/nginx/nginx.conf EXPOSE 8080

Проверка

Теперь попробуем переразвернуть приложение:

werf converge --repo <имя пользователя Docker Hub>/werf-guide-app

Ожидаемый результат:

...
┌ ⛵ image backend
│ ┌ Building stage backend/dockerfile
│ │ backend/dockerfile  Sending build context to Docker daemon  501.2kB
│ │ backend/dockerfile  Step 1/19 : FROM php:8.0-fpm-alpine as base
│ │ backend/dockerfile   ---> 52c511f481c5
...
│ │ backend/dockerfile  Successfully built 1ca8f41e400b
│ │ backend/dockerfile  Successfully tagged 96788dcb-5796-4068-9be4-d5c747184419:latest
│ ├ Info
│ │      name: .../werf-guide-app:2212ff6d5ce7b74c65f7838523ca3aa37be1a4408aca105f907e45bb-1633685776086
│ │        id: 1ca8f41e400b
│ │   created: 2021-10-08 12:36:15 +0300 MSK
│ │      size: 48.8 MiB
│ └ Building stage backend/dockerfile (18.99 seconds)
└ ⛵ image backend (24.49 seconds)

┌ ⛵ image frontend
│ ┌ Building stage frontend/dockerfile
│ │ frontend/dockerfile  Sending build context to Docker daemon  501.2kB
│ │ frontend/dockerfile  Step 1/30 : FROM php:8.0-fpm-alpine as base
│ │ frontend/dockerfile   ---> 52c511f481c5
...
│ │ frontend/dockerfile
│ │ frontend/dockerfile     Laravel Mix v6.0.31
│ │ frontend/dockerfile
│ │ frontend/dockerfile
│ │ frontend/dockerfile  ✔ Compiled Successfully in 6277ms
│ │ frontend/dockerfile  ┌───────────────────────────────────┬───────────┐
│ │ frontend/dockerfile  │                              File │ Size      │
│ │ frontend/dockerfile  ├───────────────────────────────────┼───────────┤
│ │ frontend/dockerfile  │             /images/werf-logo.svg │ 2.67 KiB  │
│ │ frontend/dockerfile  │                        /js/app.js │ 89.4 KiB  │
│ │ frontend/dockerfile  │            /js/app.js.LICENSE.txt │ 336 bytes │
│ │ frontend/dockerfile  │                      /js/image.js │ 356 bytes │
│ │ frontend/dockerfile  │                       css/app.css │ 1 bytes   │
│ │ frontend/dockerfile  │                      css/site.css │ 160 bytes │
│ │ frontend/dockerfile  └───────────────────────────────────┴───────────┘
│ │ frontend/dockerfile  ✔ Mix: Compiled successfully in 6.36s
│ │ frontend/dockerfile  webpack compiled successfully
...
│ │ frontend/dockerfile  Successfully built 1d182d7f314b
│ │ frontend/dockerfile  Successfully tagged 8b6e9f6c-94d7-4a52-af7c-8a97aaae6e2c:latest
│ ├ Info
│ │      name: .../werf-guide-app:73588c2be51068fa6b336746b8cfe11e890bcf7c00b44b94aaa6cb3e-1633685835794
│ │        id: 1d182d7f314b
│ │   created: 2021-10-08 12:37:15 +0300 MSK
│ │      size: 9.4 MiB
│ └ Building stage frontend/dockerfile (77.50 seconds)
└ ⛵ image frontend (82.38 seconds)

┌ Waiting for release resources to become ready
│ ┌ Status progress
│ │ DEPLOYMENT                                                                                      REPLICAS          AVAILABLE           UP-TO-DATE
│ │ werf-guide-app                                                                                  2->1/1            1                   1
│ │ │   POD                                 READY        RESTARTS          STATUS
│ │ ├── guide-app-645c598898-lfncm          2/2          0                 ContainerCreating ->
│ │ │                                                                      Running
│ │ └── guide-app-c5885499f-n9lm4           2/2          0                 Running -> Terminating
│ └ Status progress
└ Waiting for release resources to become ready (9.32 seconds)

Release "werf-guide-app" has been upgraded. Happy Helming!
NAME: werf-guide-app
LAST DEPLOYED: Fri Oct  8 12:37:34 2021
NAMESPACE: werf-guide-app
STATUS: deployed
REVISION: 13
TEST SUITE: None
Running time 102.90 seconds
... ┌ ⛵ image backend │ ┌ Building stage backend/dockerfile │ │ backend/dockerfile Sending build context to Docker daemon 501.2kB │ │ backend/dockerfile Step 1/19 : FROM php:8.0-fpm-alpine as base │ │ backend/dockerfile ---> 52c511f481c5 ... │ │ backend/dockerfile Successfully built 1ca8f41e400b │ │ backend/dockerfile Successfully tagged 96788dcb-5796-4068-9be4-d5c747184419:latest │ ├ Info │ │ name: .../werf-guide-app:2212ff6d5ce7b74c65f7838523ca3aa37be1a4408aca105f907e45bb-1633685776086 │ │ id: 1ca8f41e400b │ │ created: 2021-10-08 12:36:15 +0300 MSK │ │ size: 48.8 MiB │ └ Building stage backend/dockerfile (18.99 seconds) └ ⛵ image backend (24.49 seconds) ┌ ⛵ image frontend │ ┌ Building stage frontend/dockerfile │ │ frontend/dockerfile Sending build context to Docker daemon 501.2kB │ │ frontend/dockerfile Step 1/30 : FROM php:8.0-fpm-alpine as base │ │ frontend/dockerfile ---> 52c511f481c5 ... │ │ frontend/dockerfile │ │ frontend/dockerfile Laravel Mix v6.0.31 │ │ frontend/dockerfile │ │ frontend/dockerfile │ │ frontend/dockerfile ✔ Compiled Successfully in 6277ms │ │ frontend/dockerfile ┌───────────────────────────────────┬───────────┐ │ │ frontend/dockerfile │ File │ Size │ │ │ frontend/dockerfile ├───────────────────────────────────┼───────────┤ │ │ frontend/dockerfile │ /images/werf-logo.svg │ 2.67 KiB │ │ │ frontend/dockerfile │ /js/app.js │ 89.4 KiB │ │ │ frontend/dockerfile │ /js/app.js.LICENSE.txt │ 336 bytes │ │ │ frontend/dockerfile │ /js/image.js │ 356 bytes │ │ │ frontend/dockerfile │ css/app.css │ 1 bytes │ │ │ frontend/dockerfile │ css/site.css │ 160 bytes │ │ │ frontend/dockerfile └───────────────────────────────────┴───────────┘ │ │ frontend/dockerfile ✔ Mix: Compiled successfully in 6.36s │ │ frontend/dockerfile webpack compiled successfully ... │ │ frontend/dockerfile Successfully built 1d182d7f314b │ │ frontend/dockerfile Successfully tagged 8b6e9f6c-94d7-4a52-af7c-8a97aaae6e2c:latest │ ├ Info │ │ name: .../werf-guide-app:73588c2be51068fa6b336746b8cfe11e890bcf7c00b44b94aaa6cb3e-1633685835794 │ │ id: 1d182d7f314b │ │ created: 2021-10-08 12:37:15 +0300 MSK │ │ size: 9.4 MiB │ └ Building stage frontend/dockerfile (77.50 seconds) └ ⛵ image frontend (82.38 seconds) ┌ Waiting for release resources to become ready │ ┌ Status progress │ │ DEPLOYMENT REPLICAS AVAILABLE UP-TO-DATE │ │ werf-guide-app 2->1/1 1 1 │ │ │ POD READY RESTARTS STATUS │ │ ├── guide-app-645c598898-lfncm 2/2 0 ContainerCreating -> │ │ │ Running │ │ └── guide-app-c5885499f-n9lm4 2/2 0 Running -> Terminating │ └ Status progress └ Waiting for release resources to become ready (9.32 seconds) Release "werf-guide-app" has been upgraded. Happy Helming! NAME: werf-guide-app LAST DEPLOYED: Fri Oct 8 12:37:34 2021 NAMESPACE: werf-guide-app STATUS: deployed REVISION: 13 TEST SUITE: None Running time 102.90 seconds

Откроем в браузере http://werf-guide-app/image и нажмём на кнопку Get image. Ожидаемый результат:

Также обратим внимание на то, какие ресурсы были запрошены и по каким ссылкам (последний ресурс здесь получен через Ajax-запрос):

Теперь наше приложение готово выдерживать приличные нагрузки при большом количестве запросов к статическим файлам, и эти запросы не будут сказываться на работе приложения в целом. Масштабирование же php-fpm (отвечает за динамический контент) и NGINX (статический контент) происходит простым увеличением количества реплик (replicas) в Deployment’е приложения.