在Docker中启动和填充Postgres容器

73

我有一个 Docker 容器,其中包含我的 Postgres 数据库。它使用官方的 Postgres 镜像,其 CMD 条目在主线程上启动服务器。

我想通过运行 RUN psql –U postgres postgres < /dump/dump.sql 在开始侦听查询之前填充数据库。

我不明白这在 Docker 中怎么可能。如果我将 RUN 命令放在 CMD 后面,它当然永远不会被执行,因为 Docker 已经读取完 Dockerfile。但是,如果我将它放在 CMD 前面,它将在 psql 作为进程存在之前运行。

如何在 Docker 中预填 Postgres 数据库?


你可以启动你的 Docker PostgreSQL 容器,然后运行 docker exec -it container psql –U postgres postgres < /dump/dump.sql 命令,即使这不是一个完美的解决方案,使用 supervisor 似乎有些过度(http://docs.docker.com/articles/using_supervisord/)。 - user2915097
2
postgres镜像提供了在/docker-entrypoint-initdb.d目录中执行的启动脚本的功能。您可以使用--single参数在服务正式启动之前“私下”执行postgres。这里有一个很好的扩展镜像的例子,使用了一个启动脚本:https://registry.hub.docker.com/u/sameersbn/postgresql/ 查看启动脚本,它使用了--single参数。 - Greg
你可以将任何脚本放入 /docker-entrypoint-initdb.d 目录中。你可以自己取名字和编写脚本内容。你的脚本会加载数据。 - Greg
postgres --single 通常在转储文件中会失败。这是因为它将每一行解析为单独的 SQL 语句,或者将整个文件解析为一个语句,而这些都不适用于真正的转储。请参阅 postgres manpage - Daniel Vérité
正确答案似乎在官方Postgres镜像(https://registry.hub.docker.com/_/postgres/)的“如何扩展此图像”部分下,而不是您提供的链接中。 - Migwell
显示剩余2条评论
9个回答

61

经过一番努力,我终于找到了解决方案;-)

对我来说,这里发布的评论非常有用:https://registry.hub.docker.com/_/postgres/,来自“justfalter”

无论如何,我是这样做的:

# Dockerfile
FROM postgres:9.4

RUN mkdir -p /tmp/psql_data/

COPY db/structure.sql /tmp/psql_data/
COPY scripts/init_docker_postgres.sh /docker-entrypoint-initdb.d/

db/structure.sql是一个sql转储文件,用于初始化第一个表空间。

然后是init_docker_postgres.sh脚本。

#!/bin/bash

# this script is run when the docker container is built
# it imports the base database structure and create the database for the tests

DATABASE_NAME="db_name"
DB_DUMP_LOCATION="/tmp/psql_data/structure.sql"

echo "*** CREATING DATABASE ***"

# create default database
gosu postgres postgres --single <<EOSQL
    CREATE DATABASE "$DATABASE_NAME";
    GRANT ALL PRIVILEGES ON DATABASE "$DATABASE_NAME" TO postgres;
EOSQL

# clean sql_dump - because I want to have a one-line command

# remove indentation
sed "s/^[ \t]*//" -i "$DB_DUMP_LOCATION"

# remove comments
sed '/^--/ d' -i "$DB_DUMP_LOCATION"

# remove new lines
sed ':a;N;$!ba;s/\n/ /g' -i "$DB_DUMP_LOCATION"

# remove other spaces
sed 's/  */ /g' -i "$DB_DUMP_LOCATION"

# remove firsts line spaces
sed 's/^ *//' -i "$DB_DUMP_LOCATION"

# append new line at the end (suggested by @Nicola Ferraro)
sed -e '$a\' -i "$DB_DUMP_LOCATION"

# import sql_dump
gosu postgres postgres --single "$DATABASE_NAME" < "$DB_DUMP_LOCATION";


echo "*** DATABASE CREATED! ***"

最后,所以:
# no postgres is running
[myserver]# psql -h 127.0.0.1 -U postgres
psql: could not connect to server: Connection refused
    Is the server running on host "127.0.0.1" and accepting
    TCP/IP connections on port 5432?

[myserver]# docker build -t custom_psql .
[myserver]# docker run -d --name custom_psql_running -p 5432:5432 custom_psql

[myserver]# docker ps -a
CONTAINER ID        IMAGE                COMMAND                CREATED             STATUS              PORTS                    NAMES
ce4212697372        custom_psql:latest   "/docker-entrypoint.   9 minutes ago       Up 9 minutes        0.0.0.0:5432->5432/tcp   custom_psql_running

[myserver]# psql -h 127.0.0.1 -U postgres
psql (9.2.10, server 9.4.1)
WARNING: psql version 9.2, server version 9.4.
            Some psql features might not work.
Type "help" for help.

postgres=# 

# postgres is now initialized with the dump

希望能帮到您!

2
我还会在.sql文件末尾添加一个新行,因为如果没有尾随的空白行,则最后一条语句不会被执行(sed -e '$a\' -i "$DB_DUMP_LOCATION")。 - Nicola Ferraro
感谢@NicolaFerraro,我之前不知道这个,我会把你的建议添加到回复中。 - damoiser
那对我没用。容器已经退出,数据库无法访问。 - Hanan Shteingart
当你没有提供有关问题的任何额外信息时,很难帮助你@HananShteingart。尝试记录各个步骤以调试出程序的崩溃并检查你的SQL-dump。对于我和其他人而言,这意味着你的问题可能是由其他事情引起的。 - damoiser
首先,在 Docker 构建时复制整个数据集不太方便,因为当您拥有新的数据时,无法使用该镜像。此外,“发送构建上下文到 Docker 守护程序…”需要很长时间(我有 30GB 的数据集)。 - Hanan Shteingart
显示剩余4条评论

48

对于那些想在第一次运行时使用数百万条记录初始化PostgreSQL DB的人。

使用*.sql dump导入

您可以进行简单的sql dump并将dump.sql文件复制到/docker-entrypoint-initdb.d/。问题在于速度。我的dump.sql脚本约为17MB(小型DB-10个表,其中一个表中有100k行),初始化需要超过一分钟!这对于本地开发/单元测试等是不可接受的。

使用二进制转储进行导入

解决方案是制作二进制PostgreSQL转储并使用shell脚本初始化支持。 然后同样的DB只需用500毫秒进行初始化,而不是1分钟。

1.从容器内部或本地DB直接创建名为“my-db”的DB的dump.pgdata二进制转储

pg_dump -U postgres --format custom my-db > "dump.pgdata"

或者从运行容器(postgres-container)的主机中获取

docker exec postgres-container pg_dump -U postgres --format custom my-db > "dump.pgdata"

2. 使用给定的转储和初始化脚本创建Docker镜像

$ tree
.
├── Dockerfile
└── docker-entrypoint-initdb.d
    ├── 01-restore.sh
    ├── 02-small-updates.sql
    └── dump.pgdata

$ cat Dockerfile
FROM postgres:11

COPY ./docker-entrypoint-initdb.d/ /docker-entrypoint-initdb.d/

$ cat docker-entrypoint-initdb.d/01-restore.sh
#!/bin/bash

file="/docker-entrypoint-initdb.d/dump.pgdata"
dbname=my-db

echo "Restoring DB using $file"
pg_restore -U postgres --dbname=$dbname --verbose --single-transaction < "$file" || exit 1

$ cat docker-entrypoint-initdb.d/02-small-updates.sql
-- some updates on your DB, for example for next application version
-- this file will be executed on DB during next release
UPDATE ... ;

3. 构建镜像并运行

$ docker build -t db-test-img .
$ docker run -it --rm --name db-test db-test-img

2
这很简单清晰。谢谢! - Glen Thompson
2
在docker-entrypoint-initdb.d中的.sql文件将自行运行(至少一次)。 来自docker postgres镜像的注意事项: “警告:仅当您使用空数据目录启动容器时,才会运行/docker-entrypoint-initdb.d中的脚本;任何现有的数据库都将在容器启动时保持不变。一个常见的问题是,如果您的/docker-entrypoint-initdb.d脚本之一失败(这将导致entrypoint脚本退出),并且您的编排器使用已初始化的数据目录重新启动容器,则它将不会继续执行您的脚本。” - Jordan
docker-entrypoint-initdb.d/01-restore.sh 文件中加上 || exit 1 的目的是什么?我对 bash 的 || 有很好的理解,但当我在我的环境中尝试这个答案时,由于 exit 1,Postgres 容器会过早退出。 - wgj
@wgj 命令 pg_restore 没有以 0 退出代码完成 - 它失败了。我猜可能有类似于 || exit $? 的东西将退出代码转发到容器中。 - Petr Újezdský
@PetrÚjezdský 当然可以,但是返回非零代码并不等同于显式调用exit 1;当使用|| exit 1时,容器会在Postgres开始接受连接之前突然终止。我想知道预期目标是什么,因为我不清楚为什么你想要|| exit 1。从我的角度来看,它就像一个错误。谢谢 :) - wgj

31

或者,您可以将一个包含所有DDL脚本的卷挂载到/docker-entrypoint-initdb.d/。您可以放置* .sh、*.sql 或 *.sql.gz文件,它会在启动时执行这些文件。

例如(假设您的脚本位于 /tmp/my_scripts 中)

docker run -v /tmp/my_scripts:/docker-entrypoint-initdb.d postgres

3
谢谢!我会说这取决于您的使用情况。但是对于生产环境,最好从不同位置挂载,比如NFS或主机?否则,一旦销毁容器,所有数据都将被清除。 - darthbinamira
4
自从我写下我的新手评论后,我意识到 "entrypoint" 目录只有在安装 Postgres 时才会被 Postgres 读取,这当然只会发生一次,每个容器启动或停止并不会使其 Postgres 再次扫描该目录。(现在我打算删除那个误导性的评论。 :)) - Jaroslav Záruba

