Update container build to use Alpine Linux and Gunicorn instead of CentOS and Apache. Fixes #3246

This results in a much more slim-line container, requiring fewer resources to run.
In addition, the majority of the build is now done using the Docker infrastructure, allowing for quicker rebuilds and better use of layers.
This commit is contained in:
Максим Кольцов 2018-04-04 16:18:17 +01:00 committed by Dave Page
parent 1617d003cd
commit 05e2e3cb39
10 changed files with 120 additions and 192 deletions

View File

@ -33,6 +33,7 @@ Features
| `Feature #3182 <https://redmine.postgresql.org/issues/3182>`_ - Update Jasmine to v3
| `Feature #3184 <https://redmine.postgresql.org/issues/3184>`_ - Add a French translation
| `Feature #3195 <https://redmine.postgresql.org/issues/3195>`_ - Pass the service name to external processes
| `Feature #3246 <https://redmine.postgresql.org/issues/3246>`_ - Update container build to use Alpine Linux and Gunicorn instead of CentOS/Apache
| `In addition, various changes were made for PEP8 compliance`

2
pkg/docker/.dockerignore Normal file
View File

@ -0,0 +1,2 @@
pgadmin4/web/**/tests/
pgadmin4/web/regression/

View File

@ -7,58 +7,63 @@
#
#########################################################################
# Get the basics out of the way
FROM centos:latest
# First of all, build frontend with NodeJS in a separate builder container
# Node-6 with ABI v48 is supported by all needed C++ packages
FROM node:6 AS node-builder
LABEL name="pgAdmin 4" \
vendor="The pgAdmin Development Team" \
license="PostgreSQL"
COPY ./pgadmin4/web/ /pgadmin4/web/
WORKDIR /pgadmin4/web
# We only need the web/ directory, and a few other things
COPY web /var/www/pgadmin
COPY requirements.txt /var/www/pgadmin
RUN yarn install --cache-folder ./ycache --verbose && \
yarn run bundle && \
rm -rf ./ycache ./pgadmin/static/js/generated/.cache
# Install everything we need. Use easy_install to get pip, to avoid setting up EPEL
RUN yum install -y python-setuptools python-devel httpd mod_wsgi mod_ssl gcc
RUN easy_install pip
RUN pip install j2cli
# Build Sphinx documentation in separate container
FROM python:3.6-alpine3.7 as docs-builder
# Now install the Python runtime dependencies
RUN pip install -r /var/www/pgadmin/requirements.txt
# Install only dependencies absolutely required for documentation building
RUN apk add --no-cache make
RUN pip install --no-cache-dir \
sphinx flask_security flask_paranoid python-dateutil flask_sqlalchemy \
flask_gravatar simplejson
# Create required directories for config
COPY ./pgadmin4/ /pgadmin4
RUN LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8 make -C /pgadmin4/docs/en_US -f Makefile.sphinx html
# Create required directories for running
RUN mkdir -p /var/log/pgadmin
RUN chown -R apache /var/log/pgadmin
RUN mkdir -p /var/lib/pgadmin
RUN chown -R apache /var/lib/pgadmin
RUN mkdir -p /certs
RUN chown -R apache /certs
RUN chmod 700 /certs
# Then install backend, copy static files and set up entrypoint
# Need alpine3.7 to get pg_dump and friends in postgresql-client package
FROM python:3.6-alpine3.7
# Push logs to the container's output streams
RUN ln -sf /proc/self/fd/1 /var/log/httpd/access_log && \
ln -sf /proc/self/fd/1 /var/log/httpd/ssl_access_log && \
ln -sf /proc/self/fd/2 /var/log/httpd/error_log && \
ln -sf /proc/self/fd/2 /var/log/httpd/ssl_error_log
RUN pip --no-cache-dir install gunicorn
RUN apk add --no-cache postgresql-client postgresql-libs
# Apache config time
RUN mkdir -p /templates
COPY pgadmin4.conf.j2 /templates/
COPY entry.sh /
WORKDIR /pgadmin4
ENV PYTHONPATH=/pgadmin4
# Finally, remove packages we only needed for building
RUN yum -y remove gcc cpp glibc-devel glibc-headers kernel-headers libgomp libmpc mpfr
# Install build-dependencies, build & install C extensions and purge deps in one RUN step
# so that deps do not increase the size of resulting image by remaining in layers
COPY ./pgadmin4/requirements.txt /pgadmin4
RUN set -ex && \
apk add --no-cache --virtual build-deps build-base postgresql-dev && \
pip install --no-cache-dir -r requirements.txt && \
apk del --no-cache build-deps
# Default config options
ENV PGADMIN_DEFAULT_EMAIL container@pgadmin.org
ENV PGADMIN_DEFAULT_PASSWORD Conta1ner
ENV PGADMIN_ENABLE_TLS False
ENV PGADMIN_SERVER_NAME pgadmin4
COPY --from=node-builder /pgadmin4/web/pgadmin/static/js/generated/ /pgadmin4/pgadmin/static/js/generated/
COPY --from=docs-builder /pgadmin4/docs/en_US/_build/html/ /pgadmin4/docs/
COPY ./pgadmin4/web /pgadmin4
COPY ./run_pgadmin.py /pgadmin4
COPY ./config_distro.py /pgadmin4
RUN pip install --no-cache-dir -r requirements.txt
# Precompile and optimize python code to save time and space on startup
RUN python -O -m compileall /pgadmin4
COPY ./entrypoint.sh /entrypoint.sh
VOLUME /var/lib/pgadmin
EXPOSE 80 443
# Start the service
ENTRYPOINT ["/bin/bash", "/entry.sh"]
ENTRYPOINT ["/entrypoint.sh"]

