使用Kubernetes部署Mastodon

发布于 2022-12-17  906 次阅读


Mastodon是一个基于ActivityPub协议的联邦式微博客类软件,可以搭建自己的实例并与其他实例互相连接,每个实例都可以有不同的主题要求或规定,此外也可以和其他使用ActivityPub协议的站点,诸如PleromaMisskey等互通。

为了方便起见,Kubernetes使用K3s发行版部署。此外,如果有大量媒体内容,则建议使用对象存储以避免挤占服务器存储空间,以及如果想对外开放注册还需要电子邮件发件代理。

Mastodon官方提供了一个使用Helm部署的存储库,但由于该库使用了Bitnami的Helm存储库提供依赖组件,而Bitnami目前尚不支持Arm架构,且看起来也没什么意愿,所以如果需要在arm64架构的服务器中部署则需要分别部署依赖组件。这里将分别提供两种部署的方法。Bitnami现在已经提供ARM架构容器镜像。需要注意的是用于全文检索功能的ElasticSearch会大量消耗内存,如果内存低于8G可能需要修改设置来降低内存占用、换用OpenSearch,或者干脆换其他资源占用更低的ActivityPub协议服务端。

更新:官方helm源有些改动,以下values.yaml文件的内容可能并未完全覆盖所有必须内容。

准备工作

安装K3s

K3s支持跨架构,所以这里在两种架构中都使用同样的方法安装。

如果使用的是RHEL系发行版,需要先关闭firewalld,不然会导致pod间无法通信,当然如果精通防火墙配置也可以编辑防火墙设置来放行。

sudo systemctl disable firewalld --now

安装K3s时如果打算从集群外的设备来控制集群,推荐在安装命令中使用--tls-san参数来为kubeconfig文件中的证书添加域名或外部IP,反之则使用默认的安装命令即可:

curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="--tls-san 域名或IP" sh -s -

如果在控制节点控制集群时,非root用户遇到kubeconfig文件无访问权限的情况,使用以下方法将文件复制出来并获得权限:

sudo cp -i /etc/rancher/k3s/k3s.yaml $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

最后在.bashrc文件最后一行添加环境变量:

export KUBECONFIG=$HOME/.kube/config

保存后使用bash命令刷新即可。

从外部控制时,首先放行6443端口,然后将kubeconfig复制到外部设备,修改文件内的服务器地址。使用时在kubectl或helm后面添加--kubeconfig 文件路径,后面再写其他命令即可,也可以直接修改环境变量来设置为默认。

安装cert-manager

开启https访问需要申请证书,通常使用cert-manager管理,这里使用Helm来安装,首先Helm需要安装在可以控制集群的设备中。可以使用脚本或包管理器安装Helm

添加存储库:

helm repo add jetstack https://charts.jetstack.io && helm repo update

安装cert-manager:

helm install cert-manager jetstack/cert-manager --namespace cert-manager --create-namespace --set installCRDs=true

使用yaml文件创建ClusterIssuer,这里使用HTTP-01方式,需要外部可以访问80和443端口,如无法访问则需使用DNS-01方式:

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: "letsencrypt"
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: [email protected]
    privateKeySecretRef:
      name: letsencrypt
    solvers:
    - http01:
        ingress:
          class: traefik

保存后使用kubectl apply -f yaml命令来应用。此时颁发证书的准备已经完成,而正式的证书申请在创建Ingress时进行。

使用SendGrid作为发件代理

开放外部注册需要配置电子邮件发送功能来进行账号验证,这里以SendGrid为例。

首先注册Sendgrid,如果验证迟迟无法通过可以联系客服提供社交账号。注册后验证发件使用的域名,在账户管理中左侧选择Settings中的Sender Authentication,然后选择Authenticate Your Domain,根据提示设置DNS即可。

验证后创建SMTP Relay发件代理,选择左侧Email API中的Integration Guide,然后选择SMTP Relay,此时会提醒创建API Key,创建完成后妥善保存,之后会将其配置到Mastodon中。创建后需要发送一个电子邮件来通过测试才能正式激活,可以参照SendGrid说明使用Telnet发送

使用Oracle对象存储来保存媒体文件

由于媒体文件会大量占用存储空间,所以推荐使用更为廉价的对象存储来保存。如果服务器磁盘够大的话也可以不用。

