Docker imaj pou distribisyon Aplikasyon Single Page

Aplikasyon yon sèl paj (SPA) se yon seri JavaScript estatik ak HTML fichye, osi byen ke imaj ak lòt resous. Paske yo pa chanje dinamik, pibliye yo sou entènèt trè fasil. Gen yon gwo kantite sèvis bon mache e menm gratis pou sa a, kòmanse ak yon paj GitHub senp (ak pou kèk menm ak narod.ru) epi fini ak yon CDN tankou Amazon S3. Sepandan, mwen te bezwen yon lòt bagay.

Mwen te bezwen yon imaj Docker ak SPA pou li te kapab fasilman lanse tou de nan pwodiksyon kòm yon pati nan yon gwoup Kubernetes, ak sou machin nan nan yon devlopè back-end ki pa gen okenn lide ki sa SPA ye.

Mwen detèmine kondisyon imaj sa yo pou tèt mwen:

  • fasilite pou itilize (men pa asanble);
  • gwosè minimòm tou de an tèm de disk ak RAM;
  • konfigirasyon atravè varyab anviwònman pou imaj la ka itilize nan diferan anviwònman;
  • distribisyon ki pi efikas nan dosye.

Jodi a mwen pral di ou ki jan:

  • zantray nginx;
  • bati brotli soti nan sous;
  • anseye fichye estatik yo konprann varyab anviwònman yo;
  • ak nan kou ki jan yo rasanble yon imaj Docker soti nan tout bagay sa a.

Objektif atik sa a se pataje eksperyans mwen ak pwovoke manm kominote ki gen eksperyans nan kritik konstriktif.

Bati yon imaj pou asanble

Pou fè imaj Docker final la piti nan gwosè, ou bezwen konfòme yo ak de règ: yon minimòm de kouch ak yon imaj de baz minimalist. Youn nan imaj ki pi piti yo se imaj Alpine Linux, kidonk se sa mwen pral chwazi. Gen kèk ki ka diskite ke Alpine a pa apwopriye pou pwodiksyon, epi yo ta ka gen rezon. Men, pèsonèlman, mwen pa janm gen okenn pwoblèm ak li epi pa gen okenn agiman kont li.

Pou gen mwens kouch, mwen pral rasanble imaj la nan 2 etap. Premye a se yon bouyon; tout sèvis piblik oksilyè ak dosye tanporè ap rete ladan l. Ak nan etap final la mwen pral sèlman ekri vèsyon final aplikasyon an.

Ann kòmanse ak imaj oksilyè a.

Yo nan lòd yo konpile yon aplikasyon SPA, anjeneral ou bezwen node.js. Mwen pral pran imaj ofisyèl la, ki tou vini ak manadjè yo npm ak fil pake. Pou pwòp non mwen, mwen pral ajoute node-gyp, ki nesesè pou konstwi kèk pakè npm, ak COMPRESSOR Brotli ki soti nan Google, ki pral itil nou pita.

Dockerfile ak kòmantè.

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

Deja isit la mwen ap goumen pou minimalist, se konsa imaj la mete ansanm pa yon sèl gwo ekip.

Ou ka jwenn imaj la fini isit la: https://hub.docker.com/r/alexxxnf/spa-builder. Malgre ke mwen rekòmande pa konte sou imaj lòt moun ak kolekte pwòp ou yo.

nginx

Ou ka itilize nenpòt sèvè entènèt pou distribye kontni estatik. Mwen abitye travay ak nginx, kidonk mwen pral sèvi ak li kounye a.

Nginx gen yon imaj Docker ofisyèl, men li gen twòp modil pou senp distribisyon estatik. Kilès ki enkli nan livrezon an ka wè pa yon ekip espesyal oswa nan Dockerfile ofisyèl la.

$ docker kouri --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

Mwen pral sèvi ak Dockerfile a kòm yon baz, men mwen pral kite nan li sèlman sa ki nesesè yo distribye kontni estatik. Vèsyon mwen an pa pral kapab travay sou HTTPS, pa pral sipòte otorizasyon, ak plis ankò. Men, vèsyon mwen an pral kapab distribye fichye konprese ak algorithm Brotli, ki se yon ti kras pi efikas pase gzip. Nou pral konprese dosye yon fwa; pa gen okenn bezwen fè sa sou vole.

Sa a se Dockerfile mwen te fini ak. Kòmantè nan Ris yo se mwen yo ye, nan lang angle - soti nan orijinal la.

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

Mwen pral imedyatman ranje nginx.conf pou ke gzip ak brotli yo aktive pa default. Mwen pral gen ladan tou headers kachèt, paske nou p'ap janm chanje estatik. Ak manyen final la pral redireksyon tout demann 404 nan index.html, sa a nesesè pou navigasyon nan SPA la.

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";
            }
        }
    }
}

Ou ka telechaje imaj la fini isit la: https://hub.docker.com/r/alexxxnf/nginx-spa. Li pran 10,5 MB. Nginx orijinal la te pran 19,7 MB. Enterè spòtif mwen satisfè.

Ansèyman estatik pou konprann varyab anviwònman an

Poukisa ta ka bezwen paramèt nan SPA? Pou egzanp, yo nan lòd yo presize ki RESTful API yo itilize. Tipikman, paramèt pou anviwònman an vle yo transfere nan SPA nan etap nan bati. Si w bezwen chanje yon bagay, w ap oblije rebati aplikasyon an. Mwen pa vle li. Mwen vle aplikasyon an dwe bati yon fwa nan etap la CI, ak configuré otan sa nesesè nan etap la CD lè l sèvi avèk varyab anviwònman an.

Natirèlman, dosye estatik tèt yo pa konprann okenn varyab anviwònman an. Se poutèt sa, ou pral gen yo sèvi ak yon Trick. Nan imaj final la, mwen pa pral lanse nginx, men yon script koki espesyal ki pral li varyab anviwònman an, ekri yo nan fichye estatik, konprese yo, epi sèlman Lè sa a, transfere kontwòl nan nginx.

Pou rezon sa a, Dockerfile bay paramèt ENTRYPOINT la. Ann ba li script sa a (sèvi ak Angilè kòm yon egzanp):

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

Pou script la fè travay li, paramèt yo dwe ekri nan dosye js yo nan fòm sa a: ${API_URL}.

Li se vo anyen ki pi modèn SPA ajoute hash nan dosye yo lè yo bati. Sa a se nesesè pou navigatè a ka san danje kachèt fichye a pou yon tan long. Si fichye a chanje, Lè sa a, hash li yo pral chanje, ki an vire ap fòse navigatè a telechaje fichye a ankò.

Malerezman, nan metòd mwen an, chanje konfigirasyon an atravè varyab anviwònman an pa mennen nan yon chanjman nan hash dosye a, ki vle di ke kachèt navigatè a dwe invalid nan yon lòt fason. Mwen pa gen pwoblèm sa a paske diferan konfigirasyon yo deplwaye nan diferan anviwònman.

Mete ansanm imaj final la

Finalman.

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

Koulye a, imaj ki kapab lakòz yo ka reyini epi itilize nenpòt kote.

Sous: www.habr.com