Тасвири Docker барои паҳн кардани замимаи ягона саҳифа

Барномаи яксаҳифа (SPA) маҷмӯи файлҳои статикии JavaScript ва HTML, инчунин тасвирҳо ва дигар захираҳо мебошад. Азбаски онҳо ба таври динамикӣ тағир намеёбанд, нашри онҳо дар интернет хеле осон аст. Барои ин шумораи зиёди хидматҳои арзон ва ҳатто ройгон мавҷуданд, ки аз Саҳифаҳои оддии GitHub (ва барои баъзеҳо ҳатто бо narod.ru) сар карда, бо CDN ба монанди Amazon S3 тамом мешаванд. Бо вуҷуди ин, ба ман чизи дигаре лозим буд.

Ба ман як тасвири Docker бо SPA лозим буд, то он ҳам дар истеҳсолот ҳамчун як ҷузъи кластери Kubernetes ва ҳам дар мошини як таҳиягари пушти сар, ки SPA чист, намедонад, ба осонӣ ба кор андохта шавад.

Ман талаботҳои зерини тасвирро барои худ муайян кардам:

  • осонии истифода (вале на васлкунӣ);
  • андозаи ҳадди ақал ҳам аз ҷиҳати диск ва ҳам аз ҷиҳати RAM;
  • конфигуратсия тавассути тағирёбандаҳои муҳити зист, то тасвир дар муҳитҳои гуногун истифода шавад;
  • паҳнкунии самараноки файлҳо.

Имрӯз ман ба шумо мегӯям, ки чӣ тавр:

  • nginx рӯда;
  • бротли аз манбаъҳо сохтан;
  • ба файлҳои статикӣ барои фаҳмидани тағирёбандаҳои муҳити зист таълим диҳед;
  • ва албатта аз ин ҳама тасвири Docker-ро чӣ гуна ҷамъ кардан мумкин аст.

Мақсади ин мақола мубодилаи таҷрибаи ман ва барангехтани аъзоёни ботаҷрибаи ҷомеа ба интиқоди созанда аст.

Сохтани тасвир барои васлкунӣ

Барои хурд кардани тасвири ниҳоии Docker, шумо бояд ду қоидаро риоя кунед: ҳадди аққал қабатҳо ва тасвири асосии минималистӣ. Яке аз хурдтарин тасвирҳои асосӣ ин тасвири Alpine Linux мебошад, бинобар ин ман он чизеро интихоб мекунам. Баъзеҳо метавонанд баҳс кунанд, ки кӯҳи Алпӣ барои истеҳсол мувофиқ нест ва онҳо метавонанд дуруст бошанд. Аммо шахсан ман бо ӯ ҳеҷ мушкиле надоштам ва ҳеҷ далеле алайҳи ӯ нест.

Барои кам кардани қабатҳо, ман тасвирро дар 2 марҳила ҷамъ мекунам. Аввал ин лоиҳа аст; ҳама хидматҳои ёрирасон ва файлҳои муваққатӣ дар он боқӣ хоҳанд монд. Ва дар марҳилаи ниҳоӣ ман танҳо версияи ниҳоии барномаро менависам.

Биёед бо тасвири ёрирасон оғоз кунем.

Барои тартиб додани барномаи SPA ба шумо одатан node.js лозим аст. Ман тасвири расмиро мегирам, ки он инчунин бо менеҷерони бастаҳои npm ва ришта меояд. Ман аз номи худам node-gyp, ки барои сохтани баъзе бастаҳои npm лозим аст ва компрессори Brotli аз Google, ки баъдтар барои мо муфид хоҳад буд, илова мекунам.

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 одат кардаам, аз ин рӯ ҳоло онро истифода хоҳам кард.

Nginx дорои тасвири расмии Docker мебошад, аммо он барои тақсимоти оддии статикӣ модулҳои аз ҳад зиёд дорад. Кадоме аз онҳо ба таҳвил дохил карда шудаанд, метавонанд аз ҷониби як гурӯҳи махсус ё дар 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

Ман 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;"]

Ман фавран nginx.conf -ро ислоҳ мекунам, то gzip ва brotli бо нобаёнӣ фаъол бошанд. Ман инчунин сарлавҳаҳои кэшро дохил мекунам, зеро мо ҳеҷ гоҳ статикиро тағир намедиҳем. Ва тамоси ниҳоӣ ин равона кардани ҳама 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 МБ гирифт. Шавқу рағбати варзишии ман қонеъ аст.

Омӯзиши статика барои фаҳмидани тағирёбандаҳои муҳити зист

Чаро танзимот дар SPA лозим аст? Масалан, барои муайян кардани кадом API RESTful истифода мешавад. Одатан, танзимоти муҳити дилхоҳ дар марҳилаи сохтмон ба SPA интиқол дода мешаванд. Агар ба шумо чизе тағир додан лозим бошад, шумо бояд барномаро аз нав созед. Ман инро намехоҳам. Ман мехоҳам, ки барнома як маротиба дар марҳилаи CI сохта шавад ва ба қадри зарурӣ дар марҳилаи CD бо истифода аз тағирёбандаҳои муҳити зист танзим карда шавад.

Албатта, худи файлҳои статикӣ ягон тағирёбандаи муҳити зистро намефаҳманд. Аз ин рӯ, шумо бояд як ҳилларо истифода баред. Дар тасвири ниҳоӣ, ман nginx-ро оғоз намекунам, балки як скрипти махсуси ҷабҳае, ки тағирёбандаҳои муҳити зистро мехонад, онҳоро ба файлҳои статикӣ менависад, фишурда мекунад ва танҳо пас аз он назоратро ба nginx интиқол медиҳад.

Бо ин мақсад, файли Docker параметри 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;"]

Акнун тасвири ҳосилшуда метавонад дар ҳама ҷо ҷамъ карда шавад.

Манбаъ: will.com