使用 Mastodon 搭建个人信息平台:前篇

2022-03-31 08:01:56 浏览数 (1)

本篇文章是使用 Mastodon 搭建个人信息平台的第一篇内容,我将聊聊在容器环境中搭建 Mastodon 的一些细节。

同时,这篇文章或许你能够找到的为数不多的关于如何在容器环境中搭建和优化 Mastodon 服务的内容。

写在前面

随着折腾的系统越来越多,我开始期望有一个地方能够将这些系统中的消息进行集中的呈现,让我能够快速清晰的了解到有什么有趣的新鲜的、重要的事情发生了,以及让我能够通过更简单的方式对已有系统中的数据进行快速的查询,以及记录一些突然出现的想法。

我认为以时间轴为线索的 Feed 流形式的信息展示,配合和各种“虚拟应用”和 Bot 的对话方式或许能够解决我这个阶段的诉求。交互简单直接、交互操作层级也浅,在多数查询和记录场景下,我只需要输入内容,按下回车就能拿到我想要的数据,而不必打开具体的应用的页面,然后再一步一步、一步一步的操作。

简单的交互示意图

在以往工作和生活中,其实多多少少也有使用过一些包含了交互或者功能和我诉求有交集的工具,比如:在新浪云工作使用的 TeamToy、在淘宝时使用的 Redmine 和阿里门户、美团时使用的大象、之后使用的 Slack、企业微信、学城等等。

十年前在新浪云使用的 TeamToy

不过这类方案多数都是内部或者 SaaS 化的方案,在个人使用场景下,尤其是结合各种 HomeLab 系统,我更希望它是一个私有化的服务。

对于新增“实体”,我比较克制,所以所以在此之前的探索过程中,我对 Phabricator、Confluence 、WordPress、Dokuwiki、Outline 等各种我之前比较熟悉的系统都进行过了一些调查和简单的二次开发,发现虽然能够解决一部分问题,但是交互和体验上总感觉不是那么舒服。因为这些工具或多或少都是基于协作出发,或者基于内容整理出发,而不是信息汇聚和展示。我需要一个即使一个人使用也能很爽的方案。

于是,我开始彻底尝试切换思路,寻找一个上文中提到的,以时间轴为信息展示线索,能够和工具中的 Bot 互动,来记录我的想法、将各种我关注的事件实时汇聚到工具中,能够以简单的命令和方法查询各种系统中已有的数据。最终,我选择了 Mastodon,一个两年前我就已经折腾过一阵的 “Twitter / Weibo Like” 的产品。

已经成长到两万颗星星的 Mastodon

在开始折腾之前,我们先来聊聊它的技术架构。

技术架构

Mastodon 的技术架构属于比较经典的 Web 架构,主要的功能组件有:前端应用(React SPA)、应用接口(Ruby Rails6)、推送服务(Node Express WS)、后台任务(Ruby Sidekiq)、缓存和队列(Redis)、数据库(Postgres),以及可选的全文索引(Elasticsearch 7)构成。

Mastodon 应用架构中的主要构成

除此之外,支持使用匿名网络通讯的方式和互联网上其他不同的社区实例通讯,交换社区已发布内容,来完成其分布式社区的构想。不过这个功能不在本文范围之内,而且非常简单,就不啰嗦展开了。

基础服务准备

在折腾应用之前,我们先完成应用对于基础服务的依赖设施的搭建。先来聊聊网络规划。

搭建应用网关,进行网络规划

和以往应用一样,我们使用 Traefik 作为服务应用网关,让应用可以使用服务注册的方式动态地接入 Traefik。并且使用 Traefik 提供 SSL 装载、基础的 SSO 鉴权等。

如果你还不了解 Traefik,可以阅读之前的内容进行学习和了解。

Mastodon 所在主机网络规划

我希望 Mastodon 各个组件在能够通讯、必要的服务能够使用 Traefik 进行服务注册,提供 Web 访问的前提下,还能和主机上其他的容器服务在网络层面相互隔离。

出于上面的考虑,我们可以执行命令,创建一个额外的虚拟网卡进行组件之间的通讯打通:

代码语言:javascript复制
docker network create mastodon_networks

搭建数据库:Postgres

官方配置文件中,对于数据库的定义是这样的:

