安装 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
创建对应的文件夹
mkdir -p /opt/serves/nginx/app mkdir -p /opt/serves/nginx/conf mkdir -p /opt/serves/nginx/logs
创建 bitnami 的 Nginx 容器
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
创建对应的文件夹
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
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
# 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 对应的部分是:
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 命令就是:
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 对应的部分是:
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 长了很多:
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_USERNAME、DB_PASSWORD这些环境变量替换为真实的用户名、密码...
创建 Affine Migration 容器
在执行这一步之前需要等待 Redis 和 Postgres 的状态转变为healthy,可以通过podman ps查看这两个容器的状态
Affine Migration 对应的部分是:
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 命令为:
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 容器创建对应数据库
# 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 对应的部分是:
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 命令为:
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检查运行情况,应该有如下输出:
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 端口
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访问服务器时,直接关闭连接
server { listen 8080 default_server; server_name _; # 匹配任何 Host return 444; # 直接关闭连接(也可以 404 / 503) }
完成上述步骤后,即可在浏览器中通过域名访问 affine 服务
注册 systemd 服务(推荐)
由于 podman 没有守护进程,所以没法开机自启动 pod 或容器,要借助 systemd 实现自启动
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 目录下,没有这个目录就创建一个
mkdir -p ~/.config/systemd/user # 一般都有这个目录 cp *.service ~/.config/systemd/user
重载 systemd 并应用服务
systemctl --user daemon-reload systemctl --user enable --now pod-mypod.service
导出 pod 文件(推荐)
可以导出 pod 文件,这样下次再创建 pod 就不需要这么麻烦了
podman generate kube mypod>mypod.yaml # 导出pod配置,兼容k8s podmam play kube mypod.yaml # 可以从yaml文件重新创建mod