Imahe sa Docker alang sa pag-apod-apod sa Single Page Application

Ang Single-page Application (SPA) usa ka set sa static nga JavaScript ug HTML nga mga file, ingon man mga imahe ug uban pang mga kapanguhaan. Tungod kay wala sila magbag-o nga dinamiko, ang pagmantala niini online dali ra kaayo. Adunay daghang mga barato ug bisan libre nga mga serbisyo alang niini, sugod sa usa ka yano nga GitHub Pages (ug alang sa uban bisan sa narod.ru) ug natapos sa usa ka CDN sama sa Amazon S3. Apan, lain pa ang akong gikinahanglan.

Kinahanglan nako ang usa ka imahe sa Docker nga adunay SPA aron kini dali nga ilunsad sa produksiyon isip bahin sa usa ka Kubernetes cluster, ug sa makina sa usa ka back-end developer nga walay ideya kung unsa ang SPA.

Gitino nako ang mosunod nga mga kinahanglanon sa imahe para sa akong kaugalingon:

  • kasayon ​​sa paggamit (apan dili asembliya);
  • minimum nga gidak-on pareho sa disk ug RAM;
  • pag-configure pinaagi sa mga variable sa palibot aron ang imahe magamit sa lainlaing mga palibot;
  • ang labing episyente nga pag-apod-apod sa mga file.

Karon isulti ko kanimo kung giunsa:

  • gut nginx;
  • pagtukod brotli gikan sa mga tinubdan;
  • pagtudlo sa mga static nga mga file aron masabtan ang mga variable sa palibot;
  • ug siyempre kung unsaon pag-assemble ang usa ka imahe sa Docker gikan niining tanan.

Ang katuyoan niini nga artikulo mao ang pagpaambit sa akong kasinatian ug paghagit sa eksperyensiyadong mga miyembro sa komunidad sa makaayo nga pagsaway.

Pagtukod og usa ka imahen alang sa asembliya

Aron mahimo ang katapusang imahe sa Docker nga gamay sa gidak-on, kinahanglan nimo nga sundon ang duha nga mga lagda: usa ka minimum nga mga layer ug usa ka minimalistic nga base nga imahe. Usa sa pinakagamay nga base nga mga imahe mao ang Alpine Linux nga imahe, mao nga kana ang akong pilion. Ang uban tingali mangatarungan nga ang Alpine dili angay alang sa produksiyon, ug tingali husto sila. Pero sa personal, wala gyud koy problema niya ug walay mga argumento batok niya.

Aron adunay gamay nga mga layer, akong tipunon ang imahe sa 2 nga mga yugto. Ang una usa ka draft; ang tanan nga auxiliary utilities ug temporaryo nga mga file magpabilin niini. Ug sa katapusang yugto isulat ra nako ang katapusang bersyon sa aplikasyon.

Magsugod kita sa auxiliary nga imahe.

Aron ma-compile ang usa ka aplikasyon sa SPA, kasagaran kinahanglan nimo ang node.js. Kuhaon nako ang opisyal nga imahe, nga kauban usab ang npm ug yarn package managers. Sa akong kaugalingon nga ngalan, akong idugang ang node-gyp, nga gikinahanglan sa pagtukod sa pipila ka npm packages, ug ang Brotli compressor gikan sa Google, nga mahimong mapuslanon kanato sa ulahi.

Dockerfile nga adunay mga komento.

# Π‘Π°Π·ΠΎΠ²Ρ‹ΠΉ ΠΎΠ±Ρ€Π°Π·
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

Ania na ako nakig-away alang sa minimalism, mao nga ang imahe gihiusa sa usa ka dako nga team.

Ang nahuman nga imahe makita dinhi: https://hub.docker.com/r/alexxxnf/spa-builder. Bisan kung girekomenda ko nga dili magsalig sa mga imahe sa ubang mga tawo ug pagkolekta sa imong kaugalingon.

nginx

Mahimo nimong gamiton ang bisan unsang web server aron maapod-apod ang static nga sulud. Naanad ko sa pagtrabaho sa nginx, mao nga gamiton ko kini karon.