代码语言:javascript复制
version: '3'
services:

  db:
    restart: always
    image: postgres:14-alpine
    shm_size: 256mb
    networks:
      - internal_network
    healthcheck:
      test: ["CMD", "pg_isready", "-U", "postgres"]
    volumes:
      - ./postgres14:/var/lib/postgresql/data
    environment:
      - "POSTGRES_HOST_AUTH_METHOD=trust"

虽然也能使用,但是数据库运行之后,我们会收到程序到一些运行警告。

代码语言:javascript复制
********************************************************************************
WARNING: POSTGRES_HOST_AUTH_METHOD has been set to "trust". This will allow
         anyone with access to the Postgres port to access your database without
         a password, even if POSTGRES_PASSWORD is set. See PostgreSQL
         documentation about "trust":
         https://www.postgresql.org/docs/current/auth-trust.html
         In Docker's default configuration, this is effectively any other
         container on the same system.

         It is not recommended to use POSTGRES_HOST_AUTH_METHOD=trust. Replace
         it with "-e POSTGRES_PASSWORD=password" instead to set a password in
         "docker run".
********************************************************************************

在应用运行过程中,数据库终端会不断地积累一些请求日志、后台任务执行结果日志输出,最终会产生一个非常大的应用日志文件。在极端的情况下,甚至可能因此将磁盘占满,影响整台服务器上其他应用的正常运行。

所以,我结合实际状况,我对上面的配置做了一些简单调整:

代码语言:javascript复制
version: '3'
services:

  db:
    restart: always
    image: postgres:14-alpine
    shm_size: 256mb
    networks:
      - mastodon_networks
    healthcheck:
      test: ["CMD", "pg_isready", "-U", "postgres"]
      interval: 15s
      retries: 12
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - /etc/timezone:/etc/timezone:ro
      - ./data:/var/lib/postgresql/data
    environment:
      - "POSTGRES_DB=mastodon"
      - "POSTGRES_USER=mastodon"
      - "POSTGRES_PASSWORD=mastodon"
    logging:
      driver: "json-file"
      options:
        max-size: "10m"

networks:
  mastodon_networks:
    external: true

将上面的内容保存到 postgres 目录的 docker-compose.yml 文件中之后,我们使用 docker-compose up -d 启动服务,稍等片刻,使用 docker-compose ps 查看应用,可以看到服务运行正常。

代码语言:javascript复制
# docker-compose ps
NAME                COMMAND                  SERVICE             STATUS              PORTS
postgres-db-1       "docker-entrypoint.s…"   db                  running (healthy)   5432/tcp

这部分的配置和代码已经上传至 GitHub,有需要可以自取:https://github.com/soulteary/Home-Network-Note/tree/master/example/mastodon/postgres

搭建缓存和队列服务:Redis

默认的 Redis 启动会在 30秒之后提供服务,对于我们而言有一些久。为了让 Redis 开始提供响应的时间更快,我同样对官方配置中的内容进行了简单的调整:

代码语言:javascript复制
version: '3'
services:

  redis:
    restart: always
    image: redis:6-alpine
    networks:
      - mastodon_networks
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 15s
      retries: 12
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - /etc/timezone:/etc/timezone:ro
      - ./data:/data
    logging:
      driver: "json-file"
      options:
        max-size: "10m"

networks:
  mastodon_networks:
    external: true

将配置保存到 redis 目录的 docker-compose.yml 后,我们使用 docker-compose up -d 启动服务,稍等片刻,使用 docker-compose ps 查看应用,可以看到服务运行正常。

代码语言:javascript复制
# docker-compose ps
NAME                COMMAND                  SERVICE             STATUS              PORTS
redis-redis-1       "docker-entrypoint.s…"   redis               running (healthy)   6379/tcp

这部分的配置和代码也已经上传至 GitHub,有需要可以自取:https://github.com/soulteary/Home-Network-Note/tree/master/example/mastodon/redis

搭建全文检索:Elasticsearch

这个组件对于 Mastodon 是可选的,有几个情况下你可能不需要使用 ES:

  • 你的机器资源非常紧张,启用 ES 将额外的占用 500MB~1GB 的内存
  • 你的站点内容和用户数并不多
  • 你的搜索次数非常有限
  • 你期望使用资源和性能更高的检索方案

在 2018 年的 PG CONF EU 上,Oleg Bartunov 曾经做过一个分享,关于使用 Postgres 在全文检索场景的使用,感兴趣可以自行了解。