View File

@ -4,14 +4,16 @@ Building
========
Whilst you can just use the Dockerfile directly, it requires that various pre-configuration steps are performed, for
example, the pgAdmin web code must be copied to ./web and yarn install/yarn run bundle must be executed.
requirements.txt is also expected to be in this directory, and the pre-built docs must be in web/docs.
example, the pgAdmin web code must be copied to `./web`, Sphinx documentation source must be copied to `./docs`
and `requirements.txt` is also expected to be in this directory.
The recommended (and easy) way to build the container is to do:
```console
cd $PGADMIN_SRC/
workon pgadmin-venv
make docker
```
This will call the build script $PGADMIN_SRC/pkg/docker/build.sh which will prepare a staging directory containing all
the required files, then build the container and push it to your repo.
@ -21,57 +23,51 @@ Running
The container will accept the following variables at startup:
PGADMIN_DEFAULT_EMAIL
---------------------
Default: container@pgadmin.org)
PGADMIN_SETUP_EMAIL
-------------------
This is the email address used when setting up the initial administrator account to login to pgAdmin.
PGADMIN_DEFAULT_PASSWORD
------------------------
Default: Conta1ner
PGADMIN_SETUP_PASSWORD
----------------------
This is the password used when setting up the initial administrator account to login to pgAdmin.
PGADMIN_ENABLE_TLS
------------------
Default: False
Default: unset
If set to the default, False, the container will listen on port 80 for connections in plain text. If set to True, the
container will listen on port 443 for TLS connections.
If not set, the container will listen on port 8080 for connections in insecure HTTP protocol.
If set to any value, the container will listen on port 8443 for TLS connections.
When TLS is enabled, a certificate and key must be provided. Typically these should be stored on the host file system
and mounted from the container. The expected paths are /certs/server.crt and /certs/server.key
When TLS is enabled, a certificate and key must be provided.
Typically these should be stored on the host file system and mounted from the container.
The expected paths are `/certs/server.crt` and `/certs/server.key`.
PGADMIN_SERVER_NAME
-------------------
Default: pgadmin4
This variable allows you to specify the value used for the Apache HTTPD ServerName directive. This is commonly used to
ensure the CN of the TLS certificate matches what the server expects.
You need to explicitly map these ports with `-p` option to some port at your machine.
Examples
========
Run a simple container over port 80:
docker run -p 80:80 \
-e "PGADMIN_DEFAULT_EMAIL=user@domain.com" \
-e "PGADMIN_DEFAULT_PASSWORD=SuperSecret" \
```console
docker run -p 80:8080 \
-e "PGADMIN_SETUP_EMAIL=user@domain.com" \
-e "PGADMIN_SETUP_PASSWORD=SuperSecret" \
-d pgadmin4
```
Run a TLS secured container using a shared config/storage directory in /private/var/lib/pgadmin on the host:
docker run -p 443:443 \
```console
docker run -p 443:8443 \
-v "/private/var/lib/pgadmin:/var/lib/pgadmin" \
-v "/path/to/certificate.cert:/certs/server.cert" \
-v "/path/to/certificate.key:/certs/server.key" \
-e "PGADMIN_DEFAULT_EMAIL=user@domain.com" \
-e "PGADMIN_DEFAULT_PASSWORD=SuperSecret" \
-e "PGADMIN_ENABLE_TLS=True" \
-e "PGADMIN_SERVER_NAME=pgadmin.domain.com" \
-d pgadmin4
-e "PGADMIN_SETUP_EMAIL=user@domain.com" \
-e "PGADMIN_SETUP_PASSWORD=SuperSecret" \
-e "PGADMIN_ENABLE_TLS=1" \
-d pgadmin4
```

