情報発信に便利で軽量なActivityPubサーバー「Takahē」[後編] ~基本的なアーキテクチャと特徴的なコンポーネントの紹介と⁠サーバーの構築方法

前編では、ActivityPub/FediverseサーバーTakahē(タカヘー)の特徴(特に、他のActivityPubサーバーでサポートされていないマルチドメインサポート)と、クライアントアプリElkとともに使用する方法について紹介しました。

後編の記事では、Takahēサーバーの基本的なアーキテクチャや、Takahēの特徴的なコンポーネント、内部で使われている面白いライブラリなどを紹介します。記事の最後では、docker-composeを使って実際にTakahēサーバーをコンテナで起動し、手元で試してみます。

Takaheの基本的なアーキテクチャ

Takahēは、主に3つのコンポーネントから作られています。メインのTakahēサーバーは、Mastodon互換のREST APIとActivityPub APIの2種類のAPIを実装しているDjangoアプリです。また、Statorと呼ばれるバックグラウンドワーカーもDjangoで実装されており、非同期処理のタスクや他のActivityPubサーバーとのやり取りを行います。Takahēでは、サーバーとStatorワーカーはステートレスな実装になっており、すべてのステートフルなデータはPostgreSQLデータベースに保存されています。

図1 Takahēのアーキテクチャ図
図1

Djangoウェブフレームワーク

Takahēサーバーのコアとなる部分は、Djangoウェブフレームワークを使用してPythonで書かれています。Djangoは、アメリカ合衆国カンザス州ローレンスにある新聞社のウェブ部門であるWorld Onlineで開発されました[1]

Djangoの特徴は、プログラマでなくても扱いやすいテンプレートエンジン、さまざまなデータモデルを表現できる柔軟なORM、優れたデータベースマイグレーション機能、ビルトインの管理サイト・認証システム、わかりやすく包括的なドキュメンテーション、ウェブ開発の多くのユースケースをカバーする充実したエコシステムなど、多岐にわたります。また、安定性や後方互換性をとても重視しており、バージョンアップ時でもスムーズなアップグレードマイグレーションが可能になっています。Djangoの有名な利用例としては、Instagram、Mozilla、Pinterest、Spotifyなどがあります[2]。また、日経新聞電子版のバックエンドや、イベント募集サイトのconnpassなどでも利用されています。

Djangoが初めてリリースされたのは18年前の2005年ですが、現在まで常に改良が続けられてきました。2023年12月にリリース予定のバージョン5.0でも、フォームテンプレートのモダナイゼーションや、ORMでのサーバーサイドのcomputed valueやgenerated columnのサポートなどが予定されています。

Takahēの作者であるAndrew Godwin(アンドリュー・ゴッドウィン)は、Django組み込みのデータベースマイグレーション機能や、WebSocketsなどを利用可能にするDjango Channelsの開発など、多大な貢献をしてきました。最近のDjangoの特に大きな改善として、各種コンポーネントの非同期 (asyncio) サポートがありますが、Andrewはこうした非同期機能の実装でも大きな役割を果たしています[3]

APIライブラリhatchwayが提供するMastodon互換REST API

Takahēサーバーが提供する1つ目のAPIは、ユーザーがクライアントアプリケーションから接続するときに利用されるMastodon互換のREST APIです。クライアントアプリは、はじめにOAuth2認証のAPIエンドポイントを使ってアカウントに接続した後、各種APIエンドポイントからTakahēアカウントの情報を取得したりアカウントを操作したりできます。

たとえば、GET /api/v1/timelines/homeAPIエンドポイントからは、ホームタイムラインの投稿一覧がJSONで取得できます。POST /api/v1/statuses/:id/favouriteに投稿のIDを送信すれば、Takahē上でその投稿をいいねする処理が行われます。こうして取得したJSONデータを自由に加工したり、各種イベントハンドラでAPIと通信することで、クライアントアプリは自由にUIが実装できるようになるわけです。

PythonのAPIフレームワークとして最近非常に人気があるものに、FastAPIがあります。FastAPIの特徴の1つにPython標準の型情報を活用したデータのバリデーション機能がありますが、この機能は主にPydanticと呼ばれるバリデーションライブラリで実装されています[4]

Pydanticでバリデーションを行うと型が保証された扱いやすいデータが得られ、APIフレームワークの実装では非常に便利なため、Djangoでもこれを活用したdjango-ninjaというサードパーティのAPIライブラリが開発されています。Takahēでも当初はこのライブラリを使用してREST APIの実装が進められましたが、MastodonのREST APIの特殊な仕様により、Pydanticの機能が十分に活用できない問題などが見つかりました[5]

そこでTakahēのために、特殊な仕様にも対応できてDjango標準のAPIに近いインターフェイスを持った、Pydanticを活用した新しいAPIフレームワークdjango-hatchwayが開発されました。このフレームワークはTakahē以外でも汎用に利用できる独立したライブラリとして、GitHubで公開されています。Mastodon互換のREST APIは、このフレームワークを利用して実装されています。

ActivityPubプロトコルとは?

Takahēサーバーが提供する2つ目のAPIは、ActivityPub APIです。このAPIは、TakahēサーバーがMastodonやMisskeyなどの他のFediverseサーバーと通信するために利用されます。このときサーバー間の通信で利用されるプロトコルが、Fediverseの基盤となっている「ActivityPubプロトコル」です。

図2 ActivityPubの概念図。Actor(アカウント)がInboxとOutboxを持ち、JSONオブジェクトがActivityPubサーバー間でやり取りされる
図2

ActivityPubプロトコルは、複数のサーバー間でフォローや投稿などのアクティビティ(Activity)をやり取りするために2014年ごろにW3C Social Web Working Groupで策定されたプロトコルです。ここで、各種アクティビティは、Activity Streams 2.0と呼ばれる仕様に従って作られたJSONオブジェクトで表現されます。ActivityPubサーバー上のアカウントはアクター(Actor)と呼ばれ、アカウントの活動(アクティビティ)をサーバー間でやり取りする際には、⁠アクター』『オブジェクト』『アクティビティする⁠⁠」という汎用性の高い形式で表現できるようになっています。これは英語の文法のSVO構文(主語Sが目的語Oを動詞Vする)に似ています。

マイクロブログサービスの場合には、具体的なアクティビティの種類として、投稿のアクティビティNote、いいねのアクティビティLike、フォローのアクティビティFollowなどがあり、サーバー間で共通のアクティビティとして利用されます。

たとえば、AさんがBさんをフォローすると、AさんのサーバーからBさんのサーバーに対して、⁠Aさん』『Bさん』Followする⁠⁠」という意味のJSONオブジェクトが届けられます。BさんがAさんからのフォローを承認すると、今度はBさんのサーバーからAさんのサーバーに対して、⁠Bさん』『AさんからBさんへのFollowアクティビティ』Acceptする⁠⁠」というJSONオブジェクトが返信されます。このようにして、ActivityPubプロトコルにしたがった、一連のJSONオブジェクトがサーバー間で送受信されることにより、ActivityPubサーバーとして動作するサーバーが実現します。

オープンなプロトコルを実装した複数の独立したサーバー間でメッセージが送られるさまは、メールサーバーによるメールの送受信に似ているかもしれません。ActivityPubの場合には、アクターの受信ボックス(Inbox)にメールのテキストデータの代わりにさまざまなアクティビティが表現されたJSONオブジェクトが送られます。

具体例として、Takahē公式アカウント@takahe@takahe.socialがMastodon公式アカウント@mastodon@mastodon.socialからのフォローを許可した場合には、TakahēサーバーからMastodonサーバーに対して、以下のようなFollowアクティビティをAcceptするアクティビティ」を表現したJSONが送られます。

{
    "@context": "https://www.w3.org/ns/activitystreams",
    "type": "Accept",
    "id": "https://jointakahe.org/@[email protected]/#accept/1",
    "actor": "https://jointakahe.org/@[email protected]/",
    "object": {
        "type": "Follow",
        "id": "https://mastodon.social/0d4a4320-d807-4c56-9936-dd265fefea34",
        "actor": "https://mastodon.social/users/Mastodon",
        "object": "https://jointakahe.org/@[email protected]/"
    }
}

ActivityPubの仕様の詳しい解説とPerlでの実装例については、連載記事第59回 Fediverse入門―非中央集権型SNSサーバを作ろう!(1⁠⁠ | gihyo.jpでも紹介されています。

ActivityPubサーバーとして振る舞うStatorワーカー

ActivityPubの他のサーバーとのやり取りでは、処理に時間がかかったり通信が失敗したりする可能性があります。このような処理を行いたいときは、一般にバックグランド処理を非同期で実行するタスクキューシステムが利用されます。DjangoではCeleryと呼ばれるタスクキューフレームワークがRedisと合わせて利用されることが多く、実際、同じくDjangoで書かれた書評サービスのActivityPubサーバーBookwyrmでも、このCeleryとRedisを組み合わせた構成が採用されています。

ところが、Takahēはこれとはまったく異なるアプローチを取り、reconciliation loop(調整ループ)の考えに着想を得た独自のタスク処理システムを新たに実装しています。TakahēのバックグラウンドワーカーはStator(ステーター)と呼ばれ、同じコードベースのDjangoアプリをStatorモードとして起動することで、メインのTakahēサーバーとは異なる振る舞いをするバックグラウンドワーカーとして実行されるようになっています。

ノート:reconciliation loop(調整ループ)とは?

「調整ループ」という仕組みを実装したシステムとして有名なのはKubernetesです。Kubernetesでは、ユーザーがリソースの目標とする状態を定義して、その状態がetcdデータベースに保存されます。各リソースをチェックするKubernetesのコントローラーは、Kubernetesクラスタ上の担当リソースの現在の状態と目標の状態をループ処理の中で繰り返し比較します。比較時に2つの状態に違いが存在すると、コントローラーにより現在の状態を目標の状態に近づける操作が実行されます。

たとえば、目標の状態として「Pythonアプリのコンテナが3つ起動された状態」という定義があるとします。現時点で2つのPythonコンテナしか存在しなかった場合、調整ループは「新たに1つのPythonコンテナを作成する」という処理を実行して、目標とする「Pythonコンテナが3つ実行された状態」へと変化させます。このように、⁠ループ処理の中で現在の状態を繰り返しチェックして、状態に応じてリソースを調整する処理を行う」というのが、調整ループの中心となるアイデアです。

Kubernetesでは大規模にスケールするように互いに独立した多数のリソースとコントローラーから構成されますが、Takahēの調整ループではTakahēで必要とされる処理に合わせて、1つのDjangoアプリの中に独自の形でコンパクトに実装されています。

Takahēの調整ループ

Takahēの調整ループにおける「リソース」に相当するのは、ActivityPubで必要となるさまざまな種類のオブジェクトをモデル化した「Statorモデル」です。StatorモデルはDjangoのデータモデルを拡張したもので、モデルの内部に「ステート(状態⁠⁠」と「各ステートを処理するハンドラ」からなる「ステートグラフ」が追加されています。

図3 Takahēの調整ループ。Statorワーカーは、データベースに保存されたStatorオブジェクトを次々と処理し、現在のステートから目標とする次のステートへと遷移させていく
図3

Statorワーカーはループ処理の中で、データベースに保存されたStatorオブジェクトの中から処理の準備ができている状態のオブジェクトを取り出します。それぞれのStatorオブジェクトには現在のステートが記録されているので、Statorワーカーはそのステートに対応するハンドラメソッドを選んで実行して、必要な処理を実行します。処理が完了したら、ハンドラメソッドから次に遷移するべきステートが返されるので、データベース内のStatorオブジェクトのステートを新しいステートに更新します。こうして保存されたStatorオブジェクトは、再び次のStatorワーカーがループ処理で自分を取り出すのを待つことになります[6]

FollowのStatorモデル

「フォロー」をモデル化したStatorモデルのFollowを例にとって、Statorモデルとその内部のステートグラフが具体的にどのように定義されているのかを見てみましょう。次に示すのは、FollowのStatorモデルの模式図です。

図4 Takahē公式アカウント(@[email protected])からMastodon公式アカウント(@[email protected])への「フォロー」を表現したStatorモデル
図4

このようなモデルが、データベース上では1つのレコードとして記録されています。FollowStatorモデル内の右側のステートグラフは、具体的には以下のようなクラスとして定義されています[7]

Statorモデルのステートとステート状態遷移によるステートグラフの定義
class FollowStates(StateGraph):
  unrequested = State(try_interval=600)
  accepting = State(try_interval=24 * 60 * 60)
  accepted = State(externally_progressed=True)

  unrequested.transitions_to(accepting)
  accepting.transitions_to(accepted)

前半のブロックでは、グラフの各ノードに対応する3つのステートが定義されています。unrequestedはフォローリクエストを受けた状態、acceptingはフォローの許可が進行中である状態、acceptedはフォローの許可が完了した状態をそれぞれ表現しています。

後半のブロックでは、矢印のグラフのエッジに相当する状態遷移が定義されています。unrequestedacceptingに遷移でき、acceptingacceptedに遷移できることを表現しています。

次に、このFollowStatesをステートとして持つFollowStatorモデル本体のコードを見てみましょう。

Statorモデルのフィールドの定義とステートハンドラメソッドの例
class Follow(StatorModel):
    id = models.BigIntegerField(primary_key=True, default=Snowflake.generate_follow)
    source = models.ForeignKey("users.Identity")
    target = models.ForeignKey("users.Identity")

    state = StateField(FollowStates)
    
    @classmethod
    def handle_accepting(cls, instance: "Follow"):
        instance.target.signed_request(
            method="post",
            uri=instance.source.inbox_uri,
            body=canonicalise(instance.to_accept_ap()),
        )
        TimelineEvent.add_follow(instance.target, instance.source)
        return cls.accepted

idsourcetargetは、通常のDjangoモデルのフィールドです。それぞれ、FollowオブジェクトのID、フォロー元アカウントとフォロー先アカウント名は、それぞれFediverseアカウントを表現するIdentityStatorモデルを外部キーとして参照しています。そして、先ほど定義したFollowStatesが特殊なstateフィールドとしてモデルに設定されています。

FollowStatorモデルのステートハンドラとして、ここではacceptingステートを処理するハンドラメソッドhandle_accepting()を挙げました。このハンドラメッソでは、Takahēユーザーが他のアカウントからのフォローリクエストを許可した後に実行される処理が定義されています。はじめにFollowStatorモデルのメソッド.to_accept_ap()でフォロー許可を表すAcceptアクティビティのJSONオブジェクトを作成し、フォロー元アカウントのサーバーのInboxに送信した後、次に遷移するべき状態であるacceptedを返します。Statorはこの結果を受けて、Followステートを次の状態に進めます。Statorがこのようなステートを処理するハンドラメソッドと一連のステートの遷移を繰り返し実行することで、Statorオブジェクトが適切に処理されていきます。

Follow以外にも、ドメイン、投稿、絵文字、Inboxのアイテムを表現するStatorモデルとして、それぞれDomainPostEmojiInboxMessageなどのStatorモデルが定義されています。そして、それぞれのStatorモデルには、適切な処理が行われるように固有のステートグラフが上手く定義されています。 このようにして、TakahēサーバーとStatarワーカーは協調して動作し、内部で必要なデータ処理を行ったり、ActivityPubプロトコルにしたがって外部サーバーと適切に通信することで、Fediverseの中でActivityPubサーバーとして振る舞えるようになっているわけです。

Takahēのアーキテクチャの優れた点

Takahēでは追加のタスクキューシステムを使う代わりに、Stator向けにモデル化されたすべての状態(ステート)をステートフルなデータベースに集約しています。これにより、サーバーとワーカーを疎結合でステートレスなデザインにでき、処理の必要に応じて各プロセスを自由に増減することが可能になります。

また、外部のサーバー障害により通信が失敗したり、ワーカープロセスがクラッシュして処理が失敗したとしても、単純に新しいStatorワーカーを起動して、データベースに保存された状態を元に簡単にリトライができるようになっています。

ノート:SQLite対応のドロップ

Takahēは当初、Djangoでよく利用されるPostgreSQLデータベースに加えて、SQLiteデータベースも実験的にサポートしていました。SQLiteはファイルシステム上の単一ファイルとしてデーモンプロセスなしで利用できるため、特に省リソースで利用するときには大きな利点があります。しかし、ActivityPubオブジェクトの処理に便利なJSON関数の対応不足や、PostgreSQLで利用できるテキスト検索用インデックスへの未対応など、Takahēのユースケースに必要な機能が足りないことが多く、残念ながらTakahēバージョン0.9でサポートが中断されました。

フロントエンドでのhtmxと_hyperscriptの活用

Takahēのフロントエンドでは、近年Djangoコミュニティで話題になることが多い2つのライブラリ「htmx」「_hyperscript」が活用されています[8]。これら2つのライブラリを開発したのはBig Sky SoftwareCarson Grossで、モンタナ州立大学のコンピュータサイエンス学部でコンパイラやプログラミング言語の講師も務めています。

ハイパーメディアのアイデアでHTMLを拡張するhtmx

htmxは、ハイパーメディアの思想を推し進め、いくつかの属性を追加するだけでHTML要素を拡張して、ページに簡単にマイクロインタラクションが追加できる軽量なJavaScriptライブラリです。

htmxを活用したタイムラインの読み込みボタンの実装
<!-- 投稿リスト -->
<div class="page-content">
  <div class="post" data-takahe-id="221467104882060352" role="article" tabindex="0">...</div>
  <div class="post" data-takahe-id="222562219558659344" role="article" tabindex="0">...</div>
  ...
  <!-- ページネーションのボタン -->
  <div class="pagination">
    <a
      class="button"
      href=".?page=2"
      hx-boost="true"
      hx-select=".page-content"
      hx-target=".pagination"
      hx-swap="outerHTML"
    >
      Next Page
    </a>
  </div>
</div>

Takahēでの利用例の1つは、タイムラインのページネーションです。上記のコードは、タイムラインの投稿リストとページネーションのボタン部分のHTMLを抜粋したものです。htmxの動作は、hx-というprefixが付いた属性により定義できます。この例では、タイムライン1ページ目の「Next Page」ボタンをクリックすると、2ページ目のHTMLがDjangoサーバーから取得されhref=".?page=2"⁠、取得されたページの投稿リストと新しい「Next Page」ボタンが含まれる部分のHTMLhx-select=".page-content"だけが取り出されます。そして、hx-targethx-swapで指定された.paginationouterHTML部分と置換されます。

Djangoの典型的なページネーションの実装では、2ページ目を表示するためにページ全体のリロードが行われますが、このようにhtmxを利用すれば、ページ全体の遷移やリロードを行うことなく、次のページの投稿リストを読み込めます。

このTakahēの例では、ページネーションの実装をシンプルにするために、元と同じページ全体のHTMLを取得して一部だけを利用するような実装になっています。しかし、Djangoの柔軟なテンプレートエンジンを活用すれば、たとえばコンポーネントのような小さな単位でHTMLスニペットを動的にレンダリングして送信するなど、htmxは工夫次第でさまざまな活用方法が考えられます。

JavaScriptを使用せずに僅かな記述だけでシングルページアプリケーションのような体験を簡単にインクリメンタルに導入でき、Djangoの柔軟なテンプレートシステムとサーバーサイドレンダリングとも親和性が高いことが、Djangoコミュニティで人気になっている理由だと考えられます。

自然言語風の記述でHTMLを操作できる_hyperscript

同じ作者による_hyperscriptも、最小限のJavaScriptでHTML上にマイクロインタラクションを追加できる面白いライブラリです。_hyperscriptと呼ばれる自然言語に似た独自のプログラミング言語ランタイムが内部に実装されていて、HTMLの操作をこの言語で記述します。言語の文法デザインは、HyperTalkと呼ばれるプログラミング言語の影響を受けていて、macOSユーザーであれば、同じ言語の影響を受けているAppleScriptと似ていることに気づくかもしれません。

_hyperscriptによるアカウント名をクリップボードにコピーする機能の実装
<a title="Copy Content"
    class="copy"
    _="on click
        writeText(#raw_response.innerText) into the navigator's clipboard
        then add .copied
        wait 2s
        then remove .copied">
    <i class="fa-solid fa-copy"></i>
</a>

ここでは、Takahēのプロファイル上のボタンを一例として挙げます。_hyperscriptのコードは、HTML要素の_属性の値としてHTML内に記述します。ここで<a>で作られたボタンをクリックすると、クリップボードにユーザー名がコピーされ、テキストがコピーされたことを示すためにボタンの色が2秒間変更されます。

自然言語(英語)のように書かれた上記の_hyperscriptのコードは、日本語に翻訳するなら次のようになるでしょう。

_="コピーアイコンをクリックすると
     アカウント名のテキストをクリップボード(`navigator.clipboard`)に書き込み
     `copied` クラスを追加し
     2秒待ち
     そして `copied` クラスを削除する"

_hyperscriptでできるHTML要素の操作はjQueryでできることに近いですが、_hyperscriptはコードのローカリティの重要性を強調しています。jQueryが操作するHTML要素から離れたJavaScript上に動作を記述する必要があるのに対して、_hyperscriptでは操作対象のHTML要素やそのすぐ近くの要素自体にコードを書きます。これにより、自然言語風の文法と合わせて、コードを読むときの認知的な負荷が下がり、コードの読み書きが容易になっています。

Takahēサーバーを動かす

Takahēのアーキテクチャと関連ライブラリを紹介してきましたが、ここからは、実際にdocker-composeを利用して手元でTakahēサーバーを実行する方法を紹介します。

ただし、Elkなどのクライアントアプリや他のActivityPubサーバーと通信するためには、HTTPS接続が可能なパブリックなサーバーが必要になるため、ローカル環境でできるのはTakahēサーバー内でのやり取りに限られます。そのような環境を用意するのが難しい場合、後述のGitpodの一時環境を利用することもできます。

docker-composeで実行する

前提条件として、以下の条件を確認してください。

  • gitがインストールされていること
  • Dockerがインストールされていること(または、PodmanなどのOCI互換のコンテナエンジン)
  • docker-composeコマンドが利用できること

レポジトリと設定ファイルの準備

まずは、GitHubからTakahēのGitレポジトリを取得しましょう。

git clone https://github.com/jointakahe/takahe
cd takahe/

今回使用するDockerfiledocker-compose.ymlファイルは、docker/フォルダ以下に置かれています。以下にdocker-compose.ymlの主要な部分の抜粋を示します。

version: "3.4"
x-takahe-common:
  &takahe-common
  image: takahe:latest
  environment:
    TAKAHE_DATABASE_SERVER: "postgres://postgres:insecure_password@db/takahe"
    TAKAHE_DEBUG: "true"
    TAKAHE_SECRET_KEY: "insecure_secret"
    TAKAHE_CSRF_HOSTS: '["http://127.0.0.1:8000", "https://127.0.0.1:8000"]'
    TAKAHE_USE_PROXY_HEADERS: "true"
    TAKAHE_EMAIL_BACKEND: "console://console"
    TAKAHE_MAIN_DOMAIN: "example.com"
    TAKAHE_ENVIRONMENT: "development"
    GUNICORN_EXTRA_CMD_ARGS: "--reload"
  ...
services:
  db:
    image: docker.io/postgres:15-alpine
    ...
  web:
    <<: *takahe-common
    ports:
      - "8000:8000"
    ...
  stator:
    <<: *takahe-common
    command: ["/takahe/manage.py", "runstator"]
    ...
  ...

はじめのx-takahe-common:のセクションでは、TakahēサーバーコンテナとStatorコンテナで共通に利用される設定が書かれています。takahe:latestは、Docker Hubで公開されている安定版のTakahēコンテナです。その後に多くの環境変数の設定が並んでいますが、Takahēの主要な設定はすべて環境変数経由で設定できるようになっています。自分でサーバーを公開する際にはこれらの値を変更します。

services:のセクションにはdb(PostgreSQLデータベース⁠⁠、web(Takahēサーバー⁠⁠、stator(Statorサーバー)の3種類のコンテナの定義が並んでいます。まさにこれらが、冒頭で説明したTakahēの3つの主要コンポーネントになります。

注意:安全ではないデフォルト設定

Gitリポジトリ上のdocker-compose.ymlファイルは、Takahēの開発に適した設定で参考用に提供されているものです。デフォルトの設定は、安全でないパスワードが使われていたり、エラー時にデバッグ情報が表示される開発モードdevelopmentやデバッグモードが有効になっているため、本番環境ではこのままの設定で使用しないように注意してください。実際にデプロイするための各種設定については、Takahēの公式ドキュメントを参照してください。

ノート:画像のアップロード

デフォルトのdocker-compose.ymlには画像関連の設定が含まれていないため、画像のアップロードや表示が上手くできません。画像を試したい場合は、environmentのセクションに以下の設定を追加してください。

TAKAHE_MEDIA_BACKEND: "local://"
TAKAHE_MEDIA_ROOT: "/takahe/media/"
TAKAHE_MEDIA_URL: "http://<server-domain-name>/media/"

<server-domain-name>部分は、アクセスするサーバーのURLに合わせて127.0.0.1:8000などに置換する必要があります。

これにより、コンテナ内のローカルディレクトリに画像が保存されるようになります。また、TakahēではDjangoのプラグインdjango-storageを利用しているため、Google Cloud Storage、Amazon S3、Minioなどのオブジェクトストレージを画像などのバックエンドとして簡単に利用できるようになっています。

Takahēサーバーを起動するには、takahe/ディレクトリ上で次のコマンドを実行します。

docker-compose -f docker/docker-compose.yml up

このコマンドだけで、メインの3コンポーネントが起動して、さらにデータベースのマイグレーションなどの初期設定も自動的に実行されます。サービスが起動したら、アクセスする前にTakahēにログインするための管理者ユーザーを作成します。次のコマンドを実行してください。

docker-compose -f docker/docker-compose.yml exec web -- python -m manage createsuperuser

このコマンドは、Takahēサーバーwebコンテナ内で、Djangoの管理用コマンドです。ユーザー名として使用するメールアドレスとパスワードを尋ねられるので、自由に入力します。今回はメールサーバーの設定をしていないので、架空のメールアドレスでも問題ありません。

ユーザーが作成できたら、ブラウザを開いてhttp://localhost:8000にアクセスします。作成したアカウントでTakahēにログインします。

初期状態ではドメインが存在しないため、アカウント作成前にドメインを追加する必要があります。ドメイン名には、docker-compose.ymlで設定したexample.comを入力しますlocalhost:8000ではないことに注意してください⁠⁠。

これで@example.comドメインを持つアカウントが自由に作成できるようになりました。⁠Create a new identity」ボタンをクリックすると、好きな名前でアカウントが作成できます。これ以降は前編の記事で説明したとおりです。

なお、クライアントアプリが利用できない場合でも、設定画面から簡易的な投稿フォームが利用できるため、ここから実験的な投稿を行うことが可能です。複数のユーザーを作成して、投稿やフォローなどを試してみてください。

Gitpod上で実行する

Gitpodは、ブラウザ上ブラウザ上でLinux開発環境を提供するクラウドサービスです。最近ではGitHubがGitHub Codespaceという競合サービスを提供していますが、Gitpodはこの分野での先駆的な存在です。Gitpodを利用するとHTTPS接続ができる一時的なサブドメインが使えるようになるので、開発環境上でdocker-composeを利用してTakahēサーバーを実行することで、クライアントアプリからの接続なども実験できるようになります。執筆時点でGitpodは月50時間まで無料で利用可能ですが、以下の手順を実行するにはGitpodのアカウント作成が必要なことに注意してください。

注意:Gitpodの一時的な開発環境

Gitpodが提供する環境は一定時間経つと自動的にシャットダウンしてしまうため、Gitpodの開発環境で作ったアカウントは実験目的で自分で利用することに留めてください。他の公開ActivityPubサーバーと接続してしまうと、相手のサーバーに接続不可能なサーバーと通信させてエラーを起こさせることになってしまいます。

Gitpodの構成ファイルが置かれたGitHubリポジトリを開き、⁠Open in Gitpod」ボタンをクリックします。

開発環境のマシン性能の選択画面に移動しますが、デフォルトの設定で問題ないためそのまま「Continue」をクリックします。

開発環境が開くと、用意されたGitpodのセットアップスプリプトが自動的に実行されます。このスクリプト内では、上記と同様にdocker-composeでTakahēを実行しますが、それに加えて、Gitpod環境に合わせたドメイン名の設定なども自動化されています。スクリプトの実行には2、3分ほど時間がかかります。

図5 Gitpod上で自動的に起動したTakahē
図5

スクリプトの実行が最後まで実行されると、8000番ポートでアクセス可能になったTakahēのウェブページがプレビュータブに表示されるはずです。このページは独立した新しいタブでも開けます。

この時点では、管理者ユーザーが存在しないため、前に説明した方法で管理者ユーザーを作成してログインします。

図6 Gitpod上の開発環境で動作するTakahēアカウントにはElkからも接続できる
図6

Kubernetesクラスターにデプロイする

Kubernetesの知識がある人なら、Secret、ConfigMap、PostgreSQLを用意した後、サーバーのコンテナとStatorのコンテナをそれぞれDeploymentで作るすることで、Kubernetesクラスターに簡単にデプロイできます。

実際、takahe.socialもGoogle Cloud上のKubernetesクラスタ上にデプロイされています。これまで説明してきたように、Takahēサーバーのステートはすべてデータベースに集約されているため、TakahēサーバーとStatorは、それぞれステートレスなPodとしてDeploymentで簡単にデプロイして自由にスケールできます。

Kubernetesの構成ファイルは、GitHubの別のリポジトリ(jointakahe/deployment-examples)で公開されているので参考にしてください。

おわりに

この記事が、新しいアイデアを得たり、新しい場所を自分で作るきっかけになれば幸いです。

おすすめ記事

記事・ニュース一覧