Ühe lehe rakendus (SPA) – see on komplekt staatilistest JavaScripti ja HTML-failidest, samuti piltidest ja muudest ressurssidest. Kuna need ei muutu dünaamiliselt, on nende avaldamine internetis väga lihtne. Selleks on olemas palju odavaid ja isegi tasuta teenuseid, alustades lihtsast GitHub Pages'ist (ja mõne jaoks isegi narod.ru) ning lõpetades CDN-idega nagu Amazon S3. Kuid mulle oli vajalik midagi muud.
Mul oli vaja Docker-pilti SPA-ga, et seda saaks hõlpsasti käivitada nii tootmises Kubernetes-klastris kui ka back-end arendaja masinas, kes ei tea, mis asi on SPA.
Olen enda jaoks seadnud järgmised nõuded pildile:
- kasutamise lihtsus (aga mitte kokku panemise);
- minimaalne suurus nii ketas kui RAM-i osas;
- seadistamine keskkonnamuutujate kaudu, et pilti saaks kasutada erinevates keskkondades;
- kõige tõhusam failide jagamine.
Täna räägin, kuidas:
- nginx tühjendada;
- brotli lähtekoodist kokku panna;
- õpetada staatilisi faile aru saama keskkonnamuutujatest;
- ja muidugi, kuidas koguda sellest kõik Docker-pilt.
Selle artikli eesmärk on jagada oma kogemust ja julgustada kogukonna kogenud liikmeid konstruktiivsele kriitikale.
Pildi koostamine
Kuna lõpptoote Docker-pilt peab olema väikese suurusega, tuleb järgida kahte reeglit: minimaalne kihtide arv ja minimalistlik baaspilt. Üks väiksemaid baaspilte on Alpine Linux, seega valin selle. Keegi võib vaielda, et Alpine ei sobi tootmisse ja võib-olla on tal õigus. Kuid isiklikult pole mul selle kasutamisega kunagi probleeme olnud ja mul pole selle vastu argumente.
Et kihtide arv oleks võimalikult väike, koostan pildi 2 etapis. Esimene etapp on mustand, milles jäävad kõik abivahendid ja ajutised failid. Teises etapis salvestan vaid lõpliku rakenduse versiooni.
Alustame abipildist.
Et kompileerida SPA-rakendust, on tavaliselt vajalik node.js. Viskan ametlikku pilti, millega kaasnevad ka pakihaldurid npm ja yarn. Lisaks lisan node-gyp'i, mis on vajalik mõnede npm-pakettide koostamiseks, ja Google'i kompressor Brotli, mis tuleb meie jaoks hiljem kasuks.
Dockerfile kommentaaridega.
# Базовый образ
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/srcSiin ma võitlen minimalismi nimel, seega koosneb pilt ühest suurest meeskonnast.
Valminud pilti saab leida siit: . Kuigi ma soovitan mitte toetuda teiste piltidele ja koostada oma.
nginx
Statistika jagamiseks võib kasutada mis tahes veebiserverit. Ma olen harjunud töötama nginxiga, seega kasutan seda ka praegu.
Nginxil on ametlik Docker-pilt, kuid lihtsaks statistika jagamiseks on seal liiga palju mooduleid. Täpselt, millised on kaasas, saab vaadata spetsiaalse käsu abil või ametlikus Dockerfile'is.
$ docker run —rm nginx:1-alpine nginx -V
nginx versioon: nginx/1.17.9
kompileeritud gcc 8.3.0 (Alpine 8.3.0)
kompileeritud OpenSSL 1.1.1d abil 10. september 2019
TLS SNI tugi on lubatud
konfigureerimisargumendid: --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-neededVõtan aluseks Dockerfile'i, kuid jätan sinna alles vaid selle, mis on vajalik staatika jagamiseks. Minu versioon ei suuda töötada HTTPS-is, ei toeta autoriseerimist ega palju muud. Siiski suudab minu versioon jagada faile, mis on kompressitud Brotli algoritmiga, mis on veidi tõhusam kui gzip. Kompressime faile vaid ühe korra, pole vajadust seda reaalajas teha.
Siin on selline Dockerfile mul välja tulnud. Kommenteerin vene keeles - need on minu kommentaarid, inglise keeles - originaalist.
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;"]Parandan kohe nginx.conf faili, et gzip ja brotli oleksid vaikimisi lubatud. Lisan ka vahemäluseaded, sest meil jagatakse kunagi muutumatut staatikat. Viimase nüansina suunan kõik 404 päringud index.html-ile, see on vajalik SPA navigeerimiseks.
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";
}
}
}
}Valmis kujutise alla saab laadida siit: . See on 10,5 MB. Originaalne nginx oli 19,7 MB. Minu spordihimu on rahuldatud.
Õpime staatikat mõistma keskkonnamuutujaid
Millised seaded võivad SPA-s vajalikud olla? Näiteks, et näidata, millist RESTful API-d kasutada. Tavaliselt edastatakse vajalikke seadeid SPA-sse kogumise etapis. Kui on vaja midagi muuta, tuleb rakendus uuesti üles ehitada. Ma ei soovi seda. Soovin, et rakendus ehitataks ühe korra CI etapis ja konfigureeritaks nii palju, kui on vajalik, CD etapis, kasutades keskkonnamuutujaid.
Muidugi ei mõista staatilised failid iseenesest mingeid keskkonnamuutujaid. Seetõttu tuleb petta. Lõpppildis käitan ma mitte nginx-i, vaid spetsiaalset shell-skripti, mis loeb keskkonnamuutujad, kirjutab need staatilistesse failidesse, tihendab need ja alles siis edastab juhtimise nginx-ile.
Selle jaoks on Dockerfile'is ette nähtud parameeter ENTRYPOINT. Edastame sellele järgmise skripti (Angulari näitel):
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"
done
}
compress () {
for i in $(find "$TARGET_DIR" | grep -E ".css$|.html$|.js$|.svg$|.txt$|.ttfquot;); 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 "$@"
Kuna skript peab oma tööd tegema, siis peavad js-failides seaded olema kirjutatud sellisel kujul:${API_URL}.Tuleb märkida, et enamik tänapäevaseid SPA-sid lisavad oma failidele kogumise käigus hash-koodid. See on vajalik, et brauser saaks faili pikaks ajaks usaldusväärselt vahemällu salvestada. Kui fail muutub, muutub ka selle hash, mis omakorda sunnib brauserit faili uuesti alla laadima.
Kahjuks minu meetodis ei too keskkonnamuutujate kaudu konfiguratsiooni muutmine kaasa faili hash'i muutumist, mis tähendab, et brauseri vahemälu tuleb muul viisil tühjendada. Mul pole seda probleemi, sest erinevad konfiguratsioonid käivitatakse erinevates keskkondades.
Kogume lõppfaili
Lõpuks.
Dockerfile
# Первый базовый образ для сборки 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üüd saadud pilti saab koguda ja kasutada igal pool.
Allikas: habr.com