2
"最初的回答":我按照@damoiser提供的解决方案进行了操作,唯一不同的情况是我想要导入所有转储数据。请按照以下解决方案进行操作。(我没有进行任何检查)
FROM postgres:9.5

RUN mkdir -p /tmp/psql_data/

COPY db/structure.sql /tmp/psql_data/
COPY scripts/init_docker_postgres.sh /docker-entrypoint-initdb.d/

那么,init_docker_postgres.sh脚本即为最初的回答。
#!/bin/bash

DB_DUMP_LOCATION="/tmp/psql_data/structure.sql"

echo "*** CREATING DATABASE ***"

psql -U postgres < "$DB_DUMP_LOCATION";

echo "*** DATABASE CREATED! ***"

and then you can build your image as

docker build -t abhije***/postgres-data .

docker run -d abhije***/postgres-data 

2
还有另一种选项可用,它利用了 Flocker:
Flocker是一个容器数据卷管理器,旨在允许像PostgreSQL这样的数据库在生产环境中轻松运行。在生产环境中运行数据库时,您必须考虑从主机故障中恢复等问题。Flocker提供了跨机器集群管理数据卷的工具,就像在生产环境中一样。例如,当针对服务器故障响应而调度Postgres容器时,Flocker可以在同一时间自动将其相关的数据卷移动到不同主机之间。这意味着当您的Postgres容器在新主机上启动时,它已经有了它的数据。这个操作可以通过Flocker API或CLI手动完成,也可以由与Flocker集成的容器编排工具(例如Docker Swarm、Kubernetes或Mesos)自动完成。

