Single-page Application (SPA) – это набор статических JavaScript и HTML файлов, а так же картинок и других ресурсов. Поскольку они не изменяются динамически, опубликовать их в интернете очень просто. Для этого существует большое количество дешёвых и даже бесплатных сервисов, начиная с простого GitHub Pages (а для кого-то даже с narod.ru) и заканчивая CDN вроде Amazon S3. Однако мне нужно было другое.
Мне нужен был Docker-образ с SPA, чтобы его легко можно было запустить как в продакшене в составе Kubernetes-кластера, так и на машине back-end разработчика, который понятия не имеет, что такое SPA.
Я для себя определил следующие требования к образу:
- простота в использовании (но не в сборке);
- минимальный размер как с точки зрения диска, так и с точки зрения RAM;
- настройка через переменные окружения, чтобы образ можно было использовать в разных средах;
- максимально эффективная раздача файлов.
Сегодня я расскажу как:
- выпотрошить nginx;
- собрать brotli из исходников;
- научить статические файлы понимать переменные окружения;
- ну и конечно как собрать из всего этого Docker-образ.
Цель этой статьи поделиться моим опытом и спровоцировать опытных участников сообщества на конструктивную критику.
Сборка образа для сборки
Чтобы финальный Docker-образ получился маленьким по размеру, нужно придерживаться двух правил: минимум слоёв и минималистичный базовый образ. Одним из самых маленьких базовых образов является образ Alpine Linux, поэтому именно его я и выберу. Кто-то может возразить, что Alpine не подходит для продакшена и, возможно, окажется прав. Но лично у меня с ним никогда не возникало проблем и никаких аргументов против него нет.
Чтобы было поменьше слоёв, я буду собирать образ в 2 этапа. Первый – черновой, в нём останутся все вспомогательные утилиты и временные файлы. А в чистовой я запишу только финальную версию приложения.
Начнём со вспомогательного образа.
Для того, чтобы скомпилировать SPA-приложение, обычно, нужен node.js. Я возьму официальный образ в комплекте с которым так же есть пакетные менеджеры npm и yarn. От себя я добавлю node-gyp, который нужен для сборки некоторых npm-пакетов, и компрессор Brotli от Google, который пригодится нам позже.
# Базовый образ
FROM node:12-alpine
LABEL maintainer="Aleksey Maydokin <amaydokin@gmail.com>"
ENV BROTLI_VERSION 1.0.7
# Пакеты, которые нужны, чтобы собрать из исходников Brotli
RUN apk add --no-cache --virtual .build-deps bash gcc libc-dev make linux-headers cmake curl && mkdir -p /usr/src # Исходники Brotli скачиваем из официального репозитория
&& curl -LSs https://github.com/google/brotli/archive/v$BROTLI_VERSION.tar.gz | tar xzf - -C /usr/src && cd /usr/src/brotli-$BROTLI_VERSION # Компилируем Brotli
&& ./configure-cmake --disable-debug && make -j$(getconf _NPROCESSORS_ONLN) && make install # Добавляем node-gyp
&& yarn global add node-gyp # Убираем за собой мусор
&& apk del .build-deps && yarn cache clean && rm -rf /usr/src
Уже здесь я борюсь за минимализм, поэтому образ собирается одной большой командой.
Готовый образ можно найти тут: https://hub.docker.com/r/alexxxnf/spa-builder. Хотя я рекомендую не полагаться на чужие образы и собрать свой.
nginx
Для раздачи статики можно использовать любой web-сервер. Я привык работать с nginx, поэтому и сейчас буду использовать его.
У nginx есть официальный Docker-образ, однако для простой раздачи статики в нём слишком много модулей. Какие именно включены в поставку можно посмотреть специальной командой или же в официальном Dockerfile.
nginx version: nginx/1.17.9
built by gcc 8.3.0 (Alpine 8.3.0)
built with OpenSSL 1.1.1d 10 Sep 2019
TLS SNI support enabled
configure arguments: --prefix=/etc/nginx --sbin-path=/usr/sbin/nginx --modules-path=/usr/lib/nginx/modules --conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --pid-path=/var/run/nginx.pid --lock-path=/var/run/nginx.lock --http-client-body-temp-path=/var/cache/nginx/client_temp --http-proxy-temp-path=/var/cache/nginx/proxy_temp --http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp --http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp --http-scgi-temp-path=/var/cache/nginx/scgi_temp --with-perl_modules_path=/usr/lib/perl5/vendor_perl --user=nginx --group=nginx --with-compat --with-file-aio --with-threads --with-http_addition_module --with-http_auth_request_module --with-http_dav_module --with-http_flv_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_mp4_module --with-http_random_index_module --with-http_realip_module --with-http_secure_link_module --with-http_slice_module --with-http_ssl_module --with-http_stub_status_module --with-http_sub_module --with-http_v2_module --with-mail --with-mail_ssl_module --with-stream --with-stream_realip_module --with-stream_ssl_module --with-stream_ssl_preread_module --with-cc-opt='-Os -fomit-frame-pointer' --with-ld-opt=-Wl,--as-needed
Я возьму за основу Dockerfile, но оставлю в нём только то, что нужно для раздачи статики. Мой вариант не сможет работать по HTTPS, не будет поддерживать авторизацию и многое другое. Зато моя версия сможет раздавай файлы, сжатые алгоритмом Brotli, который немного эффективнее, чем gzip. Сжимать файлы будем один раз, делать это на лету нет необходимости.
Вот какой Dockerfile у меня получился. Комментарии на русском – мои, на английском – из оригинала.
# Базовый образ снова Alpine
FROM alpine:3.9
LABEL maintainer="Aleksey Maydokin <amaydokin@gmail.com>"
ENV NGINX_VERSION 1.16.0
ENV NGX_BROTLI_VERSION 0.1.2
ENV BROTLI_VERSION 1.0.7
RUN set -x && addgroup -S nginx && adduser -D -S -h /var/cache/nginx -s /sbin/nologin -G nginx nginx # Устанавливаем пакеты, которые нужны чтобы собрать nginx и модуль ngx_brotli к нему
&& apk add --no-cache --virtual .build-deps gcc libc-dev make linux-headers curl && mkdir -p /usr/src # Скачиваем исходники
&& curl -LSs https://nginx.org/download/nginx-$NGINX_VERSION.tar.gz | tar xzf - -C /usr/src && curl -LSs https://github.com/eustas/ngx_brotli/archive/v$NGX_BROTLI_VERSION.tar.gz | tar xzf - -C /usr/src && curl -LSs https://github.com/google/brotli/archive/v$BROTLI_VERSION.tar.gz | tar xzf - -C /usr/src && rm -rf /usr/src/ngx_brotli-$NGX_BROTLI_VERSION/deps/brotli/ && ln -s /usr/src/brotli-$BROTLI_VERSION /usr/src/ngx_brotli-$NGX_BROTLI_VERSION/deps/brotli && cd /usr/src/nginx-$NGINX_VERSION && CNF=" --prefix=/etc/nginx --sbin-path=/usr/sbin/nginx --modules-path=/usr/lib/nginx/modules --conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --pid-path=/var/run/nginx.pid --lock-path=/var/run/nginx.lock --http-client-body-temp-path=/var/cache/nginx/client_temp --http-proxy-temp-path=/var/cache/nginx/proxy_temp --http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp --http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp --http-scgi-temp-path=/var/cache/nginx/scgi_temp --user=nginx --group=nginx --without-http_ssi_module --without-http_userid_module --without-http_access_module --without-http_auth_basic_module --without-http_mirror_module --without-http_autoindex_module --without-http_geo_module --without-http_split_clients_module --without-http_referer_module --without-http_rewrite_module --without-http_proxy_module --without-http_fastcgi_module --without-http_uwsgi_module --without-http_scgi_module --without-http_grpc_module --without-http_memcached_module --without-http_limit_conn_module --without-http_limit_req_module --without-http_empty_gif_module --without-http_browser_module --without-http_upstream_hash_module --without-http_upstream_ip_hash_module --without-http_upstream_least_conn_module --without-http_upstream_keepalive_module --without-http_upstream_zone_module --without-http_gzip_module --with-http_gzip_static_module --with-threads --with-compat --with-file-aio --add-dynamic-module=/usr/src/ngx_brotli-$NGX_BROTLI_VERSION " # Собираем
&& ./configure $CNF && make -j$(getconf _NPROCESSORS_ONLN) && make install && rm -rf /usr/src/ # Удаляем динамический brotli модуль, оставляя только статический
&& rm /usr/lib/nginx/modules/ngx_http_brotli_filter_module.so && sed -i '$ d' /etc/apk/repositories # Bring in gettext so we can get `envsubst`, then throw
# the rest away. To do this, we need to install `gettext`
# then move `envsubst` out of the way so `gettext` can
# be deleted completely, then move `envsubst` back.
&& apk add --no-cache --virtual .gettext gettext && mv /usr/bin/envsubst /tmp/ && runDeps="$( scanelf --needed --nobanner /usr/sbin/nginx /usr/lib/nginx/modules/*.so /tmp/envsubst | awk '{ gsub(/,/, "\nso:", $2); print "so:" $2 }' | sort -u | xargs -r apk info --installed | sort -u )" && apk add --no-cache $runDeps && apk del .build-deps && apk del .gettext && mv /tmp/envsubst /usr/local/bin/ # Bring in tzdata so users could set the timezones through the environment
# variables
&& apk add --no-cache tzdata # forward request and error logs to docker log collector
&& ln -sf /dev/stdout /var/log/nginx/access.log && ln -sf /dev/stderr /var/log/nginx/error.log
COPY nginx.conf /etc/nginx/nginx.conf
EXPOSE 80
STOPSIGNAL SIGTERM
CMD ["nginx", "-g", "daemon off;"]
Я сразу же поправлю nginx.conf, чтобы gzip и brotli были включены по умолчанию. Так же включу кэширующие заголовки, ведь у нас будет раздаваться никогда не меняющаяся статика. И последним штрихом будет переадресация всех 404 запросов на index.html, это необходимо для навигации в SPA.
user nginx;
worker_processes 1;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
load_module /usr/lib/nginx/modules/ngx_http_brotli_static_module.so;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
keepalive_timeout 65;
gzip_static on;
brotli_static on;
server {
listen 80;
server_name localhost;
charset utf-8;
location / {
root html;
try_files $uri /index.html;
etag on;
expires max;
add_header Cache-Control public;
location = /index.html {
expires 0;
add_header Cache-Control "no-cache, public, must-revalidate, proxy-revalidate";
}
}
}
}
Скачать готовый образ можно здесь: https://hub.docker.com/r/alexxxnf/nginx-spa. Он занимает 10,5 МБ. Оригинальный nginx занимал 19,7 МБ. Мой спортивный интерес удовлетворён.
Учим статику понимать переменные окружения
Для чего в SPA могут понадобится настройки? Например, для того, чтобы указать какой RESTful API использовать. Обычно настройки для нужного окружения передаются в SPA на этапе сборки. Если нужно что-то поменять, то придётся пересобрать приложение. Я этого не хочу. Я хочу чтобы приложение собиралось один раз на стадии CI, а конфигурировалось столько, сколько нужно, на стадии CD с помощью переменных окружения.
Разумеется, статические файлы сами по себе не понимают никаких переменных окружения. Поэтому, придётся пойти на хитрость. В финальном образе я буду запускать не nginx, а специальный shell-скрипт, который прочитает пременные окружения, запишит их в статические файлы, сожмёт их и только после этого передаст управление nginx.
Для этого в Dockerfile предусмотрен параметр ENTRYPOINT. Передадим ему вот такой скрипт (на примере Angular):
#!/bin/sh
set -e
FLAG_FILE="/configured"
TARGET_DIR="/etc/nginx/html"
replace_vars () {
ENV_VARS=\'$(awk 'BEGIN{for(v in ENVIRON) print "$"v}')\'
# В Angular ищем плейсхолдеры в main-файлах
for f in "$TARGET_DIR"/main*.js; do
# envsubst заменяет в файлах плейсхолдеры на значения из переменных окружения
echo "$(envsubst "$ENV_VARS" < "$f")" > "$f"
done
}
compress () {
for i in $(find "$TARGET_DIR" | grep -E "\.css$|\.html$|\.js$|\.svg$|\.txt$|\.ttf$"); do
# Используем максимальную степень сжатия
gzip -9kf "$i" && brotli -fZ "$i"
done
}
if [ "$1" = 'nginx' ]; then
# Флаг нужен, чтобы выполнить скрипт только при самом первом запуске
if [ ! -e "$FLAG_FILE" ]; then
echo "Running init script"
echo "Replacing env vars"
replace_vars
echo "Compressing files"
compress
touch $FLAG_FILE
echo "Done"
fi
fi
exec "$@"
Чтобы скрипт сделал своё дело, в js-файлах настройки надо писать вот в таком виде: ${API_URL}
.
Стоит отметить, что большинство современных SPA при сборке добавляют к своим файлам хэши. Это нужно, чтобы браузер мог смело закэшировать файл на длительный срок. Если файл всё-таки изменится, то изменится и его хэш, что в свою очередь заставит браузер скачать файл заново.
К сожалению, в моём методе, изменение конфигурации через переменные окружения не приводит к изменению хэша файла, а значит инвалидировать кэш браузера надо каким-то другим образом. У меня этой проблемы нет, потому что разные конфигурации разворачиваются в разных средах.
Собираем финальный образ
Наконец-то.
# Первый базовый образ для сборки
FROM alexxxnf/spa-builder as builder
# Чтобы эффктивнее использовать кэш Docker-а, сначала устанавливаем только зависимости
COPY ./package.json ./package-lock.json /app/
RUN cd /app && npm ci --no-audit
# Потом собираем само приложение
COPY . /app
RUN cd /app && npm run build -- --prod --configuration=docker
# Второй базовый образ для раздачи
FROM alexxxnf/nginx-spa
# Забираем из первого образа сначала компрессор
COPY --from=builder /usr/local/bin/brotli /usr/local/bin
# Потом добавляем чудо-скрипт
COPY ./docker/docker-entrypoint.sh /docker-entrypoint.sh
# И в конце забираем само приложение
COPY --from=builder /app/dist/app /etc/nginx/html/
ENTRYPOINT ["/docker-entrypoint.sh"]
CMD ["nginx", "-g", "daemon off;"]
Теперь получившийся образ можно собрать и использовать где-угодно.
alexesDev
Статика редко весит больше 20-50мб в архиве. Поэтому скачивать перед стартом — малая кровь за такой простой подход.
dem0n3d
Как раз недавно изучал подобный сценарий. Обнаружилось, что в обычном образе nginx нет ни wget, ни curl, а вот в alpine-варианте есть wget. Можно пойти ещё дальше: статику предварительно сжать и раздавать её со смонтированного RAM-диска (tmpfs). И всё это со стандартным образом nginx-alpine.
Akuma
Скачивание статики перед стартом рушит всю суть контейнеров. Вам придется поддерживать две сущности — версии контейнеров и версии этой самой статики. Это неудобно и приводит к ошибкам. Ну и скачивание может отвалиться в самый неудобный момент, даже с S3.
Честно говоря вообще не понимаю рвение к экономии на образах. Чтобы он весил гигабайты нужно прям постараться.
alexesDev
У меня нет проблем с тем, чтобы хранить ссылку на архив вместо ссылки на image. А вот проблемы, когда у вас 20-50-100 контейнеров со статикой и их нужно обновить все, возникает постоянно.
Akuma
Я все же не пойму о каких проблемах идет речь?
Ну есть некий docker.io/nginx:1.2.3 b 100500 его реплик. Обновили версию — обновились контейнеры, все логично.
Если у вас 100500 контейнеров, которые что-то там выкачивают, вы не можете быть уверены что обновление пройдет удачно, т.к. где-то оно скачается, где-то может быть ошибка. В итоге вы получите то же самое, только большими усилиями. И зачем?
alexesDev
Если оно не скачается, то и не запустится. А rolling update откатит все обратно. Точно так же новый image может быть недоступен. Распределенные registry работают поверх s3 кстати и говорить, что registry надежнее не совсем правильно.
И касательно "рушит всю суть контейнеров". Ссылка на архив мало чем отличается от внешнего апи или подключение к базе данных. Вы же не назовете контейнер неправильным, потому что он подключается к базе?
PS s3 я имею в виду s3 compatible storage, которое есть у всех приличных облаков или больших кластеров (ceph, minio, etc)
Akuma
Вы берете одну точку отказа и добавляете в нее вторую точку отказа вместе с необходимостью следить за соотношением версий. На мой взгляд — это просто не имеет смысла. Если мы просто выкачиваем статику, то куда проще ее хранить в образе же.
alexesDev
Точка отказа s3? С отказом s3 откажет и registry (только если они не пользуются разными s3). high available registry всегда используют внешнее хранилище, s3 самое простое.
Akuma
Точка отказа в самом факте сценария «для запуска контейнера надо что-то скачать». Контейнер должен быть автономный, в идеале. Понятно что ваш пример с подключением к БД не будет автономный, но уж статические файлы можно положить в контейнер.
Представьте что у вас на мышке два провода и ни один не должен порваться «просто потому что было удобнее питание сделать отдельно». Они не должны рваться, все верно, но зачем они нужны, когда можно все совместить?
alexesDev
Да, уволюсь завтра, экспертиза ниже плинтуса =)
Akuma
Это не экспертиза, это опыт работы с различной реализацией статики в некрупных проектах
alexesDev
У вас это
myregistry.com/myproject1:v2
myregistry.com/myproject2:v2
myregistry.com/myproject3:v2
myregistry.com/myproject4:v2
Которые from nginx:1.2.3 с разной статикой.
И если вам нужно внести правки в базовый образ или конфиг nginx для всех проектов, то это правка всех проектов и пересборка всех images (в большой компании с большим количеством проектов это затянется на месяца без острой необходимости)
Akuma
В моей реализации базовый контейнер nginx не содержит статики, он содержит как раз nginx. У вас не может быть одного образа со статикой на все-все проекты.
Если у вас так много зависимостей, что они месяц будут собираться из-за пересбора образа со статикой — вам надо менять архитектуру, ну правда. Какая-то надуманная ситуация. Статика добавляется в финальный образ.
VolCh
Я понял, кажется, поинт:
Наши контейнеры для каждого проекта со статикой содержат
FROM nginx:1.16.1 в докерфайле в репе проекта
Выходит 1.16.2 с закрытием уязвимости. Нам надо пройтись по всем репам, по всем докерфайлам, изменить на 1.16.2, закоммитить, запушить, отдать на код-ревью, сбилдить, тегнуть новый образ, протестировать, изменить конфиги докера (ещё коммиты, пуш, ревью) и т. д. В случае с общим образом и подкачиваемым контент ом, нужно только конфиги докера изменять.
alexesDev
Да, все так. В случае уязвимости еще можно всех напрячь и быстро перекатить, но если там ничего не горит, а поменять хочеться… то это будет изменяться месяцами (завести таску на все проекты в джире и ждать пока низкоприоритетная таска выполнится через все процессы, что вы описали).
Akuma
Я видимо просто не работал с такими неповоротливыми проектами, где обновление версии nginx будет длиться месяц.
Хотя все же не понимаю в чем будет разница с выкачиванием/хранением статики, т.к. и там и там образ менять придется.
VolCh
Характерно для проектов, где много репозиториев, каждым из которых владеют разные команды со своими бэклогами.
Разница в том, что обычно нужно прогнать по всему флоу сначала изменения докерфайла, а потом изменения конфигов докера (docker-compose, swarm, k8s, ...), а в варианте с выкачкой только второе. Процессы разработки и изменения базового образа не пересекаются, не блочат друг друга
Akuma
Но это же не базовый образ. Может я как-то не так объясняю, давайте на своем, живом примере.
Есть образ с nginx, в который внесены некоторые корректировки. Это отдельный «базовый» образ docker-nginx:1.0
В этом образе нет никакой статики, там просто голый nginx. Даже конфигов нет. Этот образ меняется раз в тыщу лет, когда выходит апдейт самого nginx, например.
В проекте (k8s через helm) есть деплоймент, который берет этот образ с указанием версии, моунтит в него конфиги из k8s configmap и запускает. Для дева там запускается прям базовый образ с локальной шареной директорией для статики.
Для прода собирается так (реальный Dockerfile):
FROM registry.gitlab.com/base/docker-nginx:1.0
ADD ./public /usr/share/nginx/html/public
Это билдится в CI/CD гитлаба для конкретного проекта.
Т.е. работает это так:
— Базовый образ не меняется почти никогда
— Для смены конфигов — меняем configmap
— Для смены статики пушим статику в репо, образ билдится, деплоится для конкретного проекта
Получаем образы вида project-nginx:1/2/3/4/5 в каждый из которых вшита статика его версии.
Если у вас много команд, каждая из которых живет на разных планетах — им это все не мешает, у них у каждой свои образы. Не нужно обновлять базовый образ? Не обновляйте, дело ваше.
Я просто не понимаю что такое «прогнать по всему флоу». Если вы меняете статику — вы меняете проект. Это в любом случае коммит, пуш, ревью и все что вы там еще делаете.
А если у вас статика просто где-то валяется «во вне», то я не знаю зачем ее вообще выкачивать в докере, просто раздавайте через cdn и все.
VolCh
Вот, ситуация, обновили базовый образ. Теперь нужно (могут быть нюансы):
7...
В случае с подтягиванием файлов, шаги 1-5 не нужны, сразу начинаем с деплоймент/values
Akuma
Но это же все делается при любых изменениях в проекте. Причем шаги 3 и далее делаются автоматически (ну разве что выкат на прод по кнопке).
Шаг 1 — это поменять один символ.
Шаг 2 — это Ctrl + K, Enter в IDEA.
Ну PR может быть как-то вручную создавать, хотя не все работают через PR.
И каким это образом мы, в случае с подтягиванием файлов, начинаем сразу с шага 6?
Ссылка на выкачивание статики всегда одинаковая что ли? Тогда это убивает всю идею версионности на корню. Как понять какая версия статики сейчас запущена?
Пусть даже мы оставляем это убийство. У вас 100 реп, в каждой запускается «базовый образ». Его обновили. Вы там делаете деплой по latest тегу, что ли? Вряд ли. Тогда точно так же придется править 100 реп.
alexesDev
Ссылка на выкачивание статики берется оттуда же, откуда ссылка на image. Разницы нет.
Конкретно про спор.
FROM registry.gitlab.com/base/docker-nginx:1.0
ADD ./public /usr/share/nginx/html/public
Такой вариант хороший, кроме выше описанной ситуации, когда нужно обновить в куче реп это одну строчку (в больших компаниях реал сложно). Но если маунтить конфиг, то редко нужно.
Касательно подхода с выкачиванием статики… я вспомнил зачем это ввел. Была идея написать k8s оператор, чтобы одним деплойментом nginx сервить всю статику, потому что печально наблюдать 100+ контейнеров nginx в кластере.
VolCh
Вот это действительно хорошая идея. Может не через скачивание, а через PV или типа того я бы начал смотреть. Но куча nginx c одинаковыми конфигами действительно напрягает.
alexesDev
PV это уже stateful + PV нужен ReadWriteMany (я редко работаю с такими, может ReadOnlyMany, но вообще не сталкивался с ним), такие хранилища обычно медленные. Со скачиванием просто получается реаликация с мастером в виде s3.
VolCh
Хоть так, хоть так стейт у вас будет, просто в одном случае пользуетесь нативными для k8s механизмами (под капотом, кстати, тот же s3 может быть), а в другом самописными. Плюсы есть и у того, и у другого варианта.
alexesDev
Ссылка на s3 это как ссылка на внешнее апи или подключение к базе. Из-за этого сервис не приобретает state. Это stateless.
Подход с другой стороны
Сможете ли вы запустить 1кк контейнеров с общим PV? -> без дикого гемора нет (так себя ведут stateful)
Сможете ли вы запускать 1кк контейнеров с выкачиванием? -> да (так себя ведут stateless)
VolCh
Я об единственном кейсе сейчас: обновление базового образа, сами статические файлы не менялись, ссылка не менялась. Всё что нужно изменить таг в чарте/деплойменте. С билдом статики в контейнере нам нужно сделать 200 изменений на 100 реп: 100 в докерфайлах и 100 в чартах, со скачиванием — 100, только в чартах
alexxxnf Автор
По-моему, эта проблема характерна для любых приложений в Docker.
Всегда есть версия самого приложения, которая постепенно растёт, и есть версия базового образа, которая может вырасти неожиданно при закрытии какой-то уязвимости. Ситуация станет многократно хуже, если базовых образов несколько А их почти всегда несколько.
Не знаю, есть ли здесь универсальное решение. Мне кажется, для каждого проекта надо решать отдельно, сколько версий приложения держать с актуальными зависимостями и каким образом их обновлять.
VolCh
Если используем FROM scratch, то проблемы такой нет :) реально такое только с golang видел/делал
А так, да, проблема есть и вариантов для её решения много, какие-то лучше подходят в одной ситуации, какие-то в другой.
alexxxnf Автор
alexesDev
Насчет второго пункта — там все тоже самое. Нет никакой разницы… передавать куда-то ссылку на image или ссылку на архив или ссылку на image+архив вместе.
VolCh
Странный для меня подход. В чём плюсы?
Минусы, навскидку:
Это может иметь какой-то смысл в случае использования сторонних образов, когда свой репозиторий образов вообще не создаёшь, а инфраструкта для билда статики на S3 и её версионирования уже есть. Но в общем случае плюсы мне не понятны, хотя сам использую публичные образы nginx с примонтированными конфигами и, если надо, контентом как фронт для php-fpm. Но то монтирование, а не скачивание. Статика всё равно билдится в наши образы, вопрос лишь какие из них.
alexesDev
Я выше писал часть из этого...
У нас все, что касается разворачивание лежит в отдельном репозитории. Туда не могут лезть все. От команды конкретного проекта требуется только отдать артефакт. Будет это image или архив не так важно (фронтенщикам проще s3, мало кто хорошо умеет собрать образ).
PS Вместо s3 можно использовать артефакты CI/CD у gitlab или еще кого (они так же лежат обычно на s3 https://docs.gitlab.com/ee/administration/job_artifacts.html#s3-compatible-connection-settings). Тогда думать вообще про хранилище не нужно. Но у нас исторически напрямую.