【问题标题】:Starting and populating a Postgres container in Docker在 Docker 中启动和填充 Postgres 容器
【发布时间】:2021-10-19 01:14:19
【问题描述】:

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

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

我不明白 Docker 怎么可能做到这一点。如果我将RUN 命令放在 CMD 之后,当然永远不会到达,因为 Docker 已经完成了对 Dockerfile 的读取。但是如果我将它放在CMD 之前,它将在 psql 甚至作为进程存在之前运行。

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

【问题讨论】:

  • 你可以启动你的d​​ocker Postgresql容器,然后docker exec -it container psql –U postgres postgres < /dump/dump.sql,即使这不是一个完美的解决方案,而且使用supervisor似乎有点过头了(docs.docker.com/articles/using_supervisord
  • 数据库必须侦听连接才能获取数据。您可以使用仅允许访问某个 IP、UNIX 套接字、另一个端口等的配置来启动它,具体取决于你的配置。然后加载您的数据,然后将配置更改为正常。
  • postgres 映像具有添加在 /docker-entrypoint-initdb.d 目录中启动时执行的脚本的功能。在服务正式启动之前,您将使用 --single 参数执行 postgres 以“私下”执行此操作。这里有一个使用启动脚本扩展图像的好例子:registry.hub.docker.com/u/sameersbn/postgresql 看看启动脚本,它确实 --single
  • 你的意思是哪个启动脚本?页面上有几个。如果有正确的方法可以做到这一点,并且您可以向我展示正确的部分,请将其发布为答案,我会将其标记为正确!
  • @DanielVérité,谢谢,每天都能学到新东西!

标签: database postgresql docker


【解决方案1】:

经过大量的战斗,我找到了解决方案;-)

对我来说,在这里发表的评论非常有用:https://registry.hub.docker.com/_/postgres/ from "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

希望对你有帮助!

【讨论】:

  • 我还会在 .sql 文件的末尾追加一个新行,因为如果没有尾随空行 (sed -e '$a\' -i "$DB_DUMP_LOCATION"),则不会执行最后一条语句。
  • 谢谢@NicolaFerraro 我不知道,我将您的建议添加到回复中
  • 这对我不起作用。容器退出,无法访问数据库
  • 当您不提供有关该问题的任何额外信息时,@HananShteingart 很难为您提供帮助。尝试记录各种步骤以调试崩溃的位置并检查您的 sql-dump。对于我和其他工作的人来说,这意味着其他事情应该是你的问题。
  • 好吧,首先,在 docker build time 中复制整个数据集不太方便,因为当您有新数据时无法使用该映像。此外,“将构建上下文发送到 Docker 守护进程......”需要很长时间(我有 30GB 数据集)
【解决方案2】:

对于那些想要在第一次运行时初始化具有数百万条记录的 PostgreSQL 数据库的人。

使用 *.sql 转储导入

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

使用二进制转储导入

解决方案是制作二进制 PostgreSQL 转储并使用 shell scripts initialization support。 然后同一个数据库在大约 500 毫秒内初始化而不是 1 分钟。

1.创建名为“my-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

【讨论】:

  • 这个简单干净。谢谢!
  • docker-entrypoint-initdb.d 中的 .sql 文件将自行运行(至少一次)。来自 docker postgres 映像的注释:“警告:只有在使用空数据目录启动容器时才会运行 /docker-entrypoint-initdb.d 中的脚本;任何预先存在的数据库在容器启动时都不会受到影响。一个常见问题是,如果您的 /docker-entrypoint-initdb.d 脚本之一失败(这将导致入口点脚本退出)并且您的协调器使用已初始化的数据目录重新启动容器,它将不会继续使用您的脚本。 "
  • docker-entrypoint-initdb.d/01-restore.sh 中包含|| exit 1 的目标是什么?我对bash的||有很好的理解,但是当我在我的环境中尝试这个答案时,由于exit 1,Postgres容器过早退出。
  • @wgj 命令pg_restore 没有用0 退出代码完成 - 它失败了。我想可能会有类似|| exit $? 的东西将退出代码转发到容器。
  • @PetrÚjezdský 当然,但是具有非零返回码与显式调用exit 1 不同;当使用|| exit 1 时,容器会在 Postgres 开始接受连接之前突然终止。我在问预期的目标是什么,因为我不清楚你为什么想要|| exit 1。从我的角度来看,它就像一个错误。谢谢你。 :)
【解决方案3】:

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

例如(假设您的脚本在 /tmp/my_scripts)

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

【讨论】:

  • 谢谢!我会说这真的取决于你的用例。但是对于生产环境,你最好从不同的位置安装,比如 NFS 或主机?否则,一旦您销毁容器,您的所有数据都会被清除。
  • 自从我写了我的新手评论后,我了解到/意识到 Postgres 仅在“安装”Postgres 时才会读取“入口点”目录,这(当然)每个容器只发生一次。启动或停止容器不会使其 Postgres 再次扫描目录。 (我现在要删除那个误导性的评论。:)
【解决方案4】:

还有另一个选项utilises Flocker

Flocker 是一个容器数据卷管理器,旨在让 PostgreSQL 等数据库在生产环境中轻松运行在容器中。在生产环境中运行数据库时,您必须考虑从主机故障中恢复等问题。 Flocker 提供了用于管理跨机器集群的数据量的工具,就像您在生产环境中所拥有的一样。例如,由于在主机之间调度 Postgres 容器以响应服务器故障,Flocker 可以同时在主机之间自动移动其关联的数据量。这意味着当您的 Postgres 容器在新主机上启动时,它拥有自己的数据。此操作可以使用 Flocker API 或 CLI 手动完成,也可以通过 Flocker 集成的容器编排工具自动完成,例如 Docker Swarm、Kubernetes 或 Mesos。

【讨论】:

    【解决方案5】:

    我遵循与@damoiser 相同的解决方案,唯一不同的情况是我想导入所有转储数据。

    请按照下面的解决方案。(我没有做过任何检查)

    Dockerfile

    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! ***"
    

    然后您可以将图像构建为

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

    【讨论】:

      【解决方案6】:

      我的解决方案受到 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://stackoverflow.com/a/59303962/4440427。应该注意的是,该解决方案是从二进制转储而不是从 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 获得

      【讨论】:

        【解决方案7】:

        我能够通过在 /etc/init.d/postgresql 的 docker 文件中预先挂起运行命令来加载数据。我的 docker 文件有以下对我有用的行:

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

        【讨论】:

        • 尝试使用 docker image postgres:9.6 我收到以下错误:No PostgreSQL clusters exist; see "man pg_createcluster" ... (warning).
        【解决方案8】:

        对于 E2E 测试,我们需要一个结构和数据已经保存在 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 镜像中。

        我们已经根据这篇文章,但是去掉了多级: Creating Fast, Lightweight Testing Databases in Docker

        编辑:在 9.4 版本中,alpine 现在不能工作,因为它不能 运行 database_restore.sh 脚本。使用版本 9.4.24-alpine

        【讨论】:

          【解决方案9】:

          我的目标是拥有一个包含数据库的图像 - i。 e.每次我做 docker rundocker-compose up 时都节省时间来重建它。

          我们只需要设法将exec "$@"docker-entrypoint.sh 中取出。所以我添加到我的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
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2020-01-28
            • 2017-03-11
            • 2021-06-03
            • 1970-01-01
            • 1970-01-01
            • 2018-10-31
            • 1970-01-01
            • 2016-04-14
            相关资源
            最近更新 更多