1
我的解决方案受到了Alex Dguez答案的启发,但很不幸对我不起作用,因为:
  1. 我使用的是pg-9.6基础镜像,而RUN /docker-entrypoint.sh --help从未运行成功,总是报错The command '/bin/sh -c /docker-entrypoint.sh -' returned a non-zero code: 1
  2. 我不想污染/docker-entrypoint-initdb.d目录

以下答案最初来自我在另一篇帖子中的回复:https://dev59.com/16jka4cB1Zd3GeqPEtOh#59303962。需要注意的是,该解决方案是用于从二进制转储中还原,而非OP要求的纯SQL。但可以稍加修改以适应纯SQL情况。

Dockerfile:

FROM postgres:9.6.16-alpine

LABEL maintainer="lu@cobrainer.com"
LABEL org="Cobrainer GmbH"

ARG PG_POSTGRES_PWD=postgres
ARG DBUSER=someuser
ARG DBUSER_PWD=P@ssw0rd
ARG DBNAME=sampledb
ARG DB_DUMP_FILE=example.pg

ENV POSTGRES_DB launchpad
ENV POSTGRES_USER postgres
ENV POSTGRES_PASSWORD ${PG_POSTGRES_PWD}
ENV PGDATA /pgdata

COPY wait-for-pg-isready.sh /tmp/wait-for-pg-isready.sh
COPY ${DB_DUMP_FILE} /tmp/pgdump.pg

