រូបភាព Docker សម្រាប់ចែកចាយកម្មវិធី Single Page

កម្មវិធីតែមួយទំព័រ (SPA) គឺជាសំណុំនៃឯកសារ JavaScript និង HTML ឋិតិវន្ត ក៏ដូចជារូបភាព និងធនធានផ្សេងទៀត។ ដោយសារតែពួកគេមិនផ្លាស់ប្តូរថាមវន្ត ការផ្សព្វផ្សាយពួកគេតាមអ៊ីនធឺណិតគឺងាយស្រួលណាស់។ មានសេវាកម្មថោក និងឥតគិតថ្លៃមួយចំនួនធំសម្រាប់ការនេះ ដោយចាប់ផ្តើមពីទំព័រ GitHub សាមញ្ញ (និងសម្រាប់ខ្លះសូម្បីតែជាមួយ narod.ru) និងបញ្ចប់ដោយ CDN ដូចជា Amazon S3 ។ ទោះយ៉ាងណាក៏ដោយ ខ្ញុំត្រូវការអ្វីផ្សេងទៀត។

ខ្ញុំត្រូវការរូបភាព Docker ជាមួយ SPA ដើម្បីឱ្យវាអាចដំណើរការបានយ៉ាងងាយស្រួលទាំងនៅក្នុងផលិតកម្មដែលជាផ្នែកមួយនៃក្រុម Kubernetes និងនៅលើម៉ាស៊ីនរបស់អ្នកអភិវឌ្ឍន៍ផ្នែកខាងក្រោយដែលមិនដឹងថាអ្វីជា SPA នោះទេ។

ខ្ញុំបានកំណត់តម្រូវការរូបភាពខាងក្រោមសម្រាប់ខ្លួនខ្ញុំផ្ទាល់៖

  • ភាពងាយស្រួលនៃការប្រើប្រាស់ (ប៉ុន្តែមិនមានការជួបប្រជុំគ្នា);
  • ទំហំអប្បបរមាទាំងនៅក្នុងលក្ខខណ្ឌនៃថាសនិង RAM;
  • ការ​កំណត់​រចនាសម្ព័ន្ធ​តាម​រយៈ​អថេរ​បរិស្ថាន ដូច្នេះ​រូបភាព​អាច​ត្រូវ​បាន​ប្រើ​ក្នុង​បរិស្ថាន​ផ្សេង​គ្នា​។
  • ការចែកចាយឯកសារប្រកបដោយប្រសិទ្ធភាពបំផុត។

ថ្ងៃនេះខ្ញុំនឹងប្រាប់អ្នកពីរបៀប៖

  • nginx ពោះវៀន;
  • បង្កើត brotli ពីប្រភព;
  • បង្រៀនឯកសារឋិតិវន្តឱ្យយល់ពីអថេរបរិស្ថាន។
  • ហើយជាការពិតណាស់ របៀបប្រមូលផ្តុំរូបភាព Docker ពីទាំងអស់នេះ។

គោលបំណងនៃអត្ថបទនេះគឺដើម្បីចែករំលែកបទពិសោធន៍របស់ខ្ញុំ និងធ្វើឱ្យសមាជិកសហគមន៍ដែលមានបទពិសោធន៍មានការរិះគន់ក្នុងន័យស្ថាបនា។

ការបង្កើតរូបភាពសម្រាប់ការជួបប្រជុំគ្នា។

ដើម្បីធ្វើឱ្យរូបភាព Docker ចុងក្រោយមានទំហំតូច អ្នកត្រូវប្រកាន់ខ្ជាប់នូវច្បាប់ចំនួនពីរ៖ អប្បបរមានៃស្រទាប់ និងរូបភាពមូលដ្ឋានអប្បបរមា។ រូបភាពមូលដ្ឋានតូចបំផុតមួយគឺរូបភាព Alpine Linux ដូច្នេះហើយជាអ្វីដែលខ្ញុំនឹងជ្រើសរើស។ អ្នក​ខ្លះ​ប្រហែល​ជា​ប្រកែក​ថា​អាល់ផែន​មិន​ស័ក្តិសម​សម្រាប់​ការ​ផលិត​ទេ ហើយ​វា​ប្រហែល​ជា​ត្រូវ។ ប៉ុន្តែ​ដោយ​ផ្ទាល់ ខ្ញុំ​មិន​ដែល​មាន​បញ្ហា​អ្វី​ជាមួយ​គាត់​ទេ ហើយ​ក៏​គ្មាន​ការ​ឈ្លោះ​ប្រកែក​ជាមួយ​គាត់​ដែរ។

ដើម្បី​មាន​ស្រទាប់​តិច​ជាង​នេះ ខ្ញុំ​នឹង​ប្រមូល​រូបភាព​ជា 2 ដំណាក់កាល។ ទីមួយគឺជាសេចក្តីព្រាង ឧបករណ៍ប្រើប្រាស់ជំនួយ និងឯកសារបណ្តោះអាសន្នទាំងអស់នឹងនៅតែមាននៅក្នុងវា។ ហើយនៅដំណាក់កាលចុងក្រោយ ខ្ញុំនឹងសរសេរតែកំណែចុងក្រោយនៃកម្មវិធីប៉ុណ្ណោះ។

