第 15 章:Docker 部署
第 15 章:Docker 部署
15.1 Docker 镜像选择
官方镜像
| 镜像 | 说明 | 推荐 |
|---|
postgis/postgis:16-3.4 | PostgreSQL 16 + PostGIS 3.4 | ✅ 推荐 |
postgis/postgis:15-3.3 | PostgreSQL 15 + PostGIS 3.3 | 兼容旧项目 |
postgis/postgis:17-3.5 | PostgreSQL 17 + PostGIS 3.5 | 尝鲜 |
mdillon/postgis | 社区维护(已弃用) | ❌ 不推荐 |
kartoza/postgis | 含额外工具 | 特殊需求 |
镜像标签规范
# 格式: postgis/postgis:{pg_version}-{postgis_version}
postgis/postgis:16-3.4 # 推荐
postgis/postgis:16-3.4-alpine # 轻量版
postgis/postgis:latest # 最新版本(不推荐生产使用)
15.2 快速启动
基础启动
docker run -d \
--name postgis \
-e POSTGRES_USER=gisadmin \
-e POSTGRES_PASSWORD=SecurePass123 \
-e POSTGRES_DB=gisdb \
-p 5432:5432 \
postgis/postgis:16-3.4
带数据持久化
# 使用命名卷(推荐)
docker volume create pgdata
docker run -d \
--name postgis \
-e POSTGRES_PASSWORD=SecurePass123 \
-v pgdata:/var/lib/postgresql/data \
-p 5432:5432 \
postgis/postgis:16-3.4
# 使用宿主机目录
docker run -d \
--name postgis \
-e POSTGRES_PASSWORD=SecurePass123 \
-v /opt/pgdata:/var/lib/postgresql/data \
-p 5432:5432 \
postgis/postgis:16-3.4
连接测试
# 使用 psql 连接
docker exec -it postgis psql -U gisadmin -d gisdb
# 验证 PostGIS
SELECT PostGIS_Full_Version();
15.3 Docker Compose 完整配置
基础 Compose 文件
version: '3.8'
services:
postgis:
image: postgis/postgis:16-3.4
container_name: postgis
environment:
POSTGRES_USER: gisadmin
POSTGRES_PASSWORD: ${PG_PASSWORD:-SecurePass123}
POSTGRES_DB: gisdb
POSTGRES_INITDB_ARGS: "--encoding=UTF-8 --lc-collate=C --lc-ctype=en_US.utf8"
ports:
- "5432:5432"
volumes:
- pgdata:/var/lib/postgresql/data
- ./init:/docker-entrypoint-initdb.d
healthcheck:
test: ["CMD-SHELL", "pg_isready -U gisadmin -d gisdb"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
volumes:
pgdata:
driver: local
初始化脚本
init/01-extensions.sql:
-- 启用 PostGIS 扩展
CREATE EXTENSION IF NOT EXISTS postgis;
CREATE EXTENSION IF NOT EXISTS postgis_topology;
CREATE EXTENSION IF NOT EXISTS postgis_raster;
CREATE EXTENSION IF NOT EXISTS fuzzystrmatch;
-- 创建只读用户
CREATE USER gisreader WITH PASSWORD 'ReadOnlyPass';
GRANT CONNECT ON DATABASE gisdb TO gisreader;
GRANT USAGE ON SCHEMA public TO gisreader;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO gisreader;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO gisreader;
init/02-sample-data.sql:
-- 创建示例表
CREATE TABLE cities (
id SERIAL PRIMARY KEY,
name VARCHAR(100),
province VARCHAR(50),
population INTEGER,
geom GEOMETRY(Point, 4326)
);
CREATE INDEX idx_cities_geom ON cities USING GIST(geom);
INSERT INTO cities (name, province, population, geom) VALUES
('北京', '北京', 21890000, ST_SetSRID(ST_MakePoint(116.4074, 39.9042), 4326)),
('上海', '上海', 24870000, ST_SetSRID(ST_MakePoint(121.4737, 31.2304), 4326));
15.4 生产级 Docker Compose
version: '3.8'
services:
postgis:
image: postgis/postgis:16-3.4
container_name: postgis-production
environment:
POSTGRES_USER: gisadmin
POSTGRES_PASSWORD_FILE: /run/secrets/pg_password
POSTGRES_DB: gisdb
POSTGRES_INITDB_ARGS: "--encoding=UTF-8 --lc-collate=C"
ports:
- "127.0.0.1:5432:5432" # 仅绑定本地
volumes:
- pgdata:/var/lib/postgresql/data
- ./init:/docker-entrypoint-initdb.d
- ./backups:/backups
secrets:
- pg_password
deploy:
resources:
limits:
cpus: '4.0'
memory: 8G
reservations:
cpus: '2.0'
memory: 4G
healthcheck:
test: ["CMD-SHELL", "pg_isready -U gisadmin -d gisdb"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
logging:
driver: "json-file"
options:
max-size: "100m"
max-file: "3"
restart: unless-stopped
pgadmin:
image: dpage/pgadmin4:latest
container_name: pgadmin
environment:
PGADMIN_DEFAULT_EMAIL: [email protected]
PGADMIN_DEFAULT_PASSWORD: admin123
ports:
- "5050:80"
volumes:
- pgadmin_data:/var/lib/pgadmin
depends_on:
postgis:
condition: service_healthy
restart: unless-stopped
secrets:
pg_password:
file: ./secrets/pg_password.txt
volumes:
pgdata:
pgadmin_data:
15.5 PostgreSQL 配置调优
自定义 postgresql.conf
# 创建自定义配置文件
cat > postgresql-custom.conf << 'EOF'
# 内存配置
shared_buffers = 2GB
effective_cache_size = 6GB
work_mem = 256MB
maintenance_work_mem = 1GB
# 连接配置
max_connections = 200
# 查询优化
random_page_cost = 1.1
effective_io_concurrency = 200
max_parallel_workers_per_gather = 4
max_parallel_workers = 8
# WAL 配置
wal_buffers = 64MB
checkpoint_completion_target = 0.9
# 日志配置
log_min_duration_statement = 1000
log_checkpoints = on
log_lock_waits = on
EOF
# 在 Docker Compose 中挂载
services:
postgis:
volumes:
- ./postgresql-custom.conf:/etc/postgresql/postgresql.conf
command: postgres -c config_file=/etc/postgresql/postgresql.conf
15.6 数据备份与恢复
备份脚本
#!/bin/bash
# backup.sh
BACKUP_DIR="/backups"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
CONTAINER="postgis"
# 全量备份
docker exec $CONTAINER pg_dump \
-U gisadmin -d gisdb \
-Fc -Z9 \
-f /tmp/gisdb_${TIMESTAMP}.dump
# 复制到宿主机
docker cp $CONTAINER:/tmp/gisdb_${TIMESTAMP}.dump $BACKUP_DIR/
# 清理容器内临时文件
docker exec $CONTAINER rm /tmp/gisdb_${TIMESTAMP}.dump
# 删除 30 天前的备份
find $BACKUP_DIR -name "*.dump" -mtime +30 -delete
echo "Backup completed: gisdb_${TIMESTAMP}.dump"
恢复
# 恢复全量备份
docker exec -i postgis pg_restore \
-U gisadmin -d gisdb \
--clean --if-exists \
< /backups/gisdb_20260510_120000.dump
# 仅恢复特定表
docker exec -i postgis pg_restore \
-U gisadmin -d gisdb \
--table=cities --table=stores \
< /backups/gisdb_20260510_120000.dump
使用 WAL 进行增量备份
# docker-compose.yml 中启用 WAL 归档
services:
postgis:
environment:
POSTGRES_INITDB_ARGS: "--data-checksums"
command: >
postgres
-c wal_level=replica
-c archive_mode=on
-c archive_command='cp %p /backups/wal/%f'
-c max_wal_senders=3
volumes:
- ./backups/wal:/backups/wal
15.7 主从复制
# docker-compose-replication.yml
version: '3.8'
services:
postgis-primary:
image: postgis/postgis:16-3.4
container_name: postgis-primary
environment:
POSTGRES_USER: gisadmin
POSTGRES_PASSWORD: primary_pass
POSTGRES_DB: gisdb
ports:
- "5432:5432"
volumes:
- pg_primary:/var/lib/postgresql/data
command: >
postgres
-c wal_level=replica
-c max_wal_senders=3
-c wal_keep_size=256MB
-c hot_standby=on
healthcheck:
test: ["CMD-SHELL", "pg_isready -U gisadmin -d gisdb"]
interval: 10s
postgis-replica:
image: postgis/postgis:16-3.4
container_name: postgis-replica
environment:
PGUSER: replicator
PGPASSWORD: replica_pass
ports:
- "5433:5432"
volumes:
- pg_replica:/var/lib/postgresql/data
depends_on:
postgis-primary:
condition: service_healthy
command: >
bash -c "
until pg_basebackup -h postgis-primary -D /var/lib/postgresql/data -U replicator -Fp -Xs -P -R; do
echo 'Waiting for primary...'
sleep 5
done
postgres
"
volumes:
pg_primary:
pg_replica:
15.8 导入外部数据到容器
# 复制 Shapefile 到容器
docker cp /data/shapefile.shp postgis:/tmp/
docker cp /data/shapefile.shx postgis:/tmp/
docker cp /data/shapefile.dbf postgis:/tmp/
docker cp /data/shapefile.prj postgis:/tmp/
# 在容器内导入
docker exec postgis shp2pgsql \
-s 4326 -W UTF-8 -I \
/tmp/shapefile.shp public.imported_table \
| docker exec -i postgis psql -U gisadmin -d gisdb
# 直接从宿主机管道导入
shp2pgsql -s 4326 -W UTF-8 -I /data/shapefile.shp public.imported_table \
| docker exec -i postgis psql -U gisadmin -d gisdb
# 导入 CSV
docker cp /data/pois.csv postgis:/tmp/
docker exec -i postgis psql -U gisadmin -d gisdb << 'SQL'
CREATE TEMP TABLE tmp_import (
name TEXT, category TEXT, lat DOUBLE PRECISION, lng DOUBLE PRECISION
);
COPY tmp_import FROM '/tmp/pois.csv' WITH (FORMAT csv, HEADER true);
INSERT INTO pois (name, category, geom)
SELECT name, category, ST_SetSRID(ST_MakePoint(lng, lat), 4326)
FROM tmp_import;
DROP TABLE tmp_import;
SQL
15.9 监控容器健康
健康检查脚本
#!/bin/bash
# healthcheck.sh
# 检查容器状态
docker inspect --format='{{.State.Health.Status}}' postgis
# 检查 PostgreSQL 连接
docker exec postgis pg_isready -U gisadmin -d gisdb
# 检查 PostGIS 功能
docker exec postgis psql -U gisadmin -d gisdb -c "SELECT PostGIS_Version();"
# 检查磁盘使用
docker exec postgis du -sh /var/lib/postgresql/data
# 检查活跃连接数
docker exec postgis psql -U gisadmin -d gisdb -c \
"SELECT count(*) FROM pg_stat_activity WHERE state = 'active';"
Prometheus + Grafana 监控
# docker-compose-monitoring.yml
services:
postgres-exporter:
image: prometheuscommunity/postgres-exporter
container_name: pg-exporter
environment:
DATA_SOURCE_NAME: "postgresql://gisadmin:SecurePass123@postgis:5432/gisdb?sslmode=disable"
ports:
- "9187:9187"
depends_on:
- postgis
prometheus:
image: prom/prometheus
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
ports:
- "9090:9090"
grafana:
image: grafana/grafana
ports:
- "3000:3000"
volumes:
- grafana_data:/var/lib/grafana
15.10 常见部署问题
| 问题 | 原因 | 解决方案 |
|---|
| 启动慢 | 数据目录未持久化,每次重新初始化 | 使用 volume 挂载数据 |
| 连接被拒 | 端口绑定问题 | 检查 -p 127.0.0.1:5432:5432 |
| 权限错误 | SELinux 或文件权限 | 使用命名卷而非绑定挂载 |
| 内存不足 | PostgreSQL 默认配置太小 | 调整 shared_buffers 等参数 |
| 磁盘满 | WAL 或数据增长 | 设置 WAL 归档和监控 |
| 编码错误 | 默认编码非 UTF-8 | POSTGRES_INITDB_ARGS: "--encoding=UTF-8" |
# 调试启动问题
docker logs postgis 2>&1 | head -50
# 进入容器排查
docker exec -it postgis bash
# 检查 PostgreSQL 进程
docker exec postgis ps aux | grep postgres
# 检查磁盘空间
docker system df
docker exec postgis df -h /var/lib/postgresql/data
15.11 本章小结
| 要点 | 说明 |
|---|
| 镜像选择 | postgis/postgis:16-3.4 |
| 数据持久化 | 必须使用 volume |
| 配置优化 | 自定义 postgresql.conf |
| 备份策略 | pg_dump + WAL 归档 |
| 安全 | 使用 secrets 管理密码 |
| 监控 | healthcheck + Prometheus |
扩展阅读