遇到个很有趣的讨论,先看一下这个docker-compose配置脚本:

version: '2'

services:
  redis:
    image: 'redis:5.0.3-stretch'
    restart: always
    command: redis-server --requirepass redis
    environment:
      # ALLOW_EMPTY_PASSWORD is recommended only for development.
      - ALLOW_EMPTY_PASSWORD=yes
      - REDIS_DISABLE_COMMANDS=FLUSHDB,FLUSHALL
    ports:
      - '6379:6379'
    volumes:
      - 'redis_data:/bitnami/redis/data'

volumes:
  redis_data:
    driver: local

这里先交代一下出现这样脚本的背景,因为一直开始的时候使用的是bitnami版本,因为其支持免密配置,在配置开发环境具有很棒的优势,后来为了安全起见,使用了官方提供的镜像文件redis:5.0.3-stretch,之后就有了这么奇怪的配置脚本,既有requirepass,也写了ALLOW_EMPTY_PASSWORD=yes

在这个配置中,在commend中配置passwd:redis,然而又在enviroment中配置了allow_empty_password=yes,那么这样的配置,到底是需要密码还是不需要呢?理由又是怎样呢?

关于redis在docker-compose中免密配置的讨论

解决这个问题,先知道,问题肯定不是在docker-compose上,这玩意充其量就是个脚本解析器,通常情况下,运行指定的参数优先级大于环境变量,环境变量大于配置文件,可事实上真的是这样吗?那么我们就需要去阅读redis源码了。有兴趣的可以自己读读看redis源码

src/config.c中写到,如果是用了requirepass这个命令,则会把password的内容写进sds中,不过好像还是没有说明到environment对于redis配置文件的影响,甚至是说在整份config文件中没有介绍到对于environment这个变量的调用,因此可以推断到开发者应该是没有考虑使用environment

关于redis在docker-compose中免密配置的讨论

为了求证这个猜测,因为并不想大海捞针去发掘源码,所以去看下官方对于config的介绍

Passing arguments via the command line
Since Redis 2.6 it is possible to also pass Redis configuration parameters using the command line directly. This is very useful for testing purposes. The following is an example that starts a new Redis instance using port 6380 as a slave of the instance running at 127.0.0.1 port 6379.

./redis-server --port 6380 --slaveof 127.0.0.1 6379

The format of the arguments passed via the command line is exactly the same as the one used in the redis.conf file, with the exception that the keyword is prefixed with --.

Note that internally this generates an in-memory temporary config file (possibly concatenating the config file passed by the user if any) where arguments are translated into the format of redis.conf.

Changing Redis configuration while the server is running
It is possible to reconfigure Redis on the fly without stopping and restarting the service, or querying the current configuration programmatically using the special commands CONFIG SET and CONFIG GET

Not all the configuration directives are supported in this way, but most are supported as expected. Please refer to the CONFIG SET and CONFIG GET pages for more information.

Note that modifying the configuration on the fly has no effects on the redis.conf file so at the next restart of Redis the old configuration will be used instead.

Make sure to also modify the redis.conf file accordingly to the configuration you set using CONFIG SET. You can do it manually, or starting with Redis 2.8, you can just use CONFIG REWRITE, which will automatically scan your redis.conf file and update the fields which don't match the current configuration value. Fields non existing but set to the default value are not added. Comments inside your configuration file are retained.

通过介绍,我们中可以,知道对于redis的配置有两种方式:

  • 通过命令行,这个方式就是在通过commend指令进行配置的,属于初始化配置,会被写入到redis.conf文件中
  • 在运行过程中,这个方式是在redis运行的过程中进行配置的,属于暂时配置,也不会写入到redis.conf,当服务重启的时候,原来的配置就失效,需要重新输入指令config set xxx

至此,我们知道environment对于正版redis是无效的,那么为什么bitnami版本有这个效果呢?这个时候我们就需要看下这哥们是怎么设计的了。
先看下人家的docker-compose.yml,确实是允许免密进入,这个好是好,自己一个人小打小闹可还行,遇到团队协助或者在生产环境中分分钟被喷

version: '2'

services:
  redis:
    image: 'bitnami/redis:5.0-centos-7'
    environment:
      # ALLOW_EMPTY_PASSWORD is recommended only for development.
      - ALLOW_EMPTY_PASSWORD=yes
      - REDIS_DISABLE_COMMANDS=FLUSHDB,FLUSHALL
    ports:
      - '6379:6379'
    volumes:
      - 'redis_data:/bitnami/redis/data'