在Oracle Cloud里面选择存储里面的存储桶,然后创建存储桶,记下填写的名称,其他的使用默认设置即可,然后选择右上角的用户菜单中的“租户: 用户名”,进入租户详细信息界面后,如果Amazon S3 兼容性 API 指定的区间为空则选择编辑对象存储设置,将其设置为根区间。然后记录下对象存储名称空间的内容。

接下来选择右上角用户菜单中的“我的概要信息”,选择左下角的客户密钥,然后生成密钥,保存好生成的密钥,保存后在密钥列表中还有一个访问密钥也一并保存好。

最后在Oracle文档中查找区域标识符,当前账户的地区在管理界面的最上方可以找到,此外有一个简单的方式时直接在管理界面的URL中找到region=,后面就是区域标识符。

将区域标识符和对象存储明明空间组合进URL中:

https://<对象存储名称空间>.compat.objectstorage.<区域标识符>.oraclecloud.com

即可得到访问Oracle对象存储的地址。保存好地址和两个密钥,之后会配置到Mastodon中。

生成Mastodon的必须密钥

运行Mastodon还需要用于浏览器会话、两步验证和推送的密钥,这些密钥需要通过Mastodon镜像来生成。这里分别提供使用docker和kubectl的命令。

首先生成用于浏览器会话的密钥secret_key_base和用于两步验证的密钥otp_secret。将以下命令运行两次,在输出中即可得到所需的密钥。

docker run --rm -it tootsuite/mastodon:latest bundle exec rake secret
kubectl run -it --rm mastodon --image=tootsuite/mastodon --command bundle exec rake secret

然后使用以下命令生成用于推送通知的密钥vapid_private_key和vapid_public_key,这次一个命令中的输出即可得到公钥和私钥。

docker run --rm -it tootsuite/mastodon:latest bundle exec rake mastodon:webpush:generate_vapid_key
kubectl run -it --rm mastodon --image=tootsuite/mastodon --command bundle exec rake mastodon:webpush:generate_vapid_key

至此准备工作完成。

使用Helm Chart部署所有组件(仅限x86架构)

在x86服务器中部署较为简单,只要使用官方提供的Helm Chart包即可

首先将Mastodon Chart存储库克隆到本地:

git clone https://github.com/mastodon/chart.git

然后进入到目录中修改values.yaml文件,由于文件很长,这里分块修改,并且不会修改所有配置,如果需要修改其他配置,可以参考注释或查看官方文档

首先修改mastodon区块,其中的createAdmin用于创建默认管理员用户,将其设为开启并按需修改:

  createAdmin:
    # @ignored
    enabled: true
    # @ignored
    username: 管理员用户名
    # @ignored
    email: [email protected]

然后修改地区和域名,可用的locale可以在文档中找到。local_domain为用户名后面的服务器识别域名,web_domain为网站本身的域名,如果使用同一个则可以只设置local_domain,另一个设置为null。此外如果不同的话还需要在web服务器中配置重定向,将local_damain/.well-known/webfinger跳转到web_domain/.well-known/webfinger

  locale: zh-CN
  local_domain: mastodon.local
  # -- Use of WEB_DOMAIN requires careful consideration: https://docs.joinmastodon.org/admin/config/#federation
  # You must redirect the path LOCAL_DOMAIN/.well-known/ to WEB_DOMAIN/.well-known/ as described
  # Example: mastodon.example.com
  web_domain: null
  # -- If set to true, the frontpage of your Mastodon server will always redirect to the first profile in the database and registrations will be disabled.

接下来添加对象存储设置,使用对象存储首先需要设置为开启,然后将之前的地址和两个私钥,以及存储桶名称填入其中:

  s3:
    enabled: true
    access_key: "访问密钥"
    access_secret: "私钥"
    # -- you can also specify the name of an existing Secret
    # with keys AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY
    existingSecret: ""
    bucket: "存储桶名称"
    endpoint: "https://URL地址"
    hostname: "URL主机名,没有https"
    region: "区域标识符"
    # -- If you have a caching proxy, enter its base URL here.
    alias_host: ""

配置会话、两步验证和推送的密钥:

  secrets:
    secret_key_base: "会话密钥"
    otp_secret: "两步验证密钥"
    vapid:
      private_key: "推送私钥"
      public_key: "推送公钥"
    # -- you can also specify the name of an existing Secret
    # with keys SECRET_KEY_BASE and OTP_SECRET and
    # VAPID_PRIVATE_KEY and VAPID_PUBLIC_KEY
    existingSecret: ""

之后是邮件发送配置:

  smtp:
    auth_method: plain
    ca_file: /etc/ssl/certs/ca-certificates.crt
    delivery_method: smtp
    domain:
    enable_starttls: 'auto'
    from_address: 发件地址@example.com
    openssl_verify_mode: peer
    port: 587
    reply_to:
    server: smtp.sendgrid.net
    tls: false
    login: apikey
    password: SendGrid的API密钥
    # -- you can also specify the name of an existing Secret
    # with the keys login and password
    existingSecret:

mastodon区块修改完成,然后修改ingress配置,由于之前设置了cert-manager,这里反注释即可使用cert-manager生成证书,然后按需修改域名即可。

ingress:
  enabled: true
  annotations:
    # For choosing an ingress ingressClassName is preferred over annotations
    # kubernetes.io/ingress.class: nginx
    #
    # To automatically request TLS certificates use one of the following
    # kubernetes.io/tls-acme: "true"
    cert-manager.io/cluster-issuer: "letsencrypt"
    #
    # ensure that NGINX's upload size matches Mastodon's
    #   for the K8s ingress controller:
    # nginx.ingress.kubernetes.io/proxy-body-size: 40m
    #   for the NGINX ingress controller:
    # nginx.org/client-max-body-size: 40m
  # -- you can specify the ingressClassName if it differs from the default
  ingressClassName:
  hosts:
    - host: mastodon.local
      paths:
        - path: '/'
  tls:
    - secretName: mastodon-tls
      hosts:
        - mastodon.local

最后为数据库和缓存设置密码:

# https://github.com/bitnami/charts/tree/master/bitnami/postgresql#parameters
postgresql:
  # -- disable if you want to use an existing db; in which case the values below
  # must match those of that external postgres instance
  enabled: true
  # postgresqlHostname: preexisting-postgresql
  # postgresqlPort: 5432
  auth:
    database: mastodon_production
    username: mastodon
    # you must set a password; the password generated by the postgresql chart will
    # be rotated on each upgrade:
    # https://github.com/bitnami/charts/tree/master/bitnami/postgresql#upgrade
    password: "密码"
    # Set the password for the "postgres" admin user
    # set this to the same value as above if you've previously installed
    # this chart and you're having problems getting mastodon to connect to the DB
    # postgresPassword: ""
    # you can also specify the name of an existing Secret
    # with a key of password set to the password you want
    existingSecret: ""
# https://github.com/bitnami/charts/tree/master/bitnami/redis#parameters
redis:
  # disable if you want to use an existing redis instance; in which case the
  # values below must match those of that external redis instance
  enabled: true
  hostname: ""
  port: 6379
  # -- you must set a password; the password generated by the redis chart will be
  # rotated on each upgrade:
  password: "密码"
  # you can also specify the name of an existing Secret
  # with a key of redis-password set to the password you want
  # auth:
    # existingSecret: ""

完成后保存,然后进入chart目录并运行:

helm dep update

来拉取数据库、缓存和全文检索的依赖组件,完成后运行

helm install --namespace mastodon --create-namespace mastodon ./ -f ./values.yaml

成功后会显示登录网址,但此时管理员账号是因为没有密码是无法登录的,可以使用邮箱进行重置,也可以使用以下命令来进入容器进行管理:

kubectl -n mastodon exec -it deployment/mastodon-web -- bash

进入后执行以下命令来重置密码:

tootctl accounts modify 管理员用户名 --reset-password

也可以合并到一条命令中:

kubectl -n mastodon exec -it deployment/mastodon-web -- tootctl accounts modify 管理员用户名 --reset-password

分别部署组件和Mastodon

首先创建命名空间:

kubectl create ns mastodon

部署PostgreSQL和Redis,这里为了方便起见仍然使用Bitnami的Chart,但将镜像替换为支持ARM架构的官方镜像。使用以下命令即可:


helm -n mastodon install postgresql bitnami/postgresql \
  --set global.postgresql.auth.postgresPassword="管理员密码" \
  --set global.postgresql.auth.username="用户名" \
  --set global.postgresql.auth.password="密码" \
  --set global.postgresql.auth.database="数据库名" \
  --set image.repository="postgres" \
  --set image.tag="15.0"

helm -n mastodon install redis bitnami/redis \
  --set global.redis.password="密码" \
  --set architecture="standalone" \
  --set image.repository="redis" \
  --set image.tag="7.0"

以上的密码、用户名、数据库名要和Mastodon的values.yaml文件中一一对应。运行后会返回集群内部访问的域名,诸如postgresql-postgresql.mastodon.svc.cluster.local这样。

进入Chart目录,除了上一章中对values.yaml文件进行的修改以外,还要关闭PostgreSQL、Redis和ElasticSearch的部署,分别在230行、239行和261行,将其改为:

 enable: false

然后其余诸如用户名、密码、管理员密码等则填写之前安装时设置的,hostname则填写之前返回的域名。需要注意的是这次redis配置中的existingSecret必须填写,通常填写redis即可,如果做过其他改动可以使用kubectl -n mastodon get secret列出secret来查找。

此外,除了values.yaml以外,还需要修改Chart.yaml,在依赖项中最后添加:

 - name: common
   version: 1.0.0
   repository: https://raw.githubusercontent.com/bitnami/charts/archive-full-index/bitnami

完成后拉取依赖:

helm dep update

然后安装Mastodon:

helm -n mastodon install mastodon ./ -f values.yaml

此时安装完成,但还有全文检索功能没有安装。如果把之前安装PostgreSQL和Redis的方法来对ElasticSearch使用的话,会发现仍然遇到架构问题。因为Bitnami会使用一个init容器来修改内核参数,这个init容器同样不支持ARM。而如果把这个容器替换的话,则会出现莫名其妙的资源占用过高,很容易导致系统瘫痪。所以这里推荐使用亚马逊Fork的版本OpenSearch,首先克隆存储库下来:

git clone https://github.com/opensearch-project/helm-charts.git

进入其中的charts/opensearch目录,修改values.yaml文件来禁用TLS,在61和62行之间,也就是security:下面添加disabled: true,以及修改297行,如果算上之前添加的一行则为298行,将securityConfig.enabled修改为false,然后部署:

helm -n mastodon install opensearch ./ -f values.yaml

部署完成后修改Mastodon配置,先用kubectl -n mastodon get svc获取服务名,然后使用kubectl -n mastodon edit cm mastodon-env打开ConfigMap进行配置,在data中添加:

  ES_ENABLE: "true"
  ES_HOST: opensearch-cluster-master.mastodon.svc.cluster.local
  ES_PORT: "9200"

其中ES_HOST中的域名规则是:服务名.命名空间.svc.集群域名,默认情况下应该是以上示例中的内容,如果有其他变化应按需修改。保存后重启Mastodon:

kubectl -n mastodon rollout restart deployment -n mastodon mastodon-web

现在使用kubectl -n mastodon exec -it deployment/mastodon-web -- bash进入mastodon容器中建立索引:

bin/tootctl search deploy

进度条走完后索引建立完成,现在就部署完毕了,按照上一章结尾配置管理员密码即可

更新版本

执行前最好进行数据库备份。

和部署时相同,进入Chart目录中,然后执行以下命令拉去最新的镜像并更新:

helm -n mastodon upgrade mastodon ./ --reuse-values --set image.repository="ghcr.io/mastodon/mastodon" --set image.tag="latest"

其中--reuse-values是为了保留已经使用的values配置。

如果更新出现错误,可以使用Helm进行回滚,首先执行helm -n mastodon history mastodon来查看可用的旧版本。由于之前的更新方法仅更新了镜像,所以历史记录中的版本号还会显示旧的版本号,不过可以通过记录的更新时间来判断。

执行下列命令回滚:

helm -n mastodon rollback mastodon 历史版本序号

这里的序号为查看版本时最前方显示的数字编号,而不是后面的Chart或App的版本号。

可能出现的错误

无法发送注册邮件

首先检查是不是在垃圾邮件箱中,然后查看发件代理的后台是否有发送记录,如果有说明被接收方拦下,这个只能找发件代理解决。如果后台没有记录,则先通过telnet测试能否发送,如果发送成功,则说明问题出在mastodon中,如果检查配置没有错误的话,可能是作业调度程序sidekiq出现故障导致没有进行发件作业,可以使用以下命令重启:

kubectl rollout restart deployment -n mastodon mastodon-sidekiq-all-queues

无法建立索引,提示“You can't set the item's total value to less than the current progress”

似乎是进度条的问题,在Mastodon主容器内执行以下命令即可:

sed -E 's/indices.sum.+/2000/g' -i lib/mastodon/search_cli.rb

参考文章

Create your own “Twitter” in 10 minutes

Install Mastodon in Kubernetes

MastodonのメールサーバにSendGridを使う際のポイント

Getting Mastodon working with Amazon S3 file-hosting

Mastodon documentation

How can I replace the Image used in Helm Chart to make it support ARM architecture?

Fail to create the ElasticSearch indices