RUN set -e && \
    nohup bash -c "docker-entrypoint.sh postgres &" && \
    /tmp/wait-for-pg-isready.sh && \
    psql -U postgres -c "CREATE USER ${DBUSER} WITH SUPERUSER CREATEDB CREATEROLE ENCRYPTED PASSWORD '${DBUSER_PWD}';" && \
    psql -U ${DBUSER} -d ${POSTGRES_DB} -c "CREATE DATABASE ${DBNAME} TEMPLATE template0;" && \
    pg_restore -v --no-owner --role=${DBUSER} --exit-on-error -U ${DBUSER} -d ${DBNAME} /tmp/pgdump.pg && \
    psql -U postgres -c "ALTER USER ${DBUSER} WITH NOSUPERUSER;" && \
    rm -rf /tmp/pgdump.pg

HEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 \
  CMD pg_isready -U postgres -d launchpad

wait-for-pg-isready.sh所在的位置:

#!/bin/bash
set -e

get_non_lo_ip() {
  local _ip _non_lo_ip _line _nl=$'\n'
  while IFS=$': \t' read -a _line ;do
    [ -z "${_line%inet}" ] &&
        _ip=${_line[${#_line[1]}>4?1:2]} &&
        [ "${_ip#127.0.0.1}" ] && _non_lo_ip=$_ip
    done< <(LANG=C /sbin/ifconfig)
  printf ${1+-v} $1 "%s${_nl:0:$[${#1}>0?0:1]}" $_non_lo_ip
}

get_non_lo_ip NON_LO_IP
until pg_isready -h $NON_LO_IP -U "postgres" -d "launchpad"; do
  >&2 echo "Postgres is not ready - sleeping..."
  sleep 4
done

>&2 echo "Postgres is up - you can execute commands now"

上述脚本以及更详细的README可在https://github.com/cobrainer/pg-docker-with-restored-db获取。

0

我能够通过在Docker文件中的运行命令前加上/etc/init.d/postgresql来加载数据。我的Docker文件有以下行,对我有效:

RUN /etc/init.d/postgresql start && /usr/bin/psql -a < /tmp/dump.sql

尝试使用docker镜像postgres:9.6我收到以下错误:“没有PostgreSQL集群存在;请参阅' man pg_createcluster '...(警告)。 ” - gelonida

0

我的目标是拥有一个包含数据库的镜像 - 也就是每次执行docker run或者docker-compose up时都可以节省重建它的时间。

我们只需要将docker-entrypoint.sh中的exec "$@"这行代码删除。所以我在我的Dockerfile中添加了以下内容:

#Copy my ssql scripts into the image to /docker-entrypoint-initdb.d:
COPY ./init_db /docker-entrypoint-initdb.d

#init db
RUN grep -v 'exec "$@"' /usr/local/bin/docker-entrypoint.sh > /tmp/docker-entrypoint-without-serverstart.sh && \
    chmod a+x /tmp/docker-entrypoint-without-serverstart.sh && \
    /tmp/docker-entrypoint-without-serverstart.sh postgres && \
    rm -rf /docker-entrypoint-initdb.d/* /tmp/docker-entrypoint-without-serverstart.sh

0

我们需要进行端到端测试,需要一个包含结构和数据的数据库,这些数据已经保存在我们的Docker镜像中,我们已经完成了以下操作:

Dockerfile:

FROM postgres:9.4.24-alpine
ENV POSTGRES_USER postgres
ENV POSTGRES_PASSWORD postgres
ENV PGDATA /pgdata
COPY database.backup /tmp/
COPY database_restore.sh /docker-entrypoint-initdb.d/
RUN /docker-entrypoint.sh --help
RUN rm -rf /docker-entrypoint-initdb.d/database_restore.sh
RUN rm -rf /tmp/database.backup

database_restore.sh:

#!/bin/sh
set -e 
pg_restore -C -d postgres /tmp/database.backup

创建图像的方法:
docker build .

启动容器:

docker run --name docker-postgres -d -p 5432:5432 <Id-docker-image>

这并不是每次启动容器时都会恢复数据库。数据库的结构和数据已经包含在创建的Docker镜像中。

我们基于这篇文章,但省略了多阶段: 在Docker中创建快速、轻量级的测试数据库

编辑:使用9.4.24-alpine版本,因为版本9.4-alpine现在无法运行database_restore.sh脚本。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接