volumes:
  redis_data:
    driver: local

之后是分析他的dockerfile,前面的打包内容确实没啥好看的,主要是看最后两步ENTRYPOINT [ "/entrypoint.sh" ] && CMD [ "/run.sh" ],这个是其允许免密配置的关键,我们继续探究源码

FROM bitnami/centos-extras-base:7-r269
LABEL maintainer "Bitnami <containers@bitnami.com>"

ENV BITNAMI_PKG_CHMOD="-R g+rwX" \
    HOME="/" \
    OS_ARCH="x86_64" \
    OS_FLAVOUR="centos-7" \
    OS_NAME="linux"

# Install required system packages and dependencies
RUN install_packages glibc
RUN . ./libcomponent.sh && component_unpack "redis" "5.0.7-0" --checksum 0046ebee1870e41fe422f646d504a8ec84efb85152189ee434d8f4c9ad2917c7

COPY rootfs /
RUN /postunpack.sh
ENV BITNAMI_APP_NAME="redis" \
    BITNAMI_IMAGE_VERSION="5.0.7-centos-7-r59" \
    NAMI_PREFIX="/.nami" \
    PATH="/opt/bitnami/redis/bin:$PATH"

EXPOSE 6379

USER 1001
ENTRYPOINT [ "/entrypoint.sh" ]
CMD [ "/run.sh" ]

事实上,比较重要的就是entrypoint.sh这个文件,因为run.sh其实是用来进行启动初始化用的,有兴趣的读者可以进去读一下源码,这里就不再赘述,我们不妨看下entrypoint.sh源码

#!/bin/bash

set -o errexit
set -o nounset
set -o pipefail
#set -o xtrace
# shellcheck disable=SC1091

# Load libraries
. /libbitnami.sh
. /libredis.sh

# Load Redis environment variables
eval "$(redis_env)"

print_welcome_page

if [[ "$*" = *"/run.sh"* ]]; then
    info "** Starting Redis setup **"
    /setup.sh
    info "** Redis setup finished! **"
fi

echo ""
exec "$@"

从代码中知道,程序会先执行libbitnami.shlibredis.sh完成配置依赖库的工作,然后使用eval函数将数据进行提取,之后通过run.sh这个脚本启动redis,那问题的答案越来越近了:),先发现libredis.sh就先读这份代码

关于redis在docker-compose中免密配置的讨论

在这份代码中,可以通过搜索关键字ALLOW_EMPTY_PASSWORD确定是不是有,我们要配置脚本,事实上也被我们找到了

关于redis在docker-compose中免密配置的讨论

就是这个函数实现了免密注册redis的效果

redis_validate() {
    debug "Validating settings in REDIS_* env vars.."
    local error_code=0

    # Auxiliary functions
    print_validation_error() {
        error "$1"
        error_code=1
    }

    empty_password_enabled_warn() {
        warn "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
        [[ -z "$REDIS_PASSWORD" ]] && empty_password_error REDIS_PASSWORD
    fi
    if [[ -n "$REDIS_REPLICATION_MODE" ]]; then
        if [[ "$REDIS_REPLICATION_MODE" =~ ^(slave|replica)$ ]]; then
            if [[ -n "$REDIS_MASTER_PORT_NUMBER" ]]; then
                if ! err=$(validate_port "$REDIS_MASTER_PORT_NUMBER"); then
                    print_validation_error "An invalid port was specified in the environment variable REDIS_MASTER_PORT_NUMBER: $err"
                fi
            fi
            if ! is_boolean_yes "$ALLOW_EMPTY_PASSWORD" && [[ -z "$REDIS_MASTER_PASSWORD" ]]; then
                empty_password_error REDIS_MASTER_PASSWORD
            fi
        elif [[ "$REDIS_REPLICATION_MODE" != "master" ]]; then
            print_validation_error "Invalid replication mode. Available options are 'master/replica'"
        fi
    fi

    [[ "$error_code" -eq 0 ]] || exit "$error_code"
}

探究源码真的挺有趣的,让我们剖根究底,直面本源,知道之前拿来主义时没有经过思考就随便抄的不良后果,原来的docker-compose配置中,用了5.0.3-stretch这个版本之后,environment这段文字实际上就是无效的。至此搁笔,问题解决

参考资料:

  1. redis官方对于config的介绍,https://redis.io/topics/config
  2. redis源码,https://github.com/antirez/redis/
  3. docker-compose redis:bitnami,https://github.com/bitnami/bitnami-docker-redis