单页应用程序Docker映像

单页应用程序(SPA)是一组静态JavaScript和HTML文件以及图像和其他资源。由于它们不会动态更改,因此在Internet上发布它们非常简单。为此,有大量廉价甚至免费的服务,从简单的GitHub Pages(甚至有些甚至是narod.ru)开始,到以Amazon S3之类的CDN结尾。但是,我还需要其他东西。


我需要带有SPA的Docker映像,以便可以轻松地将其作为Kubernetes集群的一部分在生产环境中以及在不知道SPA是什么的后端开发人员机器上启动。


我为自己确定了以下图像要求:


  • 易于使用(但不便于组装);
  • 磁盘和RAM的最小大小;
  • 通过环境变量进行设置,以便可以在不同环境中使用图像;
  • 最有效的文件分发。

今天我将告诉您如何:


  • 肠nginx;
  • 从源头编译brotli;
  • 教静态文件以了解环境变量;
  • 当然还有如何从所有这些构建Docker映像。

本文的目的是分享我的经验,并激发经验丰富的社区成员进行建设性批评。


建立图像以建立


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