当前位置: 首页 > news >正文

使用容器提供postgresql RESTful API服务 - Fan

PostgreSQL是一款开源的SQL实现,可以用于关系型数据的存储。PostgREST能够将PostgreSQL数据库直接转换为 RESTful API,允许用户以HTTP方式查询和提交数据。

通常,安装PostgreSQL需要root权限,安装后默认用专用账户(postgres)访问,使用专用的目录(/var/lib/postgresql/)存储数据。服务的配置和启动由系统服务管理器管理,其控制通常需要root权限。全过程涉及多个用户、多个进程,操作较为复杂。

基于apptainer(旧名singularity)的容器化方案允许普通用户在个人目录下独立创建和管理多个数据库实例,能够极大地方便普通用户维护和调试数据库。

本文的目标是利用容器化方案,让普通用户在没有安装PostgreSQL的主机上,在用户目录下独立快速创建和维护一个PostgreSQL数据库,并使用PostgREST使其可以通过RESTful API访问。

容器镜像的制作

在空目录下创建两个文本文件Dockerfile(或apptainer.def)和entrypoint.sh,并clone GitHub仓库pgjwt。

Dockerfile指示了容器构建方法。我们以Debian 13下的postgres 18官方容器(postgres:18-trixie)为基础,从postgrest/postgrest:latest镜像获取postgrest二进制文件。
为了使用PostgREST,我们还需要安装pgjwt来为PostgreSQL提供jwt扩展支持。
此外,我们还将指定必要的环境变量,来为后续的命令提供方便。

# 基础镜像
FROM postgres:18-trixie# 安装 make
RUN apt-get update \&& apt-get install -y build-essential \&& rm -rf /var/lib/apt/lists/*# 提取 postgrest
COPY --from=postgrest/postgrest:latest /bin/postgrest /usr/local/bin/postgrest# 安装 pgjwt
COPY pgjwt /usr/src/pgjwt
RUN make -C /usr/src/pgjwt install# 设置环境变量
ENV PGDATA=/var/lib/postgresql
ENV PGHOST=/var/lib/postgresql/socket
ENV PGDATABASE=main# 设置入口脚本
COPY entrypoint.sh /usr/local/bin/entrypoint.sh
RUN chmod +x /usr/local/bin/entrypoint.sh
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]# 设置默认命令(可选)
CMD ["psql"]

Dockerfile中还指定了数据库文件目录PGDATA、创建socket目录PGHOST,并将默认的database名设置为main。此外还指定了入口脚本entrypoint.sh,以及默认的启动命令psql

如果不想使用docker,也可以直接通过apptainer.def进行镜像定义(点击展开)
Bootstrap: docker
From: postgrest/postgrest:latest
Stage: postgrest_binBootstrap: docker
From: postgres:18-trixie
Stage: main_image%files from postgrest_bin/bin/postgrest /usr/local/bin/postgrest%filespgjwt /usr/src/pgjwtentrypoint.sh /usr/local/bin/entrypoint.sh%environmentexport PGDATA=/var/lib/postgresqlexport PGHOST=/var/lib/postgresql/socketexport PGDATABASE=main%postapt-get update \&& apt-get install -y build-essential \&& rm -rf /var/lib/apt/lists/*make -C /usr/src/pgjwt installchmod +x /usr/local/bin/entrypoint.sh%runscriptif [ $# -eq 0 ]; thenset -- "psql"fiexec /usr/local/bin/entrypoint.sh "$@"

entrypoint.sh是容器运行时会调用的脚本,用于在执行用户命令前(按需)初始化数据库并启动postgres服务,并在命令结束后终止postgres服务。

#!/bin/bash
set -eif [ ! -f "$PGDATA/PG_VERSION" ]; then # 如果数据文件不存在initdb >&2 # 初始化数据库文件mkdir -p "$PGHOST" # 创建socket目录sed -i "s/^[# ]*listen_addresses\s*=.*/listen_addresses = ''/" "$PGDATA/postgresql.conf" # 禁用端口监听,只通过socket方式访问echo "unix_socket_directories = '$PGHOST'" >> "$PGDATA/postgresql.conf" # 指定socket目录pg_ctl start >&2 # 启动数据库服务createdb # 创建数据库
elsepg_ctl start >&2 # 启动数据库服务
fitrap 'pg_ctl stop >&2' EXIT # 退出时停止数据库服务"$@" # 执行传入命令

用docker构建容器,之后在apptainer中拉取为保存为sif镜像文件:

# 利用 docker 构建
docker build -t pg18-postgrest:latest .
apptainer build pg18-postgrest.sif docker-daemon://pg18-postgrest:latest
# 或者直接从 apptainer.def 文件创建镜像
apptainer build pg18-postgrest.sif apptainer.def

这样就制作完成了一个包含PostgreSQL和PostgREST的镜像pg18-postgrest.sif

容器镜像的使用

使用容器镜像部署PostgreSQL数据库

我们构建镜像时设置了数据库文件(在镜像内的)目录PGDATA = /var/lib/postgresql,为了让其中的内容在镜像运行结束后依然能保存在硬盘上,我们需要通过--bind将本地目录“挂载”到镜像的/var/lib/postgresql目录下。

利用singularity run启动镜像并运行命令(如psql),入口脚本会自动以挂载的目录为数据库数据目录启动postgresql,如果检测到数据目录为空时还会自动初始化。

singularity run --bind /path/to/database/:/var/lib/postgresql pg18-postgrest.sif psql

运行上述命令后会进入psql命令行界面,退出后可以看到/path/to/database/下自动创建了postgresql相关数据文件。

之后再次运行,可以在psql中对位于/path/to/database/的数据库进行操作,例如查询、插入或删除表和数据。

使用容器镜像运行PostgREST

PostgREST利用数据库中的结构约束和权限决定API端点和操作。

graph RLA[Web Client] -->|Request| B[PostgREST]B -->|Response| AB -->|Query| C[PostgreSQL]C -->|Result| B

PostgREST的认证涉及数据库、PostgREST和web客户端三方,PostgREST同时作为数据库客户端和web服务端,需要在两套机制之间进行转换,较为复杂。

具体而言,PostgREST利用配置中给定的用户名、密码和登录方式访问数据库,通过jwt验证web客户端所声称的身份,验证通过后以其声称的身份对数据库数据进行读取或修改。其中任何一个环节权限校验失败都会导致错误。

jwt(JSON Web Tokens)是一段纯文本字符串,由三部分组成,第一、二部分是base64编码的元数据(指定签名算法)和数据(即web客户端“声称”的身份),第三部分是利用一个密钥字符串对前两部分签名后的结果,持有密钥者可以根据密钥字符串验证jwt的合法性,以拒绝第三部分签名不合要求的访问请求。

PostgREST本身只会校验jwt的合法性,不带有认证用户或签发token的能力。为了让web客户端能够向PostgREST提供正确签名的jwt,有两种手段:

  • 一是向可信的web客户端分发密钥字符串,由客户端自行签名构造jwt。一旦如此,web客户端就可以任意“声称”身份访问数据库。
  • 二是由数据库根据web客户端传递的数据(如用户名、密码)签发jwt,web客户端只能以数据库签发的身份访问数据库。

第二种手段允许我们向不同的web客户端签发不同权限的jwt,以实现差异化授权。为此,需要在PostgreSQL数据库中定义一个认证函数login

下面的sql展示了一套适用于PostgREST的数据库定义:

-- setup.sql
-- 启用 pgjwt 扩展
CREATE EXTENSION IF NOT EXISTS pgcrypto;
CREATE EXTENSION IF NOT EXISTS pgjwt;
-- 创建用户用于postgrest访问
CREATE USER postgrest NOINHERIT LOGIN PASSWORD 'mypassword'; -- 用于postgrest登录,否则postgrest无法连接到数据库
CREATE ROLE web_anon NOLOGIN;
CREATE ROLE web_auth NOLOGIN; -- 允许web客户端声称不同的身份,以实现差异化授权
GRANT web_anon, web_auth TO postgrest; -- 允许postgrest登录后以'web_anon'或'web_auth'的身份执行操作
CREATE SCHEMA data;
-- 认证函数,用于签发jwt
CREATE OR REPLACE FUNCTION data.login(username text, password text)
RETURNS text AS $$
DECLAREsecret text := 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; -- 密钥字符串
BEGIN-- 生产环境中会根据传入的username和password进行认证判断-- 这里略过了认证步骤,对任意访问者签发'web_auth'身份的(永久)令牌-- 生产环境中通常会在令牌payload中包含过期时间字段('exp')RETURN sign(json_build_object('role', 'web_auth'                                   -- 签发的身份--,'exp', extract(epoch from now() + interval '1 hour') -- 过期时间), secret);
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- 创建表格
CREATE TABLE data.users (uid          bigint GENERATED ALWAYS AS IDENTITY primary key,username     text not null,department   text
);
-- 设置访问权限
GRANT USAGE ON SCHEMA data TO web_anon, web_auth;
GRANT execute ON FUNCTION data.login(text, text) TO web_anon; -- 允许普通身份申请jwt
GRANT select ON ALL TABLES IN SCHEMA data TO web_anon;   -- 允许普通身份查询数据
GRANT select, insert, update, delete ON ALL TABLES IN SCHEMA data TO web_auth; -- 允许'web_auth'身份修改数据
-- 导入数据,这里用3条测试记录作为示例
INSERT INTO data.users (username, department) VALUES
('Alice', 'Engineering'),
('Bob', 'Marketing'),
('Charlie', 'Human Resources');

将上述内容导入PostgreSQL:

cat setup.sql | singularity run --bind /path/to/database/:/var/lib/postgresql pg18-postgrest.sif psql -f -

新建一个postgrest.conf文件:

db-uri = "postgres://postgrest:mypassword@/main?host=/var/lib/postgresql/socket"
db-schemas = "data"
db-anon-role = "web_anon"
jwt-secret = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
server-port = 3000
log-level = "info"

这里的db-schemas是允许访问的schema,db-anon-role指定了没有传递jwt时访问数据库时的默认身份。PostgREST会通过jwt-secret密钥校验请求传递的jwt。

之后即可通过PostgREST启动RESTful API:

singularity run --bind /path/to/database/:/var/lib/postgresql pg18-postgrest.sif postgrest postgrest.conf

访问postgrest RESTful API

可以通过任意http客户端访问PostgREST RESTful API,这里以命令行客户端curl为例:

GET方法获取数据

curl "http://localhost:3000/users"

返回

[{"uid":1,"username":"Alice","department":"Engineering"},{"uid":2,"username":"Bob","department":"Marketing"},{"uid":3,"username":"Charlie","department":"Human Resources"}]

从login端点获取jwt

curl "http://localhost:3000/rpc/login?username=guest&password=123456"

返回

"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIiA6ICJ3ZWJfYXV0aCJ9.WmyKgKms-SJ9unFwSpOzqGLFVVAN6iO9sKYR2hh_KKQ"

利用签发的jwt,我们可以以web_auth的身份访问数据库,并通过POST方法插入行、PATCH方法修改行、DELETE方法删除行。

# POST: 插入行
curl -X POST "http://localhost:3000/users" \-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIiA6ICJ3ZWJfYXV0aCJ9.WmyKgKms-SJ9unFwSpOzqGLFVVAN6iO9sKYR2hh_KKQ" \-H "Content-Type: application/json" \-d '{"username":"Dave","department":"Engineering"}'
# PATCH: 修改行
curl -X PATCH "http://localhost:3000/users?username=eq.Alice" \-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIiA6ICJ3ZWJfYXV0aCJ9.WmyKgKms-SJ9unFwSpOzqGLFVVAN6iO9sKYR2hh_KKQ" \-H "Content-Type: application/json" \-d '{"department":"Marketing"}'
# DELETE: 删除行
curl -X DELETE 'http://localhost:3000/users?username=eq.Bob' \-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIiA6ICJ3ZWJfYXV0aCJ9.WmyKgKms-SJ9unFwSpOzqGLFVVAN6iO9sKYR2hh_KKQ'

再次查询数据

curl "http://localhost:3000/users"

返回

[{"uid":3,"username":"Charlie","department":"Human Resources"},{"uid":4,"username":"Dave","department":"Engineering"},{"uid":1,"username":"Alice","department":"Marketing"}]
http://www.jsqmd.com/news/896542/

相关文章:

  • 如何用novelWriter提升小说创作效率:开源结构化写作工具终极指南
  • 毕业答辩高效通关:用百考通AI 30分钟搞定专业答辩PPT
  • 构建容错性强的AI应用时如何借助Taotoken的路由与容灾能力
  • harness与hermes-agent的区别
  • STM32F103定时器入门:从CubeMX配置到代码实战,5分钟搞懂TIM2时钟源设置
  • 别再死记硬背了!用这3个真实项目案例,帮你彻底搞懂PERT图、关键路径和浮动时间
  • 别再手动导数据了!用SeaTunnel 2.3.1把Hive数据自动同步到StarRocks(附完整配置文件)
  • 告别手动测试!用CPAL脚本的IL函数实现CAN总线自动化故障注入
  • 如何用Python轻松实现本地大语言模型推理?llama-cpp-python实战指南
  • 【他山之石】《蛤蟆先生去看心理医生》导读
  • VSCode插件---Code Runner:从零到一,打造你的多语言代码执行中心
  • 国产化浪潮下:基于华为欧拉与麒麟系统构建ARM原生Harbor镜像仓库
  • 2026·牛客网Java后端高频面试题精选(收藏这一篇就够了)
  • ECDICT:为什么说这是开发者必备的免费英汉词典数据库?
  • UML/OCL模型到Z/PVS形式化验证:提升CPS设计可靠性的工程实践
  • COMSOL多物理场耦合建模:一个‘热源加倍’的常见错误与5个耦合设置检查清单
  • Squirrel-RIFE:高性能视频补帧解决方案,让每一帧都流畅如丝
  • 嵌入式实时仿真平台:赋能智能配电网的现场级数字孪生
  • novel-downloader:如何用开源工具永久保存你的数字阅读资产?
  • Taotoken多模型广场如何帮助开发者进行成本与效果选型
  • DW02KA 高精度内置MOSFET锂电池保护电路
  • 超市机器人连续跑一个月不迷路?聊聊高仙那篇Lifelong SLAM论文里的‘地图保鲜’秘诀
  • WeChatMsg终极指南:如何完整备份微信聊天记录并永久保存你的数字记忆
  • 微服务架构:API网关与服务发现
  • 硬连线用户空间中断:颠覆传统,实现亚周期级加速器通信
  • 如何在macOS上实现NTFS硬盘的完整读写:终极免费解决方案
  • UE4项目里想给道具加个‘选中光环’?用Post Process Volume五分钟搞定(附免费闪烁材质)
  • 黑客松:从编程比赛到组织创新催化剂的实践指南
  • Axure RP终极汉化指南:3分钟实现中文界面完整教程
  • harness 与 hermes-agent 设计理念和工程取向