Yagona sahifali dastur (SPA) bu statik JavaScript va HTML fayllari, shuningdek, tasvirlar va boshqa resurslar toΚ»plamidir. Ular dinamik ravishda o'zgarmasligi sababli ularni onlayn nashr qilish juda oson. Buning uchun oddiy GitHub sahifalaridan boshlab (ba'zilar uchun narod.ru bilan) va Amazon S3 kabi CDN bilan tugaydigan ko'plab arzon va hatto bepul xizmatlar mavjud. Biroq, menga boshqa narsa kerak edi.
Menga SPA bilan Docker tasviri kerak edi, shunda uni ishlab chiqarishda ham Kubernetes klasterining bir qismi sifatida, ham SPA nima ekanligini bilmaydigan orqa dasturchining mashinasida osongina ishga tushirish mumkin edi.
Men o'zim uchun quyidagi rasm talablarini aniqladim:
- foydalanish qulayligi (lekin yig'ish emas);
- disk va RAM jihatidan ham minimal o'lcham;
- tasvirni turli muhitlarda ishlatish uchun muhit o'zgaruvchilari orqali konfiguratsiya;
- fayllarni eng samarali taqsimlash.
Bugun men sizga qanday qilib aytaman:
- ichak nginx;
- manbalardan brotli qurish;
- statik fayllarni muhit o'zgaruvchilarini tushunishga o'rgatish;
- va, albatta, bularning barchasidan Docker tasvirini qanday yig'ish kerak.
Ushbu maqolaning maqsadi o'z tajribam bilan bo'lishish va tajribali jamoa a'zolarini konstruktiv tanqidga undashdir.
Yig'ish uchun rasm yaratish
Yakuniy Docker tasvirini kichik hajmda qilish uchun siz ikkita qoidaga rioya qilishingiz kerak: minimal qatlamlar va minimalist asosiy tasvir. Eng kichik asosiy tasvirlardan biri Alpine Linux tasviridir, shuning uchun men buni tanlayman. Ba'zilar Alp tog'larini ishlab chiqarish uchun mos emas deb ta'kidlashi mumkin va ular to'g'ri bo'lishi mumkin. Lekin shaxsan men u bilan hech qachon muammoga duch kelmaganman va unga qarshi argumentlar ham yo'q.
Qatlamlar kamroq bo'lishi uchun men tasvirni 2 bosqichda yig'aman. Birinchisi, qoralama, unda barcha yordamchi dasturlar va vaqtinchalik fayllar qoladi. Va oxirgi bosqichda men faqat dasturning yakuniy versiyasini yozaman.
Keling, yordamchi tasvirdan boshlaylik.
SPA ilovasini kompilyatsiya qilish uchun sizga odatda node.js kerak bo'ladi. Men rasmiy tasvirni olaman, u ham npm va ip paketi menejerlari bilan birga keladi. O'z nomimdan ba'zi npm paketlarini yaratish uchun zarur bo'lgan node-gyp va Google'dan Brotli kompressorini qo'shaman, bu biz uchun keyinchalik foydali bo'ladi.
Izohlar bilan 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
Bu erda men minimalizm uchun kurashaman, shuning uchun tasvirni bitta katta jamoa birlashtiradi.
Tayyor rasmni bu yerda topishingiz mumkin:
nginx
Statik tarkibni tarqatish uchun istalgan veb-serverdan foydalanishingiz mumkin. Men nginx bilan ishlashga o'rganib qolganman, shuning uchun uni hozir ishlataman.
Nginx rasmiy Docker tasviriga ega, ammo u oddiy statik tarqatish uchun juda ko'p modullarga ega. Qaysi biri etkazib berishga kiritilganligini maxsus guruh yoki rasmiy Dockerfile-da ko'rish mumkin.
$ 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
Men Dockerfile-dan asos sifatida foydalanaman, lekin unda faqat statik tarkibni tarqatish uchun zarur bo'lgan narsalarni qoldiraman. Mening versiyam HTTPS orqali ishlay olmaydi, avtorizatsiyani qo'llab-quvvatlamaydi va boshqalar. Ammo mening versiyam gzip-ga qaraganda bir oz samaraliroq bo'lgan Brotli algoritmi bilan siqilgan fayllarni tarqatishi mumkin. Biz fayllarni bir marta siqib chiqaramiz, buni tezda qilishning hojati yo'q.
Bu men tugatgan Dockerfile. Rus tilidagi sharhlar meniki, ingliz tilida - asl nusxadan.
Docker fayli
# ΠΠ°Π·ΠΎΠ²ΡΠΉ ΠΎΠ±ΡΠ°Π· ΡΠ½ΠΎΠ²Π° 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;"]
Men darhol nginx.conf ni tuzataman, shunda gzip va brotli sukut bo'yicha yoqilgan bo'ladi. Shuningdek, men keshlash sarlavhalarini ham qo'shaman, chunki biz hech qachon statikni o'zgartirmaymiz. Va oxirgi teginish barcha 404 so'rovni index.html ga yo'naltirish bo'ladi, bu SPAda navigatsiya qilish uchun zarur.
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";
}
}
}
}
Tayyor rasmni bu yerdan yuklab olishingiz mumkin:
Atrof-muhit o'zgaruvchilarini tushunish uchun statikani o'rgatish
Nima uchun SPA-da sozlamalar kerak bo'lishi mumkin? Masalan, qaysi RESTful API ishlatishni belgilash uchun. Odatda, kerakli muhit uchun sozlamalar qurish bosqichida SPA-ga o'tkaziladi. Agar biror narsani o'zgartirish kerak bo'lsa, dasturni qayta tiklashingiz kerak bo'ladi. Men buni xohlamayman. Men dasturni CI bosqichida bir marta qurishni va atrof-muhit o'zgaruvchilari yordamida CD bosqichida kerak bo'lganda sozlashni xohlayman.
Albatta, statik fayllarning o'zi hech qanday muhit o'zgaruvchilarini tushunmaydi. Shuning uchun, siz hiyla ishlatishingiz kerak bo'ladi. Yakuniy rasmda men nginx-ni ishga tushirmayman, balki atrof-muhit o'zgaruvchilarini o'qiydi, ularni statik fayllarga yozadi, siqadi va shundan keyingina boshqaruvni nginx-ga o'tkazadigan maxsus qobiq skriptini ishga tushiraman.
Buning uchun Dockerfile ENTRYPOINT parametrini taqdim etadi. Keling, unga quyidagi skriptni beraylik (misol sifatida Angular-dan foydalanib):
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 "$@"
Skript o'z vazifasini bajarishi uchun sozlamalar JS fayllarida quyidagi shaklda yozilishi kerak:${API_URL}
.Shuni ta'kidlash kerakki, zamonaviy SPAlarning ko'pchiligi qurishda o'z fayllariga xeshlarni qo'shadi. Bu brauzer faylni uzoq vaqt xavfsiz tarzda keshlashi uchun zarur. Agar fayl o'zgarmasa, uning xeshi o'zgaradi, bu esa o'z navbatida brauzerni faylni qayta yuklab olishga majbur qiladi.
Afsuski, mening usulimda, muhit o'zgaruvchilari orqali konfiguratsiyani o'zgartirish fayl xeshini o'zgartirishga olib kelmaydi, ya'ni brauzer keshini boshqa yo'l bilan bekor qilish kerak. Menda bu muammo yo'q, chunki turli xil konfiguratsiyalar turli muhitlarda joylashtirilgan.
Yakuniy rasmni birlashtirish
Nihoyat.
Docker fayli
# ΠΠ΅ΡΠ²ΡΠΉ Π±Π°Π·ΠΎΠ²ΡΠΉ ΠΎΠ±ΡΠ°Π· Π΄Π»Ρ ΡΠ±ΠΎΡΠΊΠΈ 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;"]
Endi olingan tasvirni yig'ish va istalgan joyda ishlatish mumkin.
Manba: www.habr.com