Docker mynd til dreifingar á Single Page Application

Single-page Application (SPA) er sett af kyrrstæðum JavaScript og HTML skrám, auk mynda og annarra auðlinda. Vegna þess að þær breytast ekki á kraftmikinn hátt er mjög auðvelt að birta þær á netinu. Það er mikill fjöldi ódýrra og jafnvel ókeypis þjónustu fyrir þetta, sem byrjar með einföldum GitHub síðum (og fyrir suma jafnvel með narod.ru) og endar með CDN eins og Amazon S3. Hins vegar þurfti ég eitthvað annað.

Mig vantaði Docker mynd með SPA þannig að auðvelt væri að setja hana af stað bæði í framleiðslu sem hluta af Kubernetes klasa og á vél bakhliðar þróunaraðila sem hefur ekki hugmynd um hvað SPA er.

Ég hef ákveðið eftirfarandi myndkröfur fyrir sjálfan mig:

  • auðveld notkun (en ekki samsetning);
  • lágmarksstærð bæði hvað varðar disk og vinnsluminni;
  • stillingar í gegnum umhverfisbreytur þannig að hægt sé að nota myndina í mismunandi umhverfi;
  • skilvirkasta dreifing skráa.

Í dag mun ég segja þér hvernig:

  • þörmum nginx;
  • byggja brotli úr heimildum;
  • kenna truflanir skrár til að skilja umhverfisbreytur;
  • og auðvitað hvernig á að setja saman Docker mynd úr þessu öllu.

Tilgangur þessarar greinar er að deila reynslu minni og vekja reynda samfélagsmenn til uppbyggjandi gagnrýni.

Byggja mynd fyrir samsetningu

Til að gera endanlega Docker myndina litla að stærð þarftu að fylgja tveimur reglum: lágmarks laga og naumhyggju grunnmynd. Ein minnsta grunnmyndin er Alpine Linux myndin, svo það er það sem ég mun velja. Sumir gætu haldið því fram að Alpine henti ekki til framleiðslu og þeir gætu haft rétt fyrir sér. En persónulega hef ég aldrei átt í neinum vandræðum með hann og það eru engin rök á móti honum.

Til að hafa færri lög mun ég setja myndina saman í 2 áföngum. Fyrsta er uppkast; öll hjálpartæki og tímabundnar skrár verða áfram í því. Og á lokastigi mun ég aðeins skrifa niður lokaútgáfu forritsins.

Byrjum á aukamyndinni.

Til þess að setja saman SPA forrit þarftu venjulega node.js. Ég tek opinberu myndina, sem einnig fylgir npm og garnpakkastjórnendum. Fyrir mína hönd mun ég bæta við node-gyp, sem þarf til að búa til einhverja npm pakka, og Brotli compressor frá Google, sem mun nýtast okkur síðar.

Dockerfile með athugasemdum.

# Базовый образ
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

Þegar hér er ég að berjast fyrir naumhyggju, þannig að myndin er sett saman af einu stóru liði.

Fullbúna myndina má finna hér: https://hub.docker.com/r/alexxxnf/spa-builder. Þó ég mæli með því að treysta ekki á myndir annarra og safna þínum eigin.

nginx

Þú getur notað hvaða vefþjón sem er til að dreifa kyrrstæðu efni. Ég er vanur að vinna með nginx, svo ég mun nota það núna.

Nginx er með opinbera Docker mynd, en hún hefur of margar einingar fyrir einfalda truflanir. Hverjir eru innifaldir í afhendingu getur sérstakt teymi séð eða í opinberu Dockerfile.

$ 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

Ég mun nota Dockerfile sem grunn, en ég mun aðeins skilja eftir það sem þarf til að dreifa kyrrstæðu efni. Útgáfan mín mun ekki geta virkað yfir HTTPS, mun ekki styðja heimildir og margt fleira. En útgáfan mín mun geta dreift skrám þjöppuðum með Brotli reikniritinu, sem er aðeins skilvirkara en gzip. Við munum þjappa skrám einu sinni; það er engin þörf á að gera þetta á flugu.

Þetta er Dockerfile sem ég endaði með. Athugasemdir á rússnesku eru mínar, á ensku - frá frumritinu.

Dockerfil

# Базовый образ снова 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;"]

Ég mun strax laga nginx.conf þannig að gzip og brotli séu sjálfgefið virkt. Ég mun einnig innihalda skyndiminnishausa, vegna þess að við munum hafa aldrei breytast truflanir. Og lokahnykkurinn verður að beina öllum 404 beiðnum yfir á index.html, þetta er nauðsynlegt fyrir siglingar í 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";
            }
        }
    }
}

Hægt er að hlaða niður fullbúinni mynd hér: https://hub.docker.com/r/alexxxnf/nginx-spa. Það tekur 10,5 MB. Upprunalega nginx tók upp 19,7 MB. Íþróttaáhugi minn er ánægður.

Að kenna truflanir til að skilja umhverfisbreytur

Af hverju gæti verið þörf á stillingum í SPA? Til dæmis, til að tilgreina hvaða RESTful API á að nota. Venjulega eru stillingar fyrir viðkomandi umhverfi fluttar yfir í SPA á byggingarstigi. Ef þú þarft að breyta einhverju þarftu að endurbyggja forritið. Ég vil það ekki. Ég vil að forritið sé byggt einu sinni á CI stigi og stillt eins mikið og nauðsynlegt er á CD stigi með því að nota umhverfisbreytur.

Auðvitað skilja statískar skrár sjálfar engar umhverfisbreytur. Þess vegna verður þú að nota bragð. Í lokamyndinni mun ég ekki ræsa nginx, heldur sérstakt skeljaforskrift sem mun lesa umhverfisbreytur, skrifa þær í kyrrstæðar skrár, þjappa þeim og aðeins þá flytja stjórn til nginx.

Í þessu skyni veitir Dockerfile ENTRYPOINT færibreytuna. Við skulum gefa honum eftirfarandi handrit (með því að nota Angular sem dæmi):

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 "$@"

Til þess að handritið geti gert starf sitt verða stillingarnar að vera skrifaðar í js skrárnar á þessu formi: ${API_URL}.

Það er athyglisvert að flestar nútíma heilsulindir bæta kjötkássa við skrárnar sínar þegar þeir byggja. Þetta er nauðsynlegt svo að vafrinn geti örugglega vistað skrána í langan tíma. Ef skráin breytist, þá mun kjötkássa hennar breytast, sem aftur mun neyða vafrann til að hlaða niður skránni aftur.

Því miður, samkvæmt minni aðferð, leiðir það að breyta stillingum í gegnum umhverfisbreytur ekki til breytinga á skráarhassinu, sem þýðir að skyndiminni vafrans verður að ógilda á annan hátt. Ég á ekki við þetta vandamál að stríða vegna þess að mismunandi stillingar eru notaðar í mismunandi umhverfi.

Að setja saman lokamyndina

Loksins.

Dockerfil

# Первый базовый образ для сборки
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;"]

Nú er hægt að setja myndina sem myndast saman og nota hvar sem er.

Heimild: www.habr.com