Single Page тиркемесин жайылтуу үчүн докер сүрөтү

Single-page Application (SPA) бул статикалык JavaScript жана HTML файлдарынын, ошондой эле сүрөттөрдүн жана башка ресурстардын жыйындысы. Алар динамикалык түрдө өзгөрбөгөндүктөн, аларды онлайн жарыялоо абдан оңой. Бул үчүн жөнөкөй GitHub баракчаларынан (жана кээ бирлери үчүн narod.ru менен) баштап, Amazon S3 сыяктуу CDN менен аяктаган көптөгөн арзан жана ал тургай бекер кызматтар бар. Бирок, мага дагы бир нерсе керек болчу.

Мага SPA менен Docker сүрөтү керек болчу, аны өндүрүштө да, Kubernetes кластеринин бир бөлүгү катары да, SPA деген эмне экенин билбеген арткы иштеп чыгуучунун машинасында да оңой ишке киргизүү керек болчу.

Мен өзүмө төмөнкү сүрөт талаптарын аныктадым:

  • колдонуунун жөнөкөйлүгү (бирок чогултуу эмес);
  • диск жана RAM жагынан да минималдуу өлчөмү;
  • сүрөт ар кандай чөйрөдө колдонулушу үчүн чөйрө өзгөрмөлөрү аркылуу конфигурациялоо;
  • файлдарды эң натыйжалуу бөлүштүрүү.

Бүгүн мен кантип айтып берем:

  • ичеги nginx;
  • булактардан brotli куруу;
  • чөйрө өзгөрмөлөрүн түшүнүүгө статикалык файлдарды үйрөтүү;
  • жана, албетте, мунун бардыгынан Docker сүрөтүн кантип чогултуу керек.

Бул макаланын максаты - тажрыйбам менен бөлүшүү жана коомчулуктун тажрыйбалуу мүчөлөрүн конструктивдүү сынга чакыруу.

Чогултуу үчүн имиджин түзүү

Докердин акыркы сүрөттөлүшүн кичинекей кылуу үчүн эки эрежени кармануу керек: минималдуу катмарлар жана минималисттик базалык сүрөт. Эң кичинекей негизги сүрөттөрдүн бири - Alpine Linux сүрөтү, ошондуктан мен ошону тандайм. Кээ бирөөлөр Альп тоосун өндүрүү үчүн ылайыктуу эмес деп ырасташат жана алар туура болушу мүмкүн. Бирок жеке менин аны менен эч качан маселем болгон эмес жана ага каршы аргументтер да жок.

Азыраак катмар болушу үчүн, мен сүрөттү 2 этапта чогултам. Биринчиси - долбоор; бардык жардамчы программалар жана убактылуу файлдар анда калат. Ал эми акыркы этапта мен арыздын акыркы версиясын гана жазам.

Көмөкчү сүрөт менен баштайлы.

SPA тиркемесин түзүү үчүн сизге адатта node.js керек. Мен расмий сүрөттү алам, ал дагы npm жана жип пакетинин менеджерлери менен келет. Өзүмдүн атымдан мен кээ бир npm пакеттерин куруу үчүн зарыл болгон node-gyp жана Google'дан Brotli компрессорун кошом, ал кийинчерээк бизге пайдалуу болот.

комментарийлер менен Dockerfile.

# Базовый образ
FROM node:12-alpine
LABEL maintainer="Aleksey Maydokin <[email protected]>"
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 менен иштегенге көнүп калдым, ошондуктан азыр колдоном.

Nginxтин расмий Docker сүрөтү бар, бирок анын жөнөкөй статикалык бөлүштүрүү үчүн модулдары өтө көп. Кайсылары жеткирүүгө камтылганын атайын команда же расмий Докерфайлдан көрүүгө болот.

$ docker run --rm nginx:1-alpine nginx -V

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 <[email protected]>"
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;"]

Мен gzip жана brotli демейки боюнча иштетилгендей кылып, nginx.conf дароо оңдойм. Мен ошондой эле кэш аталыштарын кошом, анткени биз эч качан статикалык өзгөрбөйбүз. Жана акыркы тийүү бардык 404 суроо-талаптарды index.html сайтына багыттоо болот, бул SPAда навигация үчүн зарыл.

nginx.conf

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 Мб ээледи. Спортко болгон кызыгуум канааттанды.

Айлана-чөйрөнүн өзгөрмөлөрүн түшүнүүгө статиканы үйрөтүү

Эмне үчүн СПАда орнотуулар керек болушу мүмкүн? Мисалы, кайсы RESTful API колдонууну көрсөтүү үчүн. Эреже катары, каалаган чөйрө үчүн орнотуулар куруу стадиясында SPA өткөрүлүп берилет. Эгер бир нерсени өзгөртүү керек болсо, колдонмону кайра түзүшүңүз керек болот. Мен аны каалабайм. Мен тиркеменин CI стадиясында бир жолу курулуп, чөйрө өзгөрмөлөрүнүн жардамы менен CD стадиясында зарыл болушунча конфигурацияланышын каалайм.

Албетте, статикалык файлдар өздөрү эч кандай чөйрө өзгөрмөлөрүн түшүнүшпөйт. Ошондуктан, бир айла колдонууга туура келет. Акыркы сүрөттө мен nginxти ишке киргизбейм, бирок чөйрө өзгөрмөлөрүн окуп, аларды статикалык файлдарга жазып, кысып, андан кийин гана башкарууну nginxке өткөрө турган атайын скрипт.

Бул үчүн, Dockerfile ENTRYPOINT параметрин камсыз кылат. Ага төмөнкү сценарийди берели (мисалы катары Angular колдонуп):

docker-entrypoint.sh

#!/bin/sh
set -e
FLAG_FILE="/configured"
TARGET_DIR="/etc/nginx/html"
replace_vars () {
  ENV_VARS='$(awk 'BEGIN{for(v in ENVIRON) print "

quot;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


quot;); 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;"]

Эми алынган сүрөттү чогултуп, каалаган жерде колдонсо болот.

Source: www.habr.com