Docker irudia Orrialde Bakarreko Aplikazioa banatzeko

Orrialde bakarreko aplikazioa (SPA) JavaScript eta HTML fitxategi estatikoen multzoa da, baita irudiak eta bestelako baliabideak ere. Dinamikoki aldatzen ez direnez, sarean argitaratzea oso erraza da. Zerbitzu merke eta doako ugari daude horretarako, GitHub Pages soil batekin hasi (eta batzuentzat baita narod.ru-rekin ere) eta Amazon S3 bezalako CDN batekin amaituz. Hala ere, beste zerbait behar nuen.

Docker irudi bat behar nuen SPArekin, erraz abiarazi ahal izateko bai ekoizpenean Kubernetes kluster baten barruan, bai SPA zer den ideiarik ez duen back-end garatzaile baten makinan.

Irudi-baldintza hauek zehaztu ditut niretzat:

  • erabiltzeko erraztasuna (baina ez muntaia);
  • gutxieneko tamaina bai diskoari bai RAMari dagokionez;
  • ingurune-aldagaien bidez konfiguratzea, irudia ingurune ezberdinetan erabili ahal izateko;
  • fitxategien banaketa eraginkorrena.

Gaur nola esango dizut:

  • tripa nginx;
  • sortu brotli iturrietatik;
  • Fitxategi estatikoak ingurune-aldagaiak ulertzen irakatsi;
  • eta noski nola muntatu Docker irudi bat guzti honetatik.

Artikulu honen helburua nire esperientzia partekatzea eta esperientziadun komunitateko kideak kritika eraikitzaileetara eragitea da.

Muntatzeko irudi bat eraikitzea

Docker-eko azken irudia tamaina txikian izan dadin, bi arau bete behar dituzu: gutxieneko geruza bat eta oinarrizko irudi minimalista bat. Oinarrizko irudi txikienetako bat Alpine Linux irudia da, beraz, hori da aukeratuko dudana. Batzuek esan dezakete Alpine ez dela ekoizteko egokia, eta arrazoia izan dezakete. Baina pertsonalki, ez dut inoiz arazorik izan berarekin eta ez dago bere aurkako argudiorik.

Geruza gutxiago izateko, irudia 2 etapatan muntatuko dut. Lehenengoa zirriborro bat da; erabilgarritasun laguntzaile guztiak eta aldi baterako fitxategiak bertan geratuko dira. Eta azken fasean aplikazioaren azken bertsioa bakarrik idatziko dut.

Has gaitezen irudi laguntzailearekin.

SPA aplikazio bat konpilatzeko, normalean node.js behar duzu. Irudi ofiziala hartuko dut, npm eta yarn paketeen kudeatzaileekin ere badator. Nire izenean, npm pakete batzuk eraikitzeko beharrezkoa den node-gyp eta Google-ren Brotli konpresorea gehituko ditut, geroago erabilgarria izango zaiguna.

Dockerfile iruzkinekin.

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

Dagoeneko hemen minimalismoaren alde borrokatzen ari naiz, beraz, irudia talde handi batek osatzen du.

Amaitutako irudia hemen aurki daiteke: https://hub.docker.com/r/alexxxnf/spa-builder. Besteen irudietan ez fidatzea eta zureak biltzea gomendatzen dudan arren.

nginx

Edozein web zerbitzari erabil dezakezu eduki estatikoa banatzeko. Nginx-ekin lan egiten ohituta nago, beraz, orain erabiliko dut.

Nginx-ek Docker-en irudi ofiziala du, baina modulu gehiegi ditu banaketa estatiko sinplerako. Bidalketan sartuta daudenak talde berezi batek edo Dockerfile ofizialean ikus ditzake.

$ 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 erabiliko dut oinarri gisa, baina bertan eduki estatikoa banatzeko behar dena bakarrik utziko dut. Nire bertsioak ezin izango du HTTPS bidez funtzionatu, ez du baimenik onartzen eta askoz gehiago. Baina nire bertsioak Brotli algoritmoarekin konprimitutako fitxategiak banatzeko gai izango da, hau da, gzip baino apur bat eraginkorragoa dena. Fitxategiak behin konprimituko ditugu; ez dago hau hegan egin beharrik.

Hau da amaitu nuen Dockerfile. Errusierazko iruzkinak nireak dira, ingelesez - jatorrizkotik.

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

Berehala konponduko dut nginx.conf gzip eta brotli lehenespenez gaituta egon daitezen. Cacheko goiburuak ere sartuko ditut, ez baitugu inoiz estatikorik aldatuko. Eta azken ukitua 404 eskaera guztiak index.html-ra birbideratzea izango da, hau beharrezkoa da SPAn nabigatzeko.

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

Amaitutako irudia hemen deskargatu dezakezu: https://hub.docker.com/r/alexxxnf/nginx-spa. 10,5 MB hartzen ditu. Jatorrizko nginx-ek 19,7 MB hartzen zuen. Nire kirol interesa asetzen da.

Inguruneko aldagaiak ulertzeko estatika irakastea

Zergatik behar dira ezarpenak SPAn? Adibidez, zein RESTful API erabili zehazteko. Normalean, nahi den ingurunearen ezarpenak SPAra transferitzen dira eraikuntza-fasean. Zerbait aldatu behar baduzu, aplikazioa berreraiki beharko duzu. Ez dut nahi. Aplikazioa behin eraikitzea nahi dut CI fasean, eta CD fasean behar adina konfiguratzea ingurune-aldagaiak erabiliz.

Noski, fitxategi estatikoek ez dute ingurune-aldagairik ulertzen. Hori dela eta, trikimailu bat erabili beharko duzu. Azken irudian, ez dut nginx abiaraziko, ingurune-aldagaiak irakurri, fitxategi estatikoetan idatzi, konprimitu eta gero kontrola nginx-era transferituko duen shell script berezi bat baizik.

Horretarako, Dockerfile-k ENTRYPOINT parametroa eskaintzen du. Eman diezaiogun gidoi hau (Angular adibide gisa erabiliz):

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

Scriptak bere lana egin dezan, ezarpenak js fitxategietan idatzi behar dira formulario honetan: ${API_URL}.

Aipatzekoa da SPA moderno gehienek hash gehitzen dietela fitxategiei eraikitzerakoan. Beharrezkoa da arakatzaileak fitxategia denbora luzez segurtasunez gorde dezan. Fitxategia aldatzen bada, bere hash-a aldatuko da, eta horrek arakatzailea berriro deskargatzera behartuko du.

Zoritxarrez, nire metodoan, ingurune-aldagaien bidez konfigurazioa aldatzeak ez du fitxategi-hash-aren aldaketarik ekartzen, hau da, arakatzailearen cachea beste modu batean baliogabetu behar da. Ez dut arazo hau, konfigurazio desberdinak ingurune ezberdinetan zabaltzen direlako.

Azken irudia batzen

Azkenik.

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

Orain lortutako irudia edonon muntatu eta erabil daiteke.

Iturria: www.habr.com