ចូរចាប់ផ្តើមជាមួយរូបភាពជំនួយ។

ដើម្បីចងក្រងកម្មវិធី SPA ជាធម្មតាអ្នកត្រូវការ node.js ។ ខ្ញុំនឹងយករូបភាពផ្លូវការ ដែលភ្ជាប់មកជាមួយកម្មវិធីគ្រប់គ្រងកញ្ចប់ npm និង yarn ។ ក្នុងនាមខ្ញុំផ្ទាល់ ខ្ញុំនឹងបន្ថែម 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 ដំណើរការ --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 ដែលខ្ញុំបានបញ្ចប់។ មតិជាភាសារុស្សីគឺជារបស់ខ្ញុំ ជាភាសាអង់គ្លេស - ពីដើម។

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 MB ។ nginx ដើមបានយក 19,7 MB ។ ចំណាប់អារម្មណ៍កីឡារបស់ខ្ញុំគឺពេញចិត្ត។

ការបង្រៀនឋិតិវន្តដើម្បីយល់ពីអថេរបរិស្ថាន

ហេតុអ្វីចាំបាច់ត្រូវការការកំណត់នៅក្នុង SPA? ឧទាហរណ៍ ដើម្បីបញ្ជាក់ថាតើ RESTful API ណាដែលត្រូវប្រើ។ ជាធម្មតា ការកំណត់សម្រាប់បរិស្ថានដែលចង់បានត្រូវបានផ្ទេរទៅ SPA នៅដំណាក់កាលសាងសង់។ ប្រសិនបើអ្នកត្រូវការផ្លាស់ប្តូរអ្វីមួយ អ្នកនឹងត្រូវបង្កើតកម្មវិធីឡើងវិញ។ ខ្ញុំ​មិន​ចង់​បាន​វា​។ ខ្ញុំ​ចង់​ឱ្យ​កម្មវិធី​ត្រូវ​បាន​បង្កើត​ឡើង​ម្តង​នៅ​ដំណាក់​កាល CI ហើយ​បាន​កំណត់​រចនាសម្ព័ន្ធ​ច្រើន​តាម​ការ​ចាំបាច់​នៅ​ដំណាក់កាល CD ដោយ​ប្រើ​អថេរ​បរិស្ថាន។

ជាការពិតណាស់ ឯកសារឋិតិវន្តខ្លួនឯងមិនយល់ពីអថេរបរិស្ថានណាមួយឡើយ។ ដូច្នេះអ្នកនឹងត្រូវប្រើល្បិច។ នៅក្នុងរូបភាពចុងក្រោយ ខ្ញុំនឹងមិនបើកដំណើរការ nginx ទេ ប៉ុន្តែស្គ្រីបសែលពិសេសដែលនឹងអានអថេរបរិស្ថាន សរសេរពួកវាទៅឯកសារឋិតិវន្ត បង្ហាប់ពួកវា ហើយគ្រាន់តែផ្ទេរការគ្រប់គ្រងទៅ nginx ប៉ុណ្ណោះ។

ចំពោះគោលបំណងនេះ Dockerfile ផ្តល់នូវប៉ារ៉ាម៉ែត្រ 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}.

វាគួរឱ្យកត់សម្គាល់ថា SPAs ទំនើបភាគច្រើនបន្ថែម hash ទៅក្នុងឯកសាររបស់ពួកគេនៅពេលសាងសង់។ នេះគឺចាំបាច់ដើម្បីឱ្យកម្មវិធីរុករកអាចរក្សាទុកឯកសារដោយសុវត្ថិភាពក្នុងរយៈពេលយូរ។ ប្រសិនបើឯកសារមានការផ្លាស់ប្តូរ នោះសញ្ញារបស់វានឹងផ្លាស់ប្តូរ ដែលនៅក្នុងវេននឹងបង្ខំឱ្យកម្មវិធីរុករកតាមអ៊ីនធឺណិតទាញយកឯកសារម្តងទៀត។

ជាអកុសល នៅក្នុងវិធីសាស្រ្តរបស់ខ្ញុំ ការផ្លាស់ប្តូរការកំណត់តាមរយៈអថេរបរិស្ថានមិននាំឱ្យមានការផ្លាស់ប្តូរនៅក្នុងឯកសារ hash ដែលមានន័យថាឃ្លាំងសម្ងាត់កម្មវិធីរុករកត្រូវតែមិនត្រឹមត្រូវតាមវិធីផ្សេងទៀត។ ខ្ញុំ​មិន​មាន​បញ្ហា​នេះ​ទេ ព្រោះ​ការ​កំណត់​រចនាសម្ព័ន្ធ​ផ្សេង​គ្នា​ត្រូវ​បាន​គេ​ដាក់​ពង្រាយ​ក្នុង​បរិស្ថាន​ផ្សេង​គ្នា។

ភ្ជាប់រូបភាពចុងក្រោយ

ទីបំផុត។

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

ឥឡូវនេះរូបភាពលទ្ធផលអាចត្រូវបានផ្គុំនិងប្រើគ្រប់ទីកន្លែង។

ប្រភព: www.habr.com