Gambar Docker pikeun distribusi Aplikasi Halaman Tunggal

Aplikasi halaman tunggal (SPA) mangrupikeun sakumpulan file JavaScript sareng HTML statik, ogé gambar sareng sumber sanésna. Kusabab aranjeunna henteu robih sacara dinamis, nyebarkeunana sacara online gampang pisan. Aya sajumlah ageung jasa anu murah sareng gratis pikeun ieu, dimimitian ku GitHub Pages anu saderhana (sareng pikeun sababaraha bahkan narod.ru) sareng ditungtungan ku CDN sapertos Amazon S3. Nanging, kuring peryogi anu sanés.

Abdi peryogi gambar Docker sareng SPA supados tiasa gampang diluncurkeun dina produksi salaku bagian tina klaster Kubernetes, sareng dina mesin pamekar tukang anu henteu terang naon SPA.

Kuring geus nangtukeun sarat gambar di handap pikeun kuring sorangan:

  • betah pamakéan (tapi teu assembly);
  • ukuran minimum boh tina segi disk sareng RAM;
  • konfigurasi ngaliwatan variabel lingkungan sangkan gambar bisa dipaké dina lingkungan béda;
  • distribusi file anu paling éfisién.

Dinten ieu kuring bakal ngabejaan ka maneh kumaha:

  • peujit nginx;
  • ngawangun brotli tina sumber;
  • ngajarkeun file statik ngartos variabel lingkungan;
  • sarta tangtu kumaha carana ngumpul gambar Docker tina sagala ieu.

Tujuan tulisan ieu pikeun ngabagi pangalaman kuring sareng ngadorong anggota komunitas anu ngalaman kana kritik anu konstruktif.

Ngawangun gambar pikeun assembly

Pikeun nyieun gambar Docker ahir ukuranana leutik, Anjeun kudu taat kana dua aturan: minimum lapisan jeung gambar dasar minimalistic. Salah sahiji gambar dasar pangleutikna nyaéta gambar Alpine Linux, janten éta anu kuring pilih. Sababaraha tiasa ngabantah yén Alpine henteu cocog pikeun produksi, sareng éta leres. Tapi sacara pribadi, kuring henteu kantos ngagaduhan masalah sareng anjeunna sareng teu aya dalil ngalawan anjeunna.

Pikeun gaduh lapisan pangsaeutikna, kuring bakal ngumpul gambar dina 2 tahap. Anu kahiji nyaéta draf; sadaya utilitas bantu sareng file samentawis bakal tetep aya. Sareng dina tahap ahir kuring ngan ukur nyerat versi ahir aplikasi.

Hayu urang mimitian ku gambar bantu.

Pikeun nyusun aplikasi SPA, biasana anjeun peryogi node.js. Kuring bakal nyandak gambar resmi, anu ogé hadir sareng manajer pakét npm sareng benang. Atas nama kuring sorangan, kuring bakal nambahan titik-gyp, anu diperlukeun pikeun ngawangun sababaraha bungkusan npm, sarta compressor Brotli ti Google, nu bakal mangpaat pikeun urang engké.

Dockerfile kalawan komentar.

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

Geus di dieu Kuring keur pajoang pikeun minimalism, jadi gambar ieu nunda babarengan ku hiji tim badag.

Gambar réngsé tiasa dipendakan di dieu: https://hub.docker.com/r/alexxxnf/spa-builder. Sanaos kuring nyarankeun henteu ngandelkeun gambar jalma sanés sareng ngumpulkeun gambar anjeun nyalira.

nginx

Anjeun tiasa nganggo server wéb naon waé pikeun nyebarkeun eusi statik. Abdi biasa damel sareng nginx, janten kuring bakal dianggo ayeuna.

Nginx ngagaduhan gambar Docker resmi, tapi seueur teuing modul pikeun distribusi statik saderhana. Anu mana anu kalebet dina pangiriman tiasa ditingali ku tim khusus atanapi dina Dockerfile resmi.

$ 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

Kuring baris ngagunakeun Dockerfile salaku dadasar, tapi kuring bakal ninggalkeun di dinya ngan naon anu diperlukeun pikeun ngadistribusikaeun eusi statik. Versi kuring moal tiasa dianggo dina HTTPS, moal ngadukung otorisasina, sareng seueur deui. Tapi versi abdi bakal tiasa ngadistribusikaeun file dikomprés ku algoritma Brotli, nu rada leuwih efisien ti gzip. Urang bakal niiskeun file sakali; teu perlu ngalakukeun ieu dina laleur.

Ieu mangrupikeun Dockerfile anu kuring réngsé. Koméntar dina basa Rusia milik kuring, dina basa Inggris - tina aslina.

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

Kuring bakal langsung ngalereskeun nginx.conf supados gzip sareng brotli diaktipkeun sacara standar. Kuring ogé bakal kaasup headers cache, sabab urang moal pernah ngarobah statik. Jeung touch final bakal alihan sadayana 404 requests ka index.html, ieu téh dipikabutuh pikeun navigasi di 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";
            }
        }
    }
}

Anjeun tiasa ngundeur gambar rengse di dieu: https://hub.docker.com/r/alexxxnf/nginx-spa. Butuh nepi ka 10,5 MB. Nginx aslina nyandak 19,7 MB. Minat olahraga kuring sugema.

Pangajaran statics ngartos variabel lingkungan

Naha setélan tiasa diperyogikeun dina SPA? Contona, pikeun nangtukeun mana API RESTful dipaké. Biasana, setélan pikeun lingkungan anu dipikahoyong dialihkeun ka SPA dina tahap ngawangun. Upami anjeun kedah ngarobih hiji hal, anjeun kedah ngawangun deui aplikasi. Abdi henteu hoyong. Abdi hoyong aplikasi anu diwangun sakali dina tahap CI, sarta ngonpigurasi saloba diperlukeun dina tahap CD ngagunakeun variabel lingkungan.

Tangtosna, file statik sorangan henteu ngartos variabel lingkungan. Ku alatan éta, anjeun kedah nganggo trik. Dina gambar ahir, kuring moal ngajalankeun nginx, tapi skrip cangkang khusus anu bakal maca variabel lingkungan, nyerat kana file statik, komprési, sareng ngan ukur mindahkeun kontrol ka nginx.

Pikeun tujuan ieu, Dockerfile nyayogikeun parameter ENTRYPOINT. Hayu urang masihan anjeunna skrip di handap ieu (nganggo Angular sabagé conto):

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

Supados naskah tiasa ngalaksanakeun tugasna, setélan kedah ditulis dina file js dina bentuk ieu: ${API_URL}.

Perlu dicatet yén kalolobaan SPA modéren nambihan hashes kana filena nalika ngawangun. Ieu diperlukeun ku kituna browser nu bisa aman cache file pikeun lila. Upami filena robih, teras hashna bakal robih, anu bakal maksa browser pikeun ngaunduh file deui.

Hanjakal, dina metoda abdi, ngarobah konfigurasi ngaliwatan variabel lingkungan teu ngakibatkeun parobahan dina file Hash, nu hartina cache browser kudu invalidated dina sababaraha cara séjén. Kuring teu boga masalah ieu kusabab konfigurasi béda anu deployed di lingkungan béda.

Nyusun gambar ahir

Tungtungna.

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

Ayeuna gambar anu dihasilkeun tiasa dirakit sareng dianggo dimana waé.

sumber: www.habr.com