当然,出于对官方选择的尊重,我们还是简单展开一下 ES 的搭建和使用。同样基于官方配置进行简单调整,可以完成一个新的基础编排文件:

代码语言:javascript复制
version: '3'
services:

  es:
    restart: always
    container_name: es-mastodon
    image: docker.elastic.co/elasticsearch/elasticsearch-oss:7.10.2
    environment:
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
      - "cluster.name=es-mastodon"
      - "discovery.type=single-node"
      - "bootstrap.memory_lock=true"
    networks:
      - mastodon_networks
    healthcheck:
      test: ["CMD-SHELL", "curl --silent --fail localhost:9200/_cluster/health || exit 1"]
      interval: 15s
      retries: 12
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - /etc/timezone:/etc/timezone:ro
      - ./data:/usr/share/elasticsearch/data:rw
    ulimits:
      memlock:
        soft: -1
        hard: -1
    logging:
      driver: "json-file"
      options:
        max-size: "10m"

networks:
  mastodon_networks:
    external: true

不过,如果我们将上面的编排文件保存,并尝试启动服务,会遇到一个经典的问题,目录权限不正确,服务无法启动:

代码语言:javascript复制
"stacktrace": ["org.elasticsearch.bootstrap.StartupException: ElasticsearchException[failed to bind service]; nested: AccessDeniedException[/usr/share/elasticsearch/data/nodes];",
"at org.elasticsearch.bootstrap.Elasticsearch.init(Elasticsearch.java:174) ~[elasticsearch-7.10.2.jar:7.10.2]",
...

ElasticsearchException[failed to bind service]; nested: AccessDeniedException[/usr/share/elasticsearch/data/nodes];
Likely root cause: java.nio.file.AccessDeniedException: /usr/share/elasticsearch/data/nodes
 at java.base/sun.nio.fs.UnixException.translateToIOException(UnixException.java:90)
...

解决问题的方案很简单,我们将数据目录的权限设置为容器内的 ES 进程可操作即可:(强烈不推荐使用简单粗暴的 chmod 777

代码语言:javascript复制
mkdir -p data
chown -R 1000:1000 data
docker-compose down && docker-compose up -d

执行完上述命令,重启容器进程之后,再次使用 docker-compose ps 命令查看应用状况,我们可以看到程序运行正常。

代码语言:javascript复制
# docker-compose ps
NAME                COMMAND                  SERVICE             STATUS              PORTS
es-mastodon         "/tini -- /usr/local…"   es                  running (healthy)   9300/tcp

这部分的配置和代码也已经上传至 GitHub,有需要可以自取:https://github.com/soulteary/Home-Network-Note/tree/master/example/mastodon/elasticsearch

应用搭建

基础服务搭建完毕之后,我们来完成应用的搭建和部署。

应用初始化

为了方便应用初始化,我写了一个简单的编排配置:

代码语言:javascript复制
version: "3"
services:
  web:
    image: tootsuite/mastodon:v3.4.4
    restart: always
    environment:
      - "RAILS_ENV=production"
    command: bash -c "rm -f /mastodon/tmp/pids/server.pid; tail -f /etc/hosts"
    networks:
      - mastodon_networks

networks:
  mastodon_networks:
    external: true

将上面的内容保存为 docker-compose.init.yml,接着先使用 docker-compose up -d 启动一个 Mastodon 安装就绪的容器备用。

在容器启动之后,我们执行下面的命令启动 Mastodon 安装引导程序:

代码语言:javascript复制
docker-compose -f docker-compose.init.yml exec web bundle exec rake mastodon:setup

执行完毕上面的命令,会进入交互式命令行,我们忽略掉所有的警告信息,可以得到类似下面的日志(示例,你可以根据自己的情况调整)

代码语言:javascript复制
Your instance is identified by its domain name. Changing it afterward will break things.
Domain name: hub.lab.com

Single user mode disables registrations and redirects the landing page to your public profile.
Do you want to enable single user mode? yes

Are you using Docker to run Mastodon? Yes

PostgreSQL host: db
PostgreSQL port: 5432
Name of PostgreSQL database: postgres
Name of PostgreSQL user: postgres
Password of PostgreSQL user: 
Database configuration works! 


	

0 人点赞