Imagen de Docker de aplicación de página única

La aplicación de una sola página (SPA) es un conjunto de archivos estáticos de JavaScript y HTML, así como imágenes y otros recursos. Como no cambian dinámicamente, publicarlos en Internet es muy simple. Para hacer esto, hay una gran cantidad de servicios baratos e incluso gratuitos, que comienzan con una simple GitHub Pages (y para alguien incluso con narod.ru) y terminan con un CDN como Amazon S3. Sin embargo, necesitaba algo más.


Necesitaba una imagen de Docker con SPA para poder lanzarla fácilmente tanto en producción como parte de un clúster de Kubernetes como en una máquina de desarrollo back-end que no tiene idea de qué es SPA.


Determiné los siguientes requisitos de imagen para mí:


  • facilidad de uso (pero no montaje);
  • tamaño mínimo tanto en términos de disco como de RAM;
  • establecer a través de variables de entorno para que la imagen pueda usarse en diferentes entornos;
  • La distribución de archivos más eficiente.

Hoy te diré cómo:


  • intestino nginx;
  • compilar brotli desde la fuente;
  • enseñar archivos estáticos para comprender variables de entorno;
  • y, por supuesto, cómo construir una imagen Docker a partir de todo esto.

El propósito de este artículo es compartir mi experiencia y provocar a miembros experimentados de la comunidad a críticas constructivas.


Construye una imagen para construir


Docker- , : . Alpine Linux, . - , Alpine , , . .


, 2 . – , . .


.


, SPA-, , node.js. npm yarn. node-gyp, npm-, Brotli Google, .


Dockerfile .
#  
FROM node:12-alpine
LABEL maintainer="Aleksey Maydokin <amaydokin@gmail.com>"
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


web-. 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 . – , – .


Dockerfile
#    Alpine
FROM alpine:3.9
LABEL maintainer="Aleksey Maydokin <amaydokin@gmail.com>"
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 ? , , RESTful API . SPA . - , . . CI, , , CD .


, . , . nginx, shell-, , , 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 "$"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$"); 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 . , . - , , .


, , , - . , .



-.


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

-.


All Articles