View File

@ -41,60 +41,22 @@ if [ -d docker-build ]; then
rm -rf docker-build
fi
mkdir docker-build
# Create the output directory if not present
if [ ! -d dist ]; then
mkdir dist
fi
mkdir -p docker-build/pgadmin4
# Build the clean tree
for FILE in `git ls-files web`
do
echo Adding $FILE
# We use tar here to preserve the path, as Mac (for example) doesn't support cp --parents
tar cf - $FILE | (cd docker-build; tar xf -)
done
pushd web
yarn install
yarn run bundle
rm -rf pgadmin/static/js/generated/.cache
for FILE in `ls -d pgadmin/static/js/generated/*`
do
echo Adding $FILE
tar cf - $FILE | (cd ../docker-build/web; tar xf -)
done
popd
# Build the docs
if [ -d docs/en_US/_build/html ]; then
rm -rf docs/en_US/_build/html
fi
LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8 make -C docs/en_US -f Makefile.sphinx html
mkdir docker-build/web/docs
cp -R docs/en_US/_build/html/* docker-build/web/docs/
# Configure pgAdmin
echo "HELP_PATH = '../../docs/'" >> docker-build/web/config_distro.py
echo "DEFAULT_BINARY_PATHS = {" >> docker-build/web/config_distro.py
echo " 'pg': ''," >> docker-build/web/config_distro.py
echo " 'ppas': ''," >> docker-build/web/config_distro.py
echo " 'gpdb': ''" >> docker-build/web/config_distro.py
echo "}" >> docker-build/web/config_distro.py
echo Copying source tree...
git archive HEAD -- docs web requirements.txt | tar xvf - -C docker-build/pgadmin4
# Copy the Docker specific assets into place
cp pkg/docker/Dockerfile docker-build/
cp pkg/docker/entry.sh docker-build/
cp pkg/docker/pgadmin4.conf.j2 docker-build/
cp requirements.txt docker-build/
cp pkg/docker/Dockerfile \
pkg/docker/entrypoint.sh \
pkg/docker/config_distro.py \
pkg/docker/run_pgadmin.py \
pkg/docker/.dockerignore \
docker-build/
# Build the container
docker build docker-build -t $CONTAINER_NAME \
-t $CONTAINER_NAME:latest \
-t $CONTAINER_NAME:$APP_RELEASE \
-t $CONTAINER_NAME:$APP_LONG_VERSION
-t $CONTAINER_NAME:$APP_LONG_VERSION

View File

@ -0,0 +1,4 @@
HELP_PATH = '../../docs'
DEFAULT_BINARY_PATHS = {
'pg': '/usr/bin'
}

View File

@ -1,29 +0,0 @@
#!/usr/bin/env bash
########################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2018, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
#########################################################################
export PGADMIN_SETUP_EMAIL=${PGADMIN_DEFAULT_EMAIL}
export PGADMIN_SETUP_PASSWORD=${PGADMIN_DEFAULT_PASSWORD}
if [ ${PGADMIN_ENABLE_TLS} != "True" ]; then
if [ -f /etc/httpd/conf.d/ssl.conf ]; then
mv /etc/httpd/conf.d/ssl.conf /etc/httpd/conf.d/ssl.conf.disabled
fi
else
if [ -f /etc/httpd/conf.d/ssl.conf.disabled ]; then
mv /etc/httpd/conf.d/ssl.conf.disabled /etc/httpd/conf.d/ssl.conf
fi
fi
j2 /templates/pgadmin4.conf.j2 > /etc/httpd/conf.d/pgadmin4.conf
rm -f /run/httpd/httpd.pid
/usr/sbin/httpd -D FOREGROUND

26
pkg/docker/entrypoint.sh Executable file
View File

@ -0,0 +1,26 @@
#!/bin/sh
if [ ! -f /var/lib/pgadmin/pgadmin4.db ]; then
if [ -z "${PGADMIN_DEFAULT_EMAIL}" -o -z "${PGADMIN_DEFAULT_PASSWORD}" ]; then
echo 'You need to specify PGADMIN_DEFAULT_EMAIL and PGADMIN_DEFAULT_PASSWORD environment variables'
exit 1
fi
# Set the default username and password in a
# backwards compatible way
export PGADMIN_SETUP_EMAIL=${PGADMIN_DEFAULT_EMAIL}
export PGADMIN_SETUP_PASSWORD=${PGADMIN_DEFAULT_PASSWORD}
# Initialize DB before starting Gunicorn
# Importing pgadmin4 (from this script) is enough
python run_pgadmin.py
fi
# NOTE: currently pgadmin can run only with 1 worker due to sessions implementation
# Using --threads to have multi-threaded single-process worker
if [ ! -z ${PGADMIN_ENABLE_TLS} ]; then
exec gunicorn --bind 0.0.0.0:${PGADMIN_LISTEN_PORT:-443} -w 1 --threads ${GUNICORN_THREADS:-25} --access-logfile - --keyfile /certs/server.key --certfile /certs/server.cert run_pgadmin:app
else
exec gunicorn --bind 0.0.0.0:${PGADMIN_LISTEN_PORT:-80} -w 1 --threads ${GUNICORN_THREADS:-25} --access-logfile - run_pgadmin:app
fi

View File

@ -1,43 +0,0 @@
########################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2018, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
#########################################################################
ServerName {{ PGADMIN_SERVER_NAME }}
{% if PGADMIN_ENABLE_TLS|default('False') == 'True' %}
LoadModule ssl_module modules/mod_ssl.so
<VirtualHost *:443>
SSLEngine on
SSLCipherSuite HIGH:!aNULL:!MD5
SSLCertificateFile "/certs/server.cert"
SSLCertificateKeyFile "/certs/server.key"
ServerName {{ PGADMIN_SERVER_NAME }}
WSGIDaemonProcess pgadmin processes=1 threads=25
WSGIScriptAlias / /var/www/pgadmin/pgAdmin4.wsgi
<Directory /var/www/pgadmin>
WSGIProcessGroup pgadmin
WSGIApplicationGroup %{GLOBAL}
Order deny,allow
Allow from all
</Directory>
</VirtualHost>
{% else %}
<VirtualHost *:80>
WSGIDaemonProcess pgadmin processes=1 threads=25
WSGIScriptAlias / /var/www/pgadmin/pgAdmin4.wsgi
<Directory /var/www/pgadmin>
WSGIProcessGroup pgadmin
WSGIApplicationGroup %{GLOBAL}
Order deny,allow
Allow from all
</Directory>
</VirtualHost>
{% endif %}

View File

@ -0,0 +1,4 @@
import builtins
builtins.SERVER_MODE = True
from pgAdmin4 import app