From 15f550a3bb4bfb4dc0e8672516eb35c16cd8de9e Mon Sep 17 00:00:00 2001 From: Endial Fang Date: Tue, 14 Jul 2020 22:54:18 +0800 Subject: [PATCH] =?UTF-8?q?[fix:10]=E5=90=AF=E7=94=A8lib-nss?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 10/Dockerfile | 16 +- 10/prebuilds/usr/local/bin/appcommon.sh | 385 ++++++++++++----------- 10/prebuilds/usr/local/bin/entrypoint.sh | 8 +- 3 files changed, 218 insertions(+), 191 deletions(-) diff --git a/10/Dockerfile b/10/Dockerfile index 00c97a0..61bb58f 100644 --- a/10/Dockerfile +++ b/10/Dockerfile @@ -5,9 +5,11 @@ FROM colovu/ubuntu:18.04 # 外部指定应用版本信息,如 "--build-arg app_ver=6.0.0" ARG app_ver=10 -# 编译镜像时指定本地服务器地址,如 "--build-arg LOCAL_SERVER=http://172.29.14.108/dist-files/" -ARG LOCAL_SERVER +# 编译镜像时指定本地服务器地址,如 "--build-arg local_url=http://172.29.14.108/dist-files/" +ARG local_url="" + +# 定义应用基础常量信息,该常量在容器内可使用 ENV APP_NAME=postgresql \ APP_EXEC=postgres \ APP_USER=postgres \ @@ -53,13 +55,15 @@ RUN set -eux; \ # 为应用创建对应的组、用户、相关目录 APP_DIRS="${APP_DEF_DIR:-} ${APP_CONF_DIR:-} ${APP_DATA_DIR:-} ${APP_CACHE_DIR:-} ${APP_RUN_DIR:-} ${APP_LOG_DIR:-} ${APP_CERT_DIR:-} ${APP_WWW_DIR:-} ${APP_DATA_LOG_DIR:-} ${APP_BASE_DIR:-${APP_DATA_DIR}}"; \ mkdir -p ${APP_DIRS}; \ - groupadd -r ${APP_GROUP}; \ - useradd -r -g ${APP_GROUP} -s /usr/sbin/nologin -d ${APP_BASE_DIR} ${APP_USER}; \ + groupadd -r -g 999 ${APP_GROUP}; \ + useradd -r -g ${APP_GROUP} -u 999 -s /bin/bash -d ${APP_DATA_DIR} ${APP_USER}; \ \ # 应用软件包及依赖项。相关软件包在镜像创建完成时,不会被清理 appDeps=" \ postgresql-${APP_VERSION} \ postgresql-common \ + pgdg-keyring \ + libnss-wrapper \ xz-utils \ locales \ tzdata \ @@ -146,5 +150,5 @@ EXPOSE 5432 # 容器初始化命令,默认存放在:/usr/local/bin/entrypoint.sh ENTRYPOINT ["entrypoint.sh"] -# 应用程序的服务命令,必须使用非守护进程方式运行。该配置不能使用变量定义 -CMD ["postgres", "--config-file=/srv/conf/postgresql/10/main/postgresql.conf"] +# 应用程序的服务命令,必须使用非守护进程方式运行。如果使用变量,则该变量必须在运行环境中存在(ENV可以获取) +CMD ["${APP_EXEC}", "--config-file=${PG_CONF_FILE}"] diff --git a/10/prebuilds/usr/local/bin/appcommon.sh b/10/prebuilds/usr/local/bin/appcommon.sh index f3017fa..3b59e9b 100644 --- a/10/prebuilds/usr/local/bin/appcommon.sh +++ b/10/prebuilds/usr/local/bin/appcommon.sh @@ -14,6 +14,27 @@ # 函数列表 +# 配置 libnss_wrapper 以使得 PostgreSQL 命令可以以任意用户身份执行 +# 全局变量: +# PG_* +postgresql_enable_nss_wrapper() { + if ! getent passwd "$(id -u)" &> /dev/null && [ -e /usr/lib/libnss_wrapper.so ]; then + export LD_PRELOAD='/usr/lib/libnss_wrapper.so' + export NSS_WRAPPER_PASSWD="$(mktemp)" + export NSS_WRAPPER_GROUP="$(mktemp)" + echo "postgres:x:$(id -u):$(id -g):PostgreSQL:${PG_DATA_DIR}:/bin/false" > "$NSS_WRAPPER_PASSWD" + echo "postgres:x:$(id -g):" > "$NSS_WRAPPER_GROUP" + fi +} + +postgresql_disable_nss_wrapper() { + # unset/cleanup "nss_wrapper" bits + if [ "${LD_PRELOAD:-}" = '/usr/lib/libnss_wrapper.so' ]; then + rm -f "$NSS_WRAPPER_PASSWD" "$NSS_WRAPPER_GROUP" + unset LD_PRELOAD NSS_WRAPPER_PASSWD NSS_WRAPPER_GROUP + fi +} + # 加载应用使用的环境变量初始值,该函数在相关脚本中以 eval 方式调用 # 全局变量: # ENV_* : 容器使用的全局变量 @@ -183,85 +204,6 @@ postgresql_recover_set() { postgresql_common_conf_set "${PG_RECOVERY_FILE}" "$@" } -# 检测用户参数信息是否满足条件; 针对部分权限过于开放情况,打印提示信息 -# 全局变量: -# PG_* -app_verify_minimum_env() { - local error_code=0 - LOG_D "Validating settings in PG_* env vars..." - - # Auxiliary functions - print_validation_error() { - LOG_E "$1" - error_code=1 - } - - empty_password_enabled_warn() { - LOG_W "You set the environment variable ALLOW_EMPTY_PASSWORD=${ALLOW_EMPTY_PASSWORD}. For safety reasons, do not use this flag in a production environment." - } - empty_password_error() { - print_validation_error "The $1 environment variable is empty or not set. Set the environment variable ALLOW_EMPTY_PASSWORD=yes to allow the container to be started with blank passwords. This is recommended only for development." - } - if is_boolean_yes "$ALLOW_EMPTY_PASSWORD"; then - empty_password_enabled_warn - else - if [[ -z "$PG_PASSWORD" ]]; then - empty_password_error "PG_PASSWORD" - fi - if (( ${#PG_PASSWORD} > 100 )); then - print_validation_error "The password cannot be longer than 100 characters. Set the environment variable PG_PASSWORD with a shorter value" - fi - if [[ -n "$PG_USERNAME" ]] && [[ -z "$PG_PASSWORD" ]]; then - empty_password_error "PG_PASSWORD" - fi - if [[ -n "$PG_USERNAME" ]] && [[ "$PG_USERNAME" != "postgres" ]] && [[ -n "$PG_PASSWORD" ]] && [[ -z "$PG_DATABASE" ]]; then - print_validation_error "In order to use a custom PostgreSQL user you need to set the environment variable PG_DATABASE as well" - fi - fi - - if [[ -n "$PG_REPLICATION_MODE" ]]; then - if [[ "$PG_REPLICATION_MODE" = "master" ]]; then - if (( PG_NUM_SYNCHRONOUS_REPLICAS < 0 )); then - print_validation_error "The number of synchronous replicas cannot be less than 0. Set the environment variable PG_NUM_SYNCHRONOUS_REPLICAS" - fi - elif [[ "$PG_REPLICATION_MODE" = "slave" ]]; then - if [[ -z "$PG_MASTER_HOST" ]]; then - print_validation_error "Slave replication mode chosen without setting the environment variable PG_MASTER_HOST. Use it to indicate where the Master node is running" - fi - if [[ -z "$PG_REPLICATION_USER" ]]; then - print_validation_error "Slave replication mode chosen without setting the environment variable PG_REPLICATION_USER. Make sure that the master also has this parameter set" - fi - else - print_validation_error "Invalid replication mode. Available options are 'master/slave'" - fi - # Common replication checks - if [[ -n "$PG_REPLICATION_USER" ]] && [[ -z "$PG_REPLICATION_PASSWORD" ]]; then - empty_password_error "PG_REPLICATION_PASSWORD" - fi - else - if is_boolean_yes "$ALLOW_EMPTY_PASSWORD"; then - empty_password_enabled_warn - else - if [[ -z "$PG_PASSWORD" ]]; then - empty_password_error "PG_PASSWORD" - fi - if [[ -n "$PG_USERNAME" ]] && [[ -z "$PG_PASSWORD" ]]; then - empty_password_error "PG_PASSWORD" - fi - fi - fi - - if ! is_yes_no_value "$PG_ENABLE_LDAP"; then - empty_password_error "The values allowed for PG_ENABLE_LDAP are: yes or no" - fi - - if is_boolean_yes "$PG_ENABLE_LDAP" && [[ -n "$PG_LDAP_URL" ]] && [[ -n "$PG_LDAP_SERVER" ]]; then - empty_password_error "You can not set PG_LDAP_URL and PG_LDAP_SERVER at the same time. Check your LDAP configuration." - fi - - [[ "$error_code" -eq 0 ]] || exit "$error_code" -} - # 初始化 pg_hba.conf 文件,增加 LDAP 配置;同时保留本地认证 # 全局变量: # PG_* @@ -321,82 +263,6 @@ host all all ::/0 trust EOF } -# 更改默认监听地址为 "*" 或 "0.0.0.0",以对容器外提供服务;默认配置文件应当为仅监听 localhost(127.0.0.1) -app_enable_remote_connections() { - LOG_D "Modify default config to enable all IP access" - postgresql_conf_set "listen_addresses" "*" -} - -# 以后台方式启动应用服务,并等待启动就绪 -# 全局变量: -# PG_* -# ENV_DEBUG -app_start_server_bg() { - is_app_server_running && return - - # -w wait until operation completes (default) - # -W don't wait until operation completes - # -D location of the database storage area - # -l write (or append) server log to FILENAME - # -o command line options to pass to postgres or initdb - local -r pg_ctl_flags=("-W" "-D" "$PG_DATA_DIR" "-l" "$PG_LOG_FILE" "-o" "--config-file=$PG_CONF_FILE --external_pid_file=$PG_PID_FILE --hba_file=$PG_HBA_FILE") - LOG_I "Starting ${APP_NAME} in background..." - local pg_ctl_cmd=() - if _is_run_as_root; then - pg_ctl_cmd+=("gosu" "$APP_USER") - fi - pg_ctl_cmd+=(pg_ctl) - if is_boolean_yes "${ENV_DEBUG}"; then - "${pg_ctl_cmd[@]}" "start" "${pg_ctl_flags[@]}" - else - "${pg_ctl_cmd[@]}" "start" "${pg_ctl_flags[@]}" >/dev/null 2>&1 - fi - - local -r check_args=("-h" "localhost" "-p" "${PG_PORT_NUMBER}" "-U" "postgres") - local check_cmd=() - if _is_run_as_root; then - check_cmd=("gosu" "$APP_USER") - fi - check_cmd+=(pg_isready) - local counter=$PG_INIT_MAX_TIMEOUT - LOG_I "Checking ${APP_NAME} ready status..." - while ! PGPASSWORD=$PG_REPLICATION_PASSWORD "${check_cmd[@]}" "${check_args[@]}" >/dev/null 2>&1; do - sleep 1 - counter=$(( counter - 1 )) - if (( counter <= 0 )); then - LOG_E "PostgreSQL is not ready after $PG_INIT_MAX_TIMEOUT seconds" - exit 1 - fi - done - LOG_D "${APP_NAME} is ready for service..." -} - -# 停止 PostgreSQL 后台服务 -# 全局变量: -# PG_PID_FILE -app_stop_server() { - LOG_I "Stopping ${APP_NAME}..." - stop_service_using_pid "$PG_PID_FILE" -} - -# 检测应用服务是否在后台运行中 -# 全局变量: -# PG_PID_FILE -# 返回值: -# 布尔值 -is_app_server_running() { - local pid - pid="$(get_pid_from_file "$PG_PID_FILE")" - - if [[ -z "$pid" ]]; then - LOG_D "${APP_NAME} is Stopped..." - false - else - LOG_D "${APP_NAME} is Running..." - is_service_running "$pid" - fi -} - # 使用运行中的 PostgreSQL 服务执行 SQL 操作 # 全局变量: # ENV_DEBUG @@ -428,33 +294,6 @@ postgresql_execute() { fi } -# 清理初始化应用时生成的临时文件 -app_clean_tmp_file() { - LOG_D "Clean ${APP_NAME} tmp files..." - - rm -rf "${PG_LOG_FILE}" -} - -# 在重新启动容器时,删除标志文件及必须删除的临时文件 (容器重新启动) -# 全局变量: -# APP_* -# PG_* -app_clean_from_restart() { - local -r -a files=( - "$PG_DATA_DIR"/postmaster.pid - "$PG_DATA_DIR"/standby.signal - "$PG_DATA_DIR"/recovery.signal - "$PG_PID_FILE" - ) - - for file in "${files[@]}"; do - if [[ -f "$file" ]]; then - LOG_I "Cleaning stale $file file" - rm "$file" - fi - done -} - # 生成初始 postgres.conf 配置 # 全局变量: # PG_* @@ -556,6 +395,188 @@ postgresql_create_custom_database() { echo "CREATE DATABASE \"$PG_DATABASE\"" | postgresql_execute "" "postgres" "" "localhost" } +# 检测用户参数信息是否满足条件; 针对部分权限过于开放情况,打印提示信息 +# 全局变量: +# PG_* +app_verify_minimum_env() { + local error_code=0 + LOG_D "Validating settings in PG_* env vars..." + + # Auxiliary functions + print_validation_error() { + LOG_E "$1" + error_code=1 + } + + empty_password_enabled_warn() { + LOG_W "You set the environment variable ALLOW_EMPTY_PASSWORD=${ALLOW_EMPTY_PASSWORD}. For safety reasons, do not use this flag in a production environment." + } + empty_password_error() { + print_validation_error "The $1 environment variable is empty or not set. Set the environment variable ALLOW_EMPTY_PASSWORD=yes to allow the container to be started with blank passwords. This is recommended only for development." + } + if is_boolean_yes "$ALLOW_EMPTY_PASSWORD"; then + empty_password_enabled_warn + else + if [[ -z "$PG_PASSWORD" ]]; then + empty_password_error "PG_PASSWORD" + fi + if (( ${#PG_PASSWORD} > 100 )); then + print_validation_error "The password cannot be longer than 100 characters. Set the environment variable PG_PASSWORD with a shorter value" + fi + if [[ -n "$PG_USERNAME" ]] && [[ -z "$PG_PASSWORD" ]]; then + empty_password_error "PG_PASSWORD" + fi + if [[ -n "$PG_USERNAME" ]] && [[ "$PG_USERNAME" != "postgres" ]] && [[ -n "$PG_PASSWORD" ]] && [[ -z "$PG_DATABASE" ]]; then + print_validation_error "In order to use a custom PostgreSQL user you need to set the environment variable PG_DATABASE as well" + fi + fi + + if [[ -n "$PG_REPLICATION_MODE" ]]; then + if [[ "$PG_REPLICATION_MODE" = "master" ]]; then + if (( PG_NUM_SYNCHRONOUS_REPLICAS < 0 )); then + print_validation_error "The number of synchronous replicas cannot be less than 0. Set the environment variable PG_NUM_SYNCHRONOUS_REPLICAS" + fi + elif [[ "$PG_REPLICATION_MODE" = "slave" ]]; then + if [[ -z "$PG_MASTER_HOST" ]]; then + print_validation_error "Slave replication mode chosen without setting the environment variable PG_MASTER_HOST. Use it to indicate where the Master node is running" + fi + if [[ -z "$PG_REPLICATION_USER" ]]; then + print_validation_error "Slave replication mode chosen without setting the environment variable PG_REPLICATION_USER. Make sure that the master also has this parameter set" + fi + else + print_validation_error "Invalid replication mode. Available options are 'master/slave'" + fi + # Common replication checks + if [[ -n "$PG_REPLICATION_USER" ]] && [[ -z "$PG_REPLICATION_PASSWORD" ]]; then + empty_password_error "PG_REPLICATION_PASSWORD" + fi + else + if is_boolean_yes "$ALLOW_EMPTY_PASSWORD"; then + empty_password_enabled_warn + else + if [[ -z "$PG_PASSWORD" ]]; then + empty_password_error "PG_PASSWORD" + fi + if [[ -n "$PG_USERNAME" ]] && [[ -z "$PG_PASSWORD" ]]; then + empty_password_error "PG_PASSWORD" + fi + fi + fi + + if ! is_yes_no_value "$PG_ENABLE_LDAP"; then + empty_password_error "The values allowed for PG_ENABLE_LDAP are: yes or no" + fi + + if is_boolean_yes "$PG_ENABLE_LDAP" && [[ -n "$PG_LDAP_URL" ]] && [[ -n "$PG_LDAP_SERVER" ]]; then + empty_password_error "You can not set PG_LDAP_URL and PG_LDAP_SERVER at the same time. Check your LDAP configuration." + fi + + [[ "$error_code" -eq 0 ]] || exit "$error_code" +} + +# 更改默认监听地址为 "*" 或 "0.0.0.0",以对容器外提供服务;默认配置文件应当为仅监听 localhost(127.0.0.1) +app_enable_remote_connections() { + LOG_D "Modify default config to enable all IP access" + postgresql_conf_set "listen_addresses" "*" +} + +# 以后台方式启动应用服务,并等待启动就绪 +# 全局变量: +# PG_* +# ENV_DEBUG +app_start_server_bg() { + is_app_server_running && return + + # -w wait until operation completes (default) + # -W don't wait until operation completes + # -D location of the database storage area + # -l write (or append) server log to FILENAME + # -o command line options to pass to postgres or initdb + local -r pg_ctl_flags=("-W" "-D" "$PG_DATA_DIR" "-l" "$PG_LOG_FILE" "-o" "--config-file=$PG_CONF_FILE --external_pid_file=$PG_PID_FILE --hba_file=$PG_HBA_FILE") + LOG_I "Starting ${APP_NAME} in background..." + local pg_ctl_cmd=() + if _is_run_as_root; then + pg_ctl_cmd+=("gosu" "$APP_USER") + fi + pg_ctl_cmd+=(pg_ctl) + if is_boolean_yes "${ENV_DEBUG}"; then + "${pg_ctl_cmd[@]}" "start" "${pg_ctl_flags[@]}" + else + "${pg_ctl_cmd[@]}" "start" "${pg_ctl_flags[@]}" >/dev/null 2>&1 + fi + + local -r check_args=("-h" "localhost" "-p" "${PG_PORT_NUMBER}" "-U" "postgres") + local check_cmd=() + if _is_run_as_root; then + check_cmd=("gosu" "$APP_USER") + fi + check_cmd+=(pg_isready) + local counter=$PG_INIT_MAX_TIMEOUT + LOG_I "Checking ${APP_NAME} ready status..." + while ! PGPASSWORD=$PG_REPLICATION_PASSWORD "${check_cmd[@]}" "${check_args[@]}" >/dev/null 2>&1; do + sleep 1 + counter=$(( counter - 1 )) + if (( counter <= 0 )); then + LOG_E "PostgreSQL is not ready after $PG_INIT_MAX_TIMEOUT seconds" + exit 1 + fi + done + LOG_D "${APP_NAME} is ready for service..." +} + +# 停止 PostgreSQL 后台服务 +# 全局变量: +# PG_PID_FILE +app_stop_server() { + LOG_I "Stopping ${APP_NAME}..." + stop_service_using_pid "$PG_PID_FILE" +} + +# 检测应用服务是否在后台运行中 +# 全局变量: +# PG_PID_FILE +# 返回值: +# 布尔值 +is_app_server_running() { + local pid + pid="$(get_pid_from_file "$PG_PID_FILE")" + + if [[ -z "$pid" ]]; then + LOG_D "${APP_NAME} is Stopped..." + false + else + LOG_D "${APP_NAME} is Running..." + is_service_running "$pid" + fi +} + +# 清理初始化应用时生成的临时文件 +app_clean_tmp_file() { + LOG_D "Clean ${APP_NAME} tmp files..." + + rm -rf "${PG_LOG_FILE}" +} + +# 在重新启动容器时,删除标志文件及必须删除的临时文件 (容器重新启动) +# 全局变量: +# APP_* +# PG_* +app_clean_from_restart() { + local -r -a files=( + "$PG_DATA_DIR"/postmaster.pid + "$PG_DATA_DIR"/standby.signal + "$PG_DATA_DIR"/recovery.signal + "$PG_PID_FILE" + ) + + for file in "${files[@]}"; do + if [[ -f "$file" ]]; then + LOG_I "Cleaning stale $file file" + rm "$file" + fi + done +} + # 应用默认初始化操作 # 执行完毕后,生成文件 ${APP_CONF_DIR}/.app_init_flag 及 ${APP_DATA_DIR}/.data_init_flag 文件 docker_app_init() { diff --git a/10/prebuilds/usr/local/bin/entrypoint.sh b/10/prebuilds/usr/local/bin/entrypoint.sh index fa819ce..7b6bde0 100644 --- a/10/prebuilds/usr/local/bin/entrypoint.sh +++ b/10/prebuilds/usr/local/bin/entrypoint.sh @@ -30,6 +30,8 @@ APP_DIRS="${APP_DIRS} ${PG_DATA_DIR}" # 打印镜像欢迎信息 docker_print_welcome +postgresql_enable_nss_wrapper + # 检测数据卷,创建默认的关联目录,并拷贝所必须的默认配置文件及初始化文件 # 全局变量: # APP_* @@ -84,7 +86,7 @@ _main() { LOG_I "Restart container with default user: ${APP_USER}" export RESTART_FLAG=1 - exec gosu "${APP_USER}" "$0" "$@" + exec gosu "${APP_USER}" "$0" $(eval echo "$@") fi # 执行预初始化操作 @@ -98,8 +100,8 @@ _main() { fi LOG_I "Start container with command: $@" - # 执行命令行 - exec "$@" + # 执行命令行。使用 evel 替换后,可支持 Dockerfile 脚本的 CMD 命令中使用变量 + exec $(eval echo "$@") } # 脚本入口命令