Ang Nginx adunay opisyal nga imahe sa Docker, apan kini adunay daghang mga module alang sa yano nga static nga pag-apod-apod. Kinsa ang gilakip sa paghatud makita sa usa ka espesyal nga team o sa opisyal nga 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

Akong gamiton ang Dockerfile isip basehan, apan ibilin ko lang niini ang gikinahanglan aron maapod-apod ang static nga sulod. Ang akong bersyon dili makahimo sa pagtrabaho sa HTTPS, dili mosuporta sa pagtugot, ug daghan pa. Apan ang akong bersyon makahimo sa pag-apod-apod sa mga file nga gi-compress sa Brotli algorithm, nga mas episyente kay sa gzip. Among i-compress ang mga file sa makausa; dili na kinahanglan nga buhaton kini sa langaw.

Kini ang Dockerfile nga akong natapos. Ang mga komento sa Russian akoa, sa English - gikan sa orihinal.

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

Ayuhon dayon nako ang nginx.conf aron ang gzip ug brotli ma-enable sa default. Ilakip usab nako ang mga ulohan sa pag-cache, tungod kay dili gyud kami magbag-o nga static. Ug ang katapusan nga paghikap mao ang pag-redirect sa tanan nga 404 nga mga hangyo sa index.html, kini kinahanglan alang sa nabigasyon sa 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";
            }
        }
    }
}

Mahimo nimong i-download ang nahuman nga imahe dinhi: https://hub.docker.com/r/alexxxnf/nginx-spa. Nagkinahanglan kini og 10,5 MB. Ang orihinal nga nginx mikuha og 19,7 MB. Natagbaw ang akong interes sa sport.

Pagtudlo sa statics aron masabtan ang mga variable sa palibot

Ngano kaha nga gikinahanglan ang mga setting sa SPA? Pananglitan, aron matino kung unsang RESTful API ang gamiton. Kasagaran, ang mga setting alang sa gusto nga palibot gibalhin sa SPA sa yugto sa pagtukod. Kung kinahanglan nimo nga usbon ang usa ka butang, kinahanglan nimo nga tukuron pag-usab ang aplikasyon. Dili ko gusto. Gusto nako nga ang aplikasyon matukod kausa sa yugto sa CI, ug i-configure kutob sa kinahanglan sa yugto sa CD gamit ang mga variable sa palibot.

Siyempre, ang mga static nga file mismo wala makasabut sa bisan unsang mga variable sa palibot. Busa, kamo kinahanglan nga mogamit sa usa ka lansis. Sa katapusan nga imahe, dili ako maglunsad og nginx, apan usa ka espesyal nga script sa shell nga magbasa sa mga variable sa palibot, isulat kini sa mga static nga mga file, i-compress kini, ug dayon ibalhin ang kontrol sa nginx.

Alang niini nga katuyoan, ang Dockerfile naghatag sa parameter nga ENTRYPOINT. Hatagan nato siya sa mosunod nga script (gamit ang Angular isip pananglitan):

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

Aron mahimo sa script ang trabaho niini, ang mga setting kinahanglan isulat sa mga file sa js sa kini nga porma: ${API_URL}.

Angay nga hinumdoman nga kadaghanan sa mga modernong SPA nagdugang mga hash sa ilang mga file kung magtukod. Gikinahanglan kini aron ang browser luwas nga maka-cache sa file sa dugay nga panahon. Kung ang file mausab, nan ang hash niini mausab, nga sa baylo mopugos sa browser sa pag-download sa file pag-usab.

Ikasubo, sa akong pamaagi, ang pagbag-o sa pagsumpo pinaagi sa mga variable sa palibot wala magdala sa pagbag-o sa file hash, nga nagpasabut nga ang cache sa browser kinahanglan nga dili balido sa ubang paagi. Wala ako niini nga problema tungod kay lainlain nga mga pag-configure ang gi-deploy sa lainlaing mga palibot.

Paghiusa sa katapusan nga imahe

Sa kataposan.

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

Karon ang resulta nga hulagway mahimong matigom ug magamit bisan asa.

Source: www.habr.com