Podman + Nginx + Affine 踩坑教程

安装 Podman

安装 podman 很简单

复制代码
sudo apt install podman

验证安装

复制代码
podman info

创建一个 pod 用于安装 Nginx 和 Affine

复制代码
podman pod create --name mypod -p 80:8080

注意:需要在/etc/sysctl.conf中加入net.ipv4.ip_unprivileged_port_start=80才能创建 80 端口映射

安装 Nginx

创建对应的文件夹

bash 复制代码
mkdir -p /opt/serves/nginx/app
mkdir -p /opt/serves/nginx/conf
mkdir -p /opt/serves/nginx/logs

创建 bitnami 的 Nginx 容器

bash 复制代码
podman run -d --pod mypod --name nginx \
-v /opt/serves/nginx/app:/app \
-v /opt/serves/nginx/conf:/opt/bitnami/nginx/conf \
-v /opt/serves/nginx/logs:/opt/bitnami/nginx/logs \
bitnami/nginx:latest

需要注意的点:

  • --pod mypod将容器加入 pod 中
  • 浏览器访问http://IP检查 Nginx 是否正常运行

安装 Affine

创建对应的文件夹

bash 复制代码
mkdir -p /opt/serves/affine/postgres/pgdata
mkdir -p /opt/serves/affine/storage
mkdir -p /opt/serves/affine/config

通过 podman 命令安装 affine

虽然 Affine 官方提供了 docker-compose.yaml 文件,但是 podman comopse 无法直接将创建的容器加入 pod 中,因此需要用对应的命令逐个创建容器。

这是官方提供的 compose 和 env 文件,我们不使用它们,而是直接把环境变量和参数在命令行中设置好

  • docker-compose.yaml
yaml 复制代码
name: affine
services:
  affine:
    image: ghcr.io/toeverything/affine:${AFFINE_REVISION:-stable}
    container_name: affine_server
    # ports:
    #   - '${PORT:-3010}:3010'
    depends_on:
      redis:
        condition: service_healthy
      postgres:
        condition: service_healthy
      affine_migration:
        condition: service_completed_successfully
    volumes:
      # custom configurations
      - ${UPLOAD_LOCATION}:/root/.affine/storage
      - ${CONFIG_LOCATION}:/root/.affine/config
    env_file:
      - .env
    environment:
      - REDIS_SERVER_HOST=redis
      - DATABASE_URL=postgresql://${DB_USERNAME}:${DB_PASSWORD}@postgres:5432/${DB_DATABASE:-affine}
      - AFFINE_INDEXER_ENABLED=false
    restart: unless-stopped

  affine_migration:
    image: ghcr.io/toeverything/affine:${AFFINE_REVISION:-stable}
    container_name: affine_migration_job
    volumes:
      # custom configurations
      - ${UPLOAD_LOCATION}:/root/.affine/storage
      - ${CONFIG_LOCATION}:/root/.affine/config
    command: ['sh', '-c', 'node ./scripts/self-host-predeploy.js']
    env_file:
      - .env
    environment:
      - REDIS_SERVER_HOST=redis
      - DATABASE_URL=postgresql://${DB_USERNAME}:${DB_PASSWORD}@postgres:5432/${DB_DATABASE:-affine}
      - AFFINE_INDEXER_ENABLED=false
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy

  redis:
    image: redis
    container_name: affine_redis
    healthcheck:
      test: ['CMD', 'redis-cli', '--raw', 'incr', 'ping']
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped

  postgres:
    image: pgvector/pgvector:pg16
    container_name: affine_postgres
    volumes:
      - ${DB_DATA_LOCATION}:/var/lib/postgresql/data
    environment:
      POSTGRES_USER: ${DB_USERNAME}
      POSTGRES_PASSWORD: ${DB_PASSWORD}
      POSTGRES_DB: ${DB_DATABASE:-affine}
      POSTGRES_INITDB_ARGS: '--data-checksums'
      # you better set a password for you database
      # or you may add 'POSTGRES_HOST_AUTH_METHOD=trust' to ignore postgres security policy
      POSTGRES_HOST_AUTH_METHOD: trust
    healthcheck:
      test:
        ['CMD', 'pg_isready', '-U', "${DB_USERNAME}", '-d', "${DB_DATABASE:-affine}"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped
  • .env
ini 复制代码
# select a revision to deploy, available values: stable, beta, canary
AFFINE_REVISION=stable

# set the port for the server container it will expose the server on
PORT=3010

# set the host for the server for outgoing links
# AFFINE_SERVER_HTTPS=true
# AFFINE_SERVER_HOST=affine.yourdomain.com
# or
# AFFINE_SERVER_EXTERNAL_URL=https://affine.yourdomain.com

# position of the database data to persist
DB_DATA_LOCATION=/opt/serves/affine/postgres/pgdata
# position of the upload data(images, files, etc.) to persist
UPLOAD_LOCATION=/opt/serves/affine/storage
# position of the configuration files to persist
CONFIG_LOCATION=/opt/serves/affine/config

# database credentials
DB_USERNAME=affine
DB_PASSWORD=[你的密码]
DB_DATABASE=affine
创建 Redis 容器

通过观察compose文件,可以看出我们需要创建 Redis、Postgres、Affine Migration 和 Affine Server 四个容器,我们从 Redis 开始

Redis 对应的部分是:

yaml 复制代码
  redis:
    image: redis
    container_name: affine_redis
    healthcheck:
      test: ['CMD', 'redis-cli', '--raw', 'incr', 'ping']
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped

转换成 podman 命令就是:

bash 复制代码
podman run -d --pod mypod --name affine_redis \
--restart unless-stopped \
--health-cmd "redis-cli --raw incr ping" \
--health-interval 10s \
--health-timeout 5s \
--health-retries 5 redis

可以看到创建 Redis 容器还是比较简单的,没有什么需要配置的地方

创建 Postgres 容器

不需要等待 Redis 运行,继续创建 Postgres 容器

Postgres 对应的部分是:

yaml 复制代码
  postgres:
    image: pgvector/pgvector:pg16
    container_name: affine_postgres
    volumes:
      - ${DB_DATA_LOCATION}:/var/lib/postgresql/data
    environment:
      POSTGRES_USER: ${DB_USERNAME}
      POSTGRES_PASSWORD: ${DB_PASSWORD}
      POSTGRES_DB: ${DB_DATABASE:-affine}
      POSTGRES_INITDB_ARGS: '--data-checksums'
      # you better set a password for you database
      # or you may add 'POSTGRES_HOST_AUTH_METHOD=trust' to ignore postgres security policy
      POSTGRES_HOST_AUTH_METHOD: trust
    healthcheck:
      test:
        ['CMD', 'pg_isready', '-U', "${DB_USERNAME}", '-d', "${DB_DATABASE:-affine}"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped

转换后的 podman 命令明显比 Redis 长了很多:

bash 复制代码
podman run -d --pod mypod --name affine_postgres \                                                           --restart unless-stopped \
  -v /opt/serves/affine/postgres/pgdata:/var/lib/postgresql/data \
  -e POSTGRES_USER=affine \
  -e POSTGRES_PASSWORD=[你的密码] \
  -e POSTGRES_DB=affine \
  -e POSTGRES_INITDB_ARGS='--data-checksums' \
  -e POSTGRES_HOST_AUTH_METHOD=trust \
  --health-cmd "pg_isready -U affine -d affine" \
  --health-interval 10s --health-timeout 5s --health-retries 5 \
  pgvector/pgvector:pg16

需要注意的地方:

  • DB_USERNAMEDB_PASSWORD这些环境变量替换为真实的用户名、密码...
创建 Affine Migration 容器

在执行这一步之前需要等待 Redis 和 Postgres 的状态转变为healthy,可以通过podman ps查看这两个容器的状态

Affine Migration 对应的部分是:

yaml 复制代码
  affine_migration:
    image: ghcr.io/toeverything/affine:${AFFINE_REVISION:-stable}
    container_name: affine_migration_job
    volumes:
      # custom configurations
      - ${UPLOAD_LOCATION}:/root/.affine/storage
      - ${CONFIG_LOCATION}:/root/.affine/config
    command: ['sh', '-c', 'node ./scripts/self-host-predeploy.js']
    env_file:
      - .env
    environment:
      - REDIS_SERVER_HOST=redis
      - DATABASE_URL=postgresql://${DB_USERNAME}:${DB_PASSWORD}@postgres:5432/${DB_DATABASE:-affine}
      - AFFINE_INDEXER_ENABLED=false
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy

转换后的 podman 命令为:

bash 复制代码
podman run --rm --pod mypod --name affine_migration_job \
  -v /opt/serves/affine/storage:/root/.affine/storage \
  -v /opt/serves/affine/config:/root/.affine/config \
  -e REDIS_SERVER_HOST=localhost \
  -e DATABASE_URL=postgresql://affine:[密码]@affine_postgres:5432/$affine \
  -e AFFINE_INDEXER_ENABLED=false \
  ghcr.io/toeverything/affine:stable \
  sh -c 'node ./scripts/self-host-predeploy.js'

这其实是一个数据迁移步骤,执行完毕后用podman ps也不会看到相关容器,不必担心,需要注意的是:

  • 由于所有容器都在一个 pod 中,REDIS_SERVER_HOST直接设置为localhost即可
  • 如果出现User was denied access on the database错误,进入 postgres 容器创建对应数据库
bash 复制代码
# 1. 进入容器
podman exec -it affine_postgres psql -U postgres

# 2. 确认用户是否存在
\du

# 3. 如果没有 affine 用户,请创建:
CREATE USER affine WITH PASSWORD '19970614yao';

# 4. 创建数据库并授权
CREATE DATABASE affine OWNER affine;
GRANT ALL PRIVILEGES ON DATABASE affine TO affine;

# 5. 刷新权限
FLUSH PRIVILEGES;

# 6. 退出数据库后重新运行创建affine_migration_job容器即可
创建 Affine Server 容器

终于到了部署 Affine 的最后一步

Affine Migration 对应的部分是:

yaml 复制代码
  affine:
    image: ghcr.io/toeverything/affine:${AFFINE_REVISION:-stable}
    container_name: affine_server
    ports:
      - '${PORT:-3010}:3010'
    depends_on:
      redis:
        condition: service_healthy
      postgres:
        condition: service_healthy
      affine_migration:
        condition: service_completed_successfully
    volumes:
      # custom configurations
      - ${UPLOAD_LOCATION}:/root/.affine/storage
      - ${CONFIG_LOCATION}:/root/.affine/config
    env_file:
      - .env
    environment:
      - REDIS_SERVER_HOST=redis
      - DATABASE_URL=postgresql://${DB_USERNAME}:${DB_PASSWORD}@postgres:5432/${DB_DATABASE:-affine}
      - AFFINE_INDEXER_ENABLED=false
    restart: unless-stopped

转换后的 podman 命令为:

bash 复制代码
podman run -d --pod mypod --name affine_server \
  --restart unless-stopped \
  -v /opt/serves/affine/storage:/root/.affine/storage \
  -v /opt/serves/affine/config:/root/.affine/config \
  -e REDIS_SERVER_HOST=localhost \
  -e DATABASE_URL=postgresql://affine:[密码]@localhost:5432/affine \
  -e AFFINE_INDEXER_ENABLED=false \
  ghcr.io/toeverything/affine:stable

运行podman logs affine_server检查运行情况,应该有如下输出:

bash 复制代码
LOG [NestApplication] Nest application successfully started
LOG [WorkspaceResolver] AFFiNE Server is running in [selfhosted] mode
LOG [WorkspaceResolver] Listening on http://0.0.0.0:3010
LOG [WorkspaceResolver] And the public server should be recognized as http://localhost:3010

要注意的是:

  • 由于 pod 统一映射了端口,不需要再设置端口映射

配置 Nginx 反向代理

终于我们完成了 Affine 的配置,接下来只要再配置 Nginx 反向代理让我们能访问 Affine 服务就行了

/opt/serves/nginx/conf/server_blocks下新建一个affine.conf文件和一个default.conf文件

  • affine.conf
    用于把访问affine.your.domain的流量代理至 Affine 监听的 3010 端口
nginx 复制代码
server {
    listen 8080;  # 映射的是pod外的80端口
    server_name affine.your.domain;  # 只有通过这个域名访问的流量才会被代理至Affine
    access_log /opt/bitnami/nginx/logs/affine.log;
    error_log /opt/bitnami/nginx/logs/affine.log;

    location / {
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header HOST $http_host;
        proxy_set_header X-NginX-Proxy true;

        proxy_pass http://localhost:3010;  # 代理至 Affine监听的3010端口
        proxy_redirect off;
    }
}
  • default.conf
    用于屏蔽域名不匹配的流量(可选)
    当不是通过affine.your.domain访问服务器时,直接关闭连接
nginx 复制代码
server {
    listen 8080 default_server;
    server_name _;              # 匹配任何 Host
    return 444;                 # 直接关闭连接(也可以 404 / 503)
}

完成上述步骤后,即可在浏览器中通过域名访问 affine 服务

注册 systemd 服务(推荐)

由于 podman 没有守护进程,所以没法开机自启动 pod 或容器,要借助 systemd 实现自启动

bash 复制代码
podman generate systemd --name mypod --new --files

# 会生成下面这些文件
/opt/serves/container-doocs.service
/opt/serves/container-affine_redis.service
/opt/serves/container-affine_postgres.service
/opt/serves/pod-mypod.service
/opt/serves/container-affine_server.service
/opt/serves/container-nginx.service

把这些文件复制到用户的 systemd 目录下,没有这个目录就创建一个

bash 复制代码
mkdir -p ~/.config/systemd/user  # 一般都有这个目录
cp *.service ~/.config/systemd/user

重载 systemd 并应用服务

bash 复制代码
systemctl --user daemon-reload
systemctl --user enable --now pod-mypod.service

导出 pod 文件(推荐)

可以导出 pod 文件,这样下次再创建 pod 就不需要这么麻烦了

bash 复制代码
podman generate kube mypod>mypod.yaml # 导出pod配置,兼容k8s
podmam play kube mypod.yaml # 可以从yaml文件重新创建mod
相关推荐
科技树支点3 小时前
无GC的Java创新设计思路:作用域引用式自动内存管理
java·python·go·web·编程语言·编译器
奋斗的老史7 小时前
25年Docker镜像无法下载的四种对策
docker·容器·eureka
chillxiaohan7 小时前
Docker学习记录
学习·docker·容器
柯南二号8 小时前
【后端】Docker 常用命令详解
服务器·nginx·docker·容器
新鲜萝卜皮8 小时前
容器内运行的进程,在宿主机的top命令中可以显示吗?
容器
容器魔方10 小时前
Karmada v1.15 版本发布!多模板工作负载资源感知能力增强
云原生·容器·云计算
容器魔方11 小时前
全栈AI驱动!华为云云容器引擎CCE智能助手焕新升级
云原生·容器·云计算
13线14 小时前
Windows+Docker一键部署CozeStudio私有化,保姆级
docker·容器·开源
0wioiw014 小时前
Docker(②创造nginx容器)
docker·容器
菜鸟IT胡14 小时前
docker更新jar包,懒人执行脚本
运维·docker·容器