[Linux][Mastodon][Virtualization] KubernetesインフラにMastodonエンジンを突っ込む

前提事項

ここで記載する内容なんですが、以下が前提になっています。

  • 読む人はDockerやdocker-composeについてよく理解されている
  • 読む人はKubernetes(k8s)についてよく理解されている
  • Kubernetes(k8s)クラスタ及びネットワークコンポーネント(当環境ではProject Calico)、DBコンテナ環境やらRedisコンテナ環境やらはすでに出来上がっている

ちょっと期待はずれな内容かもしれませんので、先に記載しておきます。

今回やった事

ずばり、Mastodonエンジンのコンテナ化です。元々Mastodonはコンテナ構成のものも払い出されては居ますが、あくまでdocker-composeを前提としたものであり、Kubernetesのようなクラスタ構成を想定したものとはなっていません。

私自身、以前Dockerコンテナ型のインスタンスを立てて挫折した経験があるんですが、当時は全くコンテナ技術に対する知見がなく、理解しようとしても中々理解至るのに苦慮しましたが、今回自宅環境でKubernetesに手を出した、ということもあり、思い切ってやってみることにしました。

今回やった目的

目的は「リソースの節約」です。自宅環境、一応こう見えても物理サーバの台数は2台、CPU総合計16Core、メモリ148GB、ストレージ実効4.2TiBほどあるんですが、そこで30台ほど仮想サーバ動かしてたら流石にパツパツになってきました。仮想アプライアンスのメモリ使用量が比較的デカイ状況があります。

で、この度ちょっとコンテナに手を出して、手始めにKubernetesクラスタを組み、WWWインフラをコンテナ化してみたところ、これがすごくサクサク動く。よし、次にメモリ食ってるのはどこだ?って調べてみたところ、Mastodonが該当してました。よし、これをサクサク軽く動くようにするぞと。

現状の構成

こんな感じになっています。(切り替え後の状態)実は前回の記事投稿後、コンテナ基盤の拡張を少々行っています。DB/Redisについてはすでにコンテナ化を実施してたわけですが、Mastodonエンジンのコンテナ化はまだ行ってませんでしたし、nginxのコンテナ化は逆にロードバランサがうまく機能せずに失敗してました。

ところで、nginxってラウンドロビン方式であれば、それ自身をロードバランサに出来るんですね。知りませんでした。そこで、元々あるAPサーバのnginxを残し、すべてコンテナ化してしまい、かつ、元々APサーバにあるnginxをロードバランサとして稼働するインスタンスに生まれ変わらせてみることにしました。

というわけで、今回紹介する対象範囲は以下のとおりです。DB/Redisに関しては、色々情報があると思うので、他をあたってもらえればと思います。

Mastodonの情報を読み解く

そもそも、MastodonのDocker版構成情報ってどうなってるんでしょう?って思ってgithubの内容を参照しました。latestであれば、以下ページにdocker-compose.ymlの情報があります。
https://github.com/tootsuite/mastodon/blob/master/docker-compose.yml

この中で、DB/Redis/Elasticsearchを除く3つのコンポーネントについて読み解きをしてみようと思います。

Mastodon-webの構造

Mastodon-webはruby on railsで処理されたコンテンツを応答するためのWebサーバで、内部ではpumaと言うソフトウェアが動いています。pumaが全部のWeb処理をしてるわけではなく、静的コンテンツの処理は前段のnginxが行う形になっています。コンテナは3000/tcpで待ち受けしており、publicコンテンツの内、assets, packs, system3つのディレクトリが永続領域として設定されています。

Mastodon-streamingの構造

Mastodon-streamingはタイムラインのストリーミング処理を行っているサーバで、これがうまく機能しないとタイムラインの動的更新ができません。コンテナは4000/tcpで待ち受けしており、webサーバとは異なり、永続領域の設定はありません。

Mastodon-sidekiqの構造

SidekiqはMastodonの中でキュー処理を行う機能を有していて、その機能もdefault, mailers, pull, pushの4種類が存在します。キュー構成は柔軟に行うことができ、1本のキューに複数の機能をもたせることができれば、また複数のキューに分割させることもできます。

public領域の内、packs/systemの2領域に対する永続領域設定があります。

全体を読んでみるとこんな構成になっていた

docker-composeのYAMLファイルを眺める限り、こんな構成になっているのが読み取れました。
外のnginxとやり取りするコンテナがExternal-networkへ接続しています。そして、全サーバ共通でInternal-Networkに接続しており、どうやら複数セグメントで構成される感じになってました。

永続ボリュームに関してもそれぞれ必要なところだけ設定がなされており、実はバージョン1.xの頃と比べると構成がだいぶしっかりしたものになっています。

構成を設計する

ネットワーク全体の概要

全体の構成としては以下の通りになります。

今回私はネットワークコンポーネントに「Project Calico」という製品を導入しており、それをカスタマイズして10.240.0.0/16空間をコンテナ用ネットワークとして展開しています。
k8s環境では、大まかに3つのネットワーク階層が構成されることが多いです。

  • 物理ネットワーク
    • 所謂ホストが接続するネットワークです。一部のシステムコンテナがこれにブリッジ接続をしていたりします。外部ネットワークをやり取りする際はここが中継地点になります。
    • 基本的にはポート払い出しによって外部との接続を担保します。
  • クラスタネットワーク
    • 主にk8sリソースの「Service」リソースが使用します。所謂負荷分散クラスタのVirtual-IPが使われる専用空間だと思っていただければよいのかなと思います
    • Virtual-IPのことをここではCluster IPと呼びますが、残念ながらそのままではCluster IPと外部の接続はできません。あくまでk8sコンテナ側で呼び出すIPと考えたほうが良いです。
    • デフォルト設定ではCluster IPとのICMP通信は許可されておらず、本当のサービスポートとしかアクセスができません。なので、Pingが通らないのは仕様ということでびっくりなさらぬようにー。
  • コンテナネットワーク
    • ネットワークコンポーネントが払い出すネットワークです。デフォルト設定は製品により異なります。
      • 例えば、weaveの場合は一気通貫の12bitマスクセグメントが1つ用意されます。
      • 例えば、Project Calicoの場合は全体として16bitマスク、ノード毎に24bitマスクのセグメントが用意されます。
    • ユーザコンテナやdashboard、kube-dnsがこのネットワークに接続します。
    • ホストも仮想ブリッジを形成してこのネットワークに接続しています。

フロントアクセス、バックエンドアクセスに関しては後述していますので、続きを読んでいただければと思います。

フロントアクセス部分の通信設計

フロント部分のネットワーク通信は以下のようになります。外部サーバからのアクセスは今回、NodePortモードでサービスを構成することにより、クラスタを構成するノード全てに待ち受けポートを配備する構成とします。さらに上流のMastodonフロントエンドである旧APサーバをロードバランサ兼一部コンテンツWebサーバとして動作させ、全体として可用性を向上させた構成とします。

バックエンドの通信設計

バックエンドの通信部分は以下の通りとしています。

MastodonコアエンジンとDB/Redis間は直接コンテナへ通信するのではなく、Serviceに割り当てられたFQDNを通じてアクセスする方針とします。これは、設定変更のオーバーヘッドを減らすためです。

k8s環境には、kube-dnsというDNSサーバコンテナが存在しており、これがコンテナネットワークに接続されています。ユーザが作成するすべてのコンテナはこれに対して名前解決をするようレゾルバが設定されていて、FQDNも「<コンテナ名>.<名前空間>.svc.cluster.local 」と言う形で割り当てられ、kube-dns内に登録されています。

コンテナは再作成されるたびに名称が変わったりIPアドレスが変わったりします。Serviceはそれを緩衝してくれる存在と考えればよいかと思います。

ストレージ設計

ストレージ設計ですが、前段に記載したdocker-compose.ymlで読み取った内容に従って構成を行います。元々、旧APサーバはNFS NAS上にmastodonユーザのホームディレクトリを作成してマウントしており、それに合わせる形でディスクマウントの構成を作っています。

コンテナからは永続ボリュームを使用してマウントします。hostPathを使うのも手ですが、それをやると今後ノードが増えるたびにノードに対してNFS設定をしなければならなくなるので、永続ボリュームの使用があるべき姿なのかなと思います。

複数台からアクセスできるよう、マウント方式はReadWriteManyを指定します。

イメージ設計

コンテナのベースとなるイメージですが、下図の流れで作成・展開します。

本当は、mastodonのgithubサイトから手に入るDockerfileを用いて構築したかったのですが、残念ながらうまくいかず、どうしてもPostgreSQLのライブラリが動いてくれませんでした。

というわけで、今回コンテナの構築に際しては私がコレまでに構築して動かしてきたCentOSベースの流れをそのままコンテナに転用したやり方で作っています。恐らくは、これは最適解ではないと思っていて、やっぱりいちばん効率の良いやり方としては、githubサイトからDockerfileを落としてきて、それをカスタマイズして作るのが良いのでしょう。

ちなみに作るイメージは1つだけでいいです。そのイメージでpumaもsidekiqもstreamingもどれも動きます。

コンテナ設計

コンテナの設計内容としては以下の通りとなります。 コンテナの構築にはReplicationControllerを使うことにしました。理由としては、記述が最もシンプルかつ、ノード台数の変更が容易に効く点が挙げられます。Scale設定をすることにより、台数を増やすことも減らすことも止めることも出来るのは非常に便利です。

また、他のリソースのように複雑なYAML書式が必要ないという点も嬉しいポイントでした。

コンテナを作成する上での留意ポイント

コンテナ・・・・・k8sではPodと呼ぶのですが、これの定義を行う上で必要なパラメータは原則docker-compose.yamlで定義された内容に従えばいいと思います。ただ私の場合、1年ぐらい前からインスタンスを動かしてきた関係上、書式が一部古くなっているところがあります。

そこは、本来バージョンに合わせて変えるべきですが、私の場合は実績を重視して、コンテナの定義をsystemdの記載に合わせています。以下、参考にした箇所についてまとめていきます。

Mastodon-webの構成

コンテナのEnvironmentですが、既存環境では多くのリソースを搭載した1台のサーバで担保していたため、WEB_CONCURRENCYとMAX_THREADSを8にしていますが、今回複数分散構成にするため、いずれも 2 に設定しています。

その他、実行コマンドやWorkingDirectoryはここに書かれている内容を使用することになります。

Mastodon-Streamingの構成

基本的な路線はWebと同じですが、設定として拾う箇所は圧倒的に少ないです。
現行バージョンではどうやらnpmではなく、yarnを使用することが推奨されているようです。デベロッパー界隈には居ない人間なので、いまいちこの辺りがピンときてませんw

Mastodon-Sidekiqの構成

Mastodonインスタンスを構築されている方はおわかりかと思いますが、Sidekiqのキュー配置を設定するのは「ExecStart」の箇所になります。ここで、-qを使って何のキューを適用するかを記載することになります。

また、DB_POOLと-cオプションで指定する数値は一致させる必要があります。
こちらも、複数分散構成にするため、数値を引き下げる必要があります。 このようにして、設定内容を参考にしながら各種リソースのYAMLファイルを書いていくことになります。

イメージの作成

任意のディレクトリを作成して、まずはOSベースイメージの作成を行います。
Dockerfileを以下のように作成します。途中、mastodonユーザのUID/GIDを設定していますが、これは既存環境のサーバに割り当てられているUID/GIDに合わせるためです。

最後、3000番ポートと4000番ポートをEXPOSEして、どちらの用途にもイメージが使用できるようにしています。

FROM centos:centos7
MAINTAINER BLUECORE.NET <words@bluecore.net>

RUN yum -y update && yum clean all
RUN yum -y install openssl openssl-devel libxml2-devel ImageMagick libxslt-devel git curl file gcc gcc-c++ protobuf-compiler protobuf-devel readline-devel libicu-devel libidn-devela && yum clean all
RUN rpm -ivh https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm
RUN rpm -Uvh http://li.nux.ro/download/nux/dextop/el7/x86_64/nux-dextop-release-0-5.el7.nux.noarch.rpm
RUN yum -y install ffmpeg ffmpeg-devel nodejs npm yarn && yum clean all
RUN yum -y install net-tools rubygem-redis rsync postgresql && yum clean all
RUN groupadd mastodon -g 1001
RUN adduser mastodon -u 1001 -g 1001

EXPOSE 3000
EXPOSE 4000

続いて、このDockerfileを用いてコンテナを1台作成します。

# docker build -t mstdn/engine-201804xxa .

コンテナへログインします。

# docker run -i -t mstdn/engine-201804xxa /bin/bash

この時点で、すでにmastodonユーザは完成しており、「su – mastodon」でmastodonユーザにもなれます。/home/mastodonも作成済みの状態です。そこへ、既存サーバのホームディレクトリをコピーして持ってきます。

# su - mastodon
$ vi excludelist.lst
-----
/home/mastodon/live/public/assets/
/home/mastodon/live/public/system/
/home/mastodon/live/public/packs/
/home/mastodon/backup/
-----

$ rsync -avz sns-ap:/home/mastodon/ /home/mastodon --exclude-from excludelist.lst

で、コピーしておいてなんですが、rbenvとrubyを入れ直します。入れっぱなしで動かそうとすると、エラーが発生したため、この措置をとることにしました。

■rbenvディレクトリ内クリーンナップ
$ rm -rf ~/.rbenv/*
■rbenv導入
$ git clone https://github.com/sstephenson/rbenv.git ~/.rbenv
$ cd ~/.rbenv && src/configure && make -C src && cd ~

■Ruby導入
$ git clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-build
$ rbenv install 2.5.0 && rbenv global $_ && rbenv rehash

ruby実行環境が整ったので、改めてアプリケーションインストール作業を実施します。

■リビルド
$ gem install bundler
$ bundle install --deployment --without development test
$ yarn install --pure-lockfile

■.env.productionファイルの修正
$ vi .env.production
-----
REDIS_HOST=ext-redis-server.bluecore-net.svc.cluster.local
REDIS_PORT=6379

DB_HOST=pgset-primary.bluecore-net.svc.cluster.local
DB_PORT=5432
----

ここでミソなのが、REDIS_HOSTとDB_HOST指定のところです。Serviceで定義するホスト名を指定しています。Serviceリソースは仮にNodePort方式で定義しても、クラスタIPが提供されるようになっています。クラスタ内であれば、そこから普通にポートアクセスが行えるようになっています。

先にも言ったように、コンテナ環境はコロコロIPアドレスやホスト名が変わるので、そういう環境においては、このサービスリソースが提供するFQDNに従うことが望ましいです。

assets:precompileの実行をします。

■assets:precompileの実行
$ RAILS_ENV=production bundle exec rails assets:precompile

これで下地ができたので、「Ctrl+P」+「Ctrl+Q」を押してコンソールを抜けます。
次に構成確定を一度行います。

■dockerイメージのCommit
# docker ps -a|grep mstdn
 ⇒Build中のDocker IDを押さえる

# docker ******** commit
 ⇒少し時間がかかる

上記、commit操作には少し時間がかかると書いていますが、実際には結構なファイルの操作をしているため、十数分ぐらいかかると思います。結果として、私の環境ではでっかいコンテナイメージができてしまいました・・・トホホ。

# docker images|grep mstdn
mstdn/engine-20180427l latest 8e5ea1322129 40 hours ago 3.82 GB

一旦バックアップの意味もこめて、プライベートリポジトリに突っ込みます。

■リポジトリに突っ込む
# docker tag ******** 192.168.100.171:5000/mstdn/engine:201804xxb
# docker push 192.168.100.171:5000/mstdn/engine:201804xxb

このイメージをベースに、今度はデフォルトユーザをmastodonにするためのDockerfileを適用してイメージをBuildします。

■デフォルトユーザをmastodonにする
以下のようなDockerfileを作成する
-----
FROM 192.168.100.171:5000/mstdn/engine:201804xxb
USER mastodon
-----

# docker build -t 192.168.100.171:5000/mstdn/engine:201804xxc

これで、イメージが出来上がりました。次はk8sリソースの作成です。

リソースパラメータ設計

作成するリソース

作成するリソースはこれだけあります。

  • ReplicationController
    • puma
    • sidekiq-defaults
    • sidekiq-others
    • streaming
  • Service
    • puma
    • streaming
  • PersistentVolume
    • assets用
    • system用
    • packs用
  • PersistentVolumeClaim
    • asset用
    • system用
    • packs用

ReplicationController: mstdn-engine-puma.yaml

apiVersion: v1
kind: ReplicationController
metadata:
 name: mstdn-puma
spec:
 replicas: 2
 selector:
   app: mstdn-engine-puma
 template:
   metadata:
     labels:
       app: mstdn-engine-puma
 spec:
   containers:
   - name: mstdn-puma
     workingDir: /home/mastodon/live
     resources:
       requests:
         memory: "768Mi"
         cpu: "200m"
       limits:
         memory: "768Mi"
         cpu: "300m"
     image: 192.168.100.171:5000/mstdn/engine:201804xxcj
     volumeMounts:
     - name: mstdn-assets
       mountPath: "/home/mastodon/live/public/assets"
     - name: mstdn-packs
       mountPath: "/home/mastodon/live/public/packs"
     - name: mstdn-system
       mountPath: "/home/mastodon/live/public/system"
     env:
     - name: RAILS_ENV
       value: "production"
     - name: PORT
       value: "3000"
     - name: WEB_CONCURRENCY
       value: "2"
     - name: MAX_THREADS
       value: "3"
     ports:
     - containerPort: 3000
       protocol: TCP
     command: ["/home/mastodon/.rbenv/shims/bundle"]
     args: ["exec","puma","-C","config/puma.rb"]
     #command: ["tail"]
     #args: ["-f","/dev/null"]
     volumes:
     - name: mstdn-assets
       persistentVolumeClaim:
         claimName: pvc-nfs-mstdn-front-assets
     - name: mstdn-packs
       persistentVolumeClaim:
         claimName: pvc-nfs-mstdn-front-packs
     - name: mstdn-system
       persistentVolumeClaim:
         claimName: pvc-nfs-mstdn-front-system

ReplicationController: mstdn-engine-sidekiq-defaults.yaml

apiVersion: v1
kind: ReplicationController
metadata:
 name: mstdn-sidekiq-default
spec:
 replicas: 2
 selector:
   app: mstdn-sidekiq-default
 template:
   metadata:
     labels:
       app: mstdn-sidekiq-default
   spec:
     containers:
     - name: mstdn-sidekiq
       workingDir: /home/mastodon/live
       resources:
         requests:
           memory: "512Mi"
           cpu: "200m"
         limits:
           memory: "512Mi"
           cpu: "300m"
       image: 192.168.100.171:5000/mstdn/engine:201804xxc
       volumeMounts:
       - name: mstdn-packs
         mountPath: "/home/mastodon/live/public/packs"
       - name: mstdn-system
         mountPath: "/home/mastodon/live/public/system"
       env:
       - name: RAILS_ENV
         value: "production"
       - name: DB_POOL
         value: "20"
       command: ["/home/mastodon/.rbenv/shims/bundle"]
       args: ["exec","sidekiq","-c","20","-q","default"]
       volumes:
       - name: mstdn-packs
         persistentVolumeClaim:
           claimName: pvc-nfs-mstdn-front-packs
       - name: mstdn-system
         persistentVolumeClaim:
           claimName: pvc-nfs-mstdn-front-system

ReplicationController: mstdn-engine-sidekiq-other.yaml

apiVersion: v1
kind: ReplicationController
metadata:
 name: mstdn-sidekiq-other
spec:
 replicas: 2
 selector:
   app: mstdn-sidekiq-other
 template:
   metadata:
     labels:
       app: mstdn-sidekiq-other
 spec:
   containers:
   - name: mstdn-sidekiq
     workingDir: /home/mastodon/live
     resources:
       requests:
         memory: "1024Mi"
         cpu: "200m"
       limits:
         memory: "1024Mi"
         cpu: "300m"
     image: 192.168.100.171:5000/mstdn/engine:201804xxc
     volumeMounts:
     - name: mstdn-packs
       mountPath: "/home/mastodon/live/public/packs"
     - name: mstdn-system
       mountPath: "/home/mastodon/live/public/system"
     env:
     - name: RAILS_ENV
       value: "production"
     - name: DB_POOL
       value: "20"
     command: ["/home/mastodon/.rbenv/shims/bundle"]
     args: ["exec","sidekiq","-c","20","-q","mailers","-q","pull","-q","push"]
     volumes:
     - name: mstdn-packs
       persistentVolumeClaim:
         claimName: pvc-nfs-mstdn-front-packs
     - name: mstdn-system
       persistentVolumeClaim:
         claimName: pvc-nfs-mstdn-front-system

ReplicationController: mstdn-engine-streaming.yaml

apiVersion: v1
kind: ReplicationController
metadata:
 name: mstdn-streaming
spec:
 replicas: 2
 selector:
   app: mstdn_engine_streaming
 template:
   metadata:
     labels:
       app: mstdn_engine_streaming
   spec:
     containers:
     - name: mstdn-streaming
       workingDir: /home/mastodon/live
       resources:
         requests:
           memory: "512Mi"
           cpu: "200m"
         limits:
           memory: "512Mi"
           cpu: "300m"
       image: 192.168.100.171:5000/mstdn/engine:201804xxc
       env:
       - name: NODE_ENV
         value: "production"
       - name: PORT
         value: "4000"
       ports:
       - containerPort: 4000
         protocol: TCP
       command: ["/usr/bin/npm"]
       args: ["run","start"]

Service: mstdn_ext_puma.yaml

apiVersion: v1
kind: Service
metadata:
 name: ext-mstdn-puma
spec:
 type: NodePort
 ports:
 - nodePort: 31300
   port: 3000
   protocol: TCP
   targetPort: 3000
 selector:
   app: mstdn-engine-puma

Service: mstdn_ext_streaming.yaml

apiVersion: v1
kind: Service
metadata:
 name: ext-mstdn-streaming
spec:
 type: NodePort
 ports:
 - nodePort: 31400
   port: 4000
   protocol: TCP
   targetPort: 4000
 selector:
   app: mstdn_engine_streaming

PersistentVolume: mstdn-pv-system.yaml

apiVersion: v1
kind: PersistentVolume
metadata:
 name: nfs-mstdn-front-system
 labels:
   name: pv-nfs-mstdn-front-system
spec:
 capacity:
   storage: 40Gi
 accessModes:
 - ReadWriteMany
 nfs:
   server: 192.168.100.129
   path: "/Pool-1/share_root/sns_ap/live/public/system"

PersistentVolume: mstdn-pv-packs.yaml

apiVersion: v1
kind: PersistentVolume
metadata:
 name: nfs-mstdn-front-packs
 labels:
   name: pv-nfs-mstdn-front-packs
spec:
 capacity:
   storage: 40Gi
 accessModes:
 - ReadWriteMany
 nfs:
   server: 192.168.100.129
   path: "/Pool-1/share_root/sns_ap/live/public/packs"

PersistentVolume: mstdn-pv-assets.yaml

apiVersion: v1
kind: PersistentVolume
metadata:
 name: nfs-mstdn-front-assets
 labels:
   name: pv-nfs-mstdn-front-assets
spec:
 capacity:
   storage: 40Gi
 accessModes:
 - ReadWriteMany
 nfs:
   server: 192.168.100.129
   path: "/Pool-1/share_root/sns_ap/live/public/assets"

PersistentVolumeClaim:mstdn-pvc-system.yaml

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
 name: pvc-nfs-mstdn-front-system
spec:
 accessModes:
 - ReadWriteMany
 storageClassName: ""
 resources:
   requests:
     storage: "40Gi"
   selector:
     matchLabels:
       name: pv-nfs-mstdn-front-system

PersistentVolumeClaim:mstdn-pvc-packs.yaml

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
 name: pvc-nfs-mstdn-front-packs
spec:
 accessModes:
 - ReadWriteMany
 storageClassName: ""
 resources:
   requests:
     storage: "40Gi"
 selector:
   matchLabels:
     name: pv-nfs-mstdn-front-packs

PersistentVolumeClaim:mstdn-pvc-assets.yaml

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
 name: pvc-nfs-mstdn-front-assets
spec:
 accessModes:
 - ReadWriteMany
 storageClassName: ""
 resources:
   requests:
     storage: "40Gi"
   selector:
     matchLabels:
       name: pv-nfs-mstdn-front-assets

リソースの作成

リソースの作成は「PersistentVolume」⇒「PersistentVolumeClaim」⇒「ReplicationController」⇒「Service」の順で作る感じになります。

■永続ボリューム作成
# kubectl create -f mstdn-pv-assets.yaml -n bluecore-net
# kubectl create -f mstdn-pv-packs.yaml -n bluecore-net
# kubectl create -f mstdn-pv-system.yaml -n bluecore-net

■永続ボリューム要求作成
# kubectl create -f mstdn-pvc-assets.yaml -n bluecore-net
# kubectl create -f mstdn-pvc-packs.yaml -n bluecore-net
# kubectl create -f mstdn-pvc-system.yaml -n bluecore-net

■コンテナ作成
# kubectl create -f mstdn-engine-puma.yaml -n bluecore-net
# kubectl create -f mstdn-engine-sidekiq-default.yaml -n bluecore-net
# kubectl create -f mstdn-engine-sidekiq-other.yaml -n bluecore-net
# kubectl create -f mstdn-engine-streaming.yaml -n bluecore-net

■サービス作成
# kubectl create -f mstdn_ext_puma.yaml -n bluecore.net
# kubectl create -f mstdn_ext_streaming.yaml -n bluecore.net

動作確認

「kubectl get pods -n bluecore-net (私の環境の場合)」で確認するのも良いですし、dashboardを見るというのも手です。取り敢えず、CrashBackOffが発生していないか、リブートが発生していないかどうかを見ましょう。

# kubectl get pods -n bluecore-net
NAME READY STATUS RESTARTS AGE
mstdn-puma-gbw58 1/1 Running 0 6h
mstdn-puma-zshvh 1/1 Running 0 6h
mstdn-sidekiq-default-fhzhf 1/1 Running 0 6h
mstdn-sidekiq-default-sxzn6 1/1 Running 0 6h
mstdn-sidekiq-other-79rr7 1/1 Running 0 6h
mstdn-sidekiq-other-ftjvt 1/1 Running 0 6h
mstdn-streaming-j8mll 1/1 Running 0 6h
mstdn-streaming-w75w2 1/1 Running 0 6h

取り敢えずみんなRunningになってれば大丈夫かな。
再起動を繰り返す場合は、割り当てられているホスト上の/var/log/messagesを見るとか、kubectl logs コマンドを叩いてみるとかすると良いかもしれません。

また、dashboardなんかは結構便利で、サービス画面を見ることで、正しくサービスポートにPodsが紐ついているかどうかが確認できます。

上記例はpumaサービスリソースの画面ですが、もしEndpointsに1台もPodsが表示されていなければ、その場合はラベルセレクターの設定を間違っているか、ReplicationControllerのTemplate部分でラベル設定を間違えているかの何れかを起こしているので要チェックです。

nginxの設定変更

既存APサーバで以下の通り設定変更を行います。(対象ファイルは/etc/nginx/conf.d/mstdn.conf)

map $http_upgrade $connection_upgrade {
 default upgrade;
 '' close;
}

upstream mstdn-puma {
 server 192.168.100.132:31300;
 server 192.168.100.143:31300;
 server 192.168.100.171:31300;
 server 192.168.100.172:31300;
 server 192.168.100.173:31300;
}
upstream mstdn-streaming {
 server 192.168.100.132:31400;
 server 192.168.100.143:31400;
 server 192.168.100.171:31400;
 server 192.168.100.172:31400;
 server 192.168.100.173:31400;
}
 (省略)
server {
 listen 443 ssl http2;
 listen [::]:443 ssl http2;
 server_name social.bluecore.net;
  (省略)

location ~ ^/(packs|system/media_attachments/files|system/accounts/avatars) {
 add_header Cache-Control "public, max-age=31536000, immutable";
 try_files $uri @proxy;
 }

  location @proxy {
 (省略)
  proxy_pass http://mstdn-puma;
 (省略)
  }

 location /api/v1/streaming {
 (省略)
  proxy_pass http://mstdn-streaming;
 (省略)
  }

  error_page 500 501 502 503 504 /500.html;
}

nginxを再起動します。

# systemctl restart nginx

インスタンスのURLをブラウザで叩き、無事にアクセスできたら成功です。

実際、どう?

軽いです。動作がかなり軽いです。VM単騎でやってた時は、どうしてもCPUがうまく使用しきれてなかったようで、それによるパフォーマンスの目詰まりが若干あったのですが、コンテナ化したことで、複数用意したコンテナがプロセスとして動くため、CPUが効率良く使えるようになったのかなーという気がします。

レスポンスが早くなったというのが正直なところかもしれないですが、まぁまぁアクティブユーザが私だけなんで・・・・・(´Д`)ハァ…

でも、コレをきっかけにかなりのことが楽しめたのと、理解を深めることができたのと、いろいろな意味で美味しい日々が過ごせました。ありがとう!Mastodon!ありがとう!Kubernetes!ありがとう!Docker!ってところですかね?

おまけ:ログの扱いについて

k8sでは、アプリケーションログはコンテナの標準出力経由からsyslogに吐き出されています。取り敢えず、Sidekiqで吐き出してるログは以下のように割り当てられたホストの /var/log/messages にこんな形で吐き出されていることを確認しました。

Apr 30 14:00:32 maitreya journal: 2018-04-30 05:00:32.241 [INFO][92] health.go 150: Overall health summary=&health.HealthReport{Live:true, Ready:true}
Apr 30 14:00:33 maitreya journal: 2018-04-30 05:00:33.279 [INFO][92] health.go 150: Overall health summary=&health.HealthReport{Live:true, Ready:true}
Apr 30 14:00:38 maitreya journal: 2018-04-30T05:00:38.715Z 1 TID-go4w5jpc8 ProcessingWorker JID-47dd315e6335e01e6733c3c8 INFO: start
Apr 30 14:00:38 maitreya journal: 2018-04-30T05:00:38.746Z 1 TID-go4w5hzio DistributionWorker JID-c497ce85cab8563c19e216f0 INFO: start
Apr 30 14:00:38 maitreya journal: 2018-04-30T05:00:38.750Z 1 TID-go4w5jpc8 ProcessingWorker JID-47dd315e6335e01e6733c3c8 INFO: done: 0.035 sec
Apr 30 14:00:38 maitreya journal: [active_model_serializers] Rendered REST::StatusSerializer with ActiveModelSerializers::Adapter::Attributes (13.72ms)
Apr 30 14:00:38 maitreya journal: 2018-04-30T05:00:38.778Z 1 TID-go4w5hzio DistributionWorker JID-c497ce85cab8563c19e216f0 INFO: done: 0.032 sec
Apr 30 14:00:41 maitreya journal: 2018-04-30 05:00:41.052 [INFO][92] int_dataplane.go 733: Applying dataplane updates
Apr 30 14:00:41 maitreya journal: 2018-04-30 05:00:41.052 [INFO][92] ipsets.go 222: Asked to resync with the dataplane on next update. family="inet"
Apr 30 14:00:41 maitreya journal: 2018-04-30 05:00:41.052 [INFO][92] ipsets.go 253: Resyncing ipsets with dataplane. family="inet"
Apr 30 14:00:41 maitreya journal: 2018-04-30 05:00:41.054 [INFO][92] ipsets.go 295: Finished resync family="inet" numInconsistenciesFound=0 resyncDuration=1.74325ms
Apr 30 14:00:41 maitreya journal: 2018-04-30 05:00:41.054 [INFO][92] int_dataplane.go 747: Finished applying updates to dataplane. msecToApply=2.034095
Apr 30 14:00:42 maitreya journal: 2018-04-30 05:00:42.240 [INFO][92] health.go 150: Overall health summary=&health.HealthReport{Live:true, Ready:true}

中にはProject Calicoのログとか混ざってたりするのですが、「journal」から始まるメッセージは基本的にk8s絡みのものであり、bundle系列のログはだいたい吐き出されています。

コンテナの性質上、コアとなるプロセスはフォアグラウンド動作させてなんぼなので、大抵は標準出力に表示させてるだろうと思われるのですが、例えばサーバを単純にコンテナ化させた場合なんかは、ログ記録の設定等残っている可能性があり、注意が必要です。(永続ボリューム組まない限り落としたら吹っ飛ぶし、そもそもそのために永続ボリューム構成するのも馬鹿らしいし)

とまぁ、ログが取れる素地はそれなりにあるようなので、そこでログ監視すると、それなりに良いのかもしれません。

さらに高度なログ管理をしたければ、その場合はfulentdなどの力を借りる必要があると思われます。

ついでに

Kubernetesは非常にリソース使用が少なく、検証リソースが少ない場合、もしそれがミドルウェア寄りの内容であれば、Kubernetesを使ったりDockerを使ったりするのも一つの手なのかなと思いました。或いは、私のように自宅サーバで運用してる中で、ある程度構成が固まって触らないものとか。

サーバとしての運用はそれはそれで必要なんですが、「このサービスにあまりリソース切り出したくないなー」「本番化してだいぶ立つけどもう少し可用性上がらないかなー」とか思ったときに役に立つなぁと。あくまで自宅サーバ内の話としてではありますけれども。

そういう意味でDocker/docker-compose/Kubernetesを学ぶのって良いのかもしれません。
コンテナ環境として、ミニマムからエンタープライズちょい手前ぐらいのレンジで多くの学びが得られるのかなと思います。

エンタープライズ向けとしては、コレを進化させたOpenShiftと言うプラットフォームがRedHatから出ていますが、なんだか要求リソース量が半端なくて、ちょっと組むには組めなかったです。
(業務の中で評価しようとしたんだけど、プロキシ越しだとどーしてもうまく組めなかった)

その他、課題など。

実はまだ完全に移行できたわけではなく、いくつか不具合があったりします。その辺りは追々解決を進めていきたいなぁとは思ったりしています。

  • IPv6に対応できてない。コンテナをIPv6に対応させるのって意外と難しい。実は今回、ネットワークの機能をweaveからProject Calicoに変更した理由がこれ。
  • Redis SENTINELとRedis Replicationの関係性、Master/Slaveの読み書きの区別の付け方がわかっておらず、ほとんど機能が無駄になってる。
  • ログ管理の高度化。先述の通りfluentdの力を使おうと思うのだけど、fluentd提供のYAML適用したらマスターがハングアップするという事象に遭遇し、それ以来トラウマになっている・・・・(´Д`)ハァ…

あ、ところで

こんなことやってんの俺だけだろうなーふふ~んとか思ってたら、すでにやっておられる方がいらっしゃいました。さすがっす!しかも一年前ときたもんだ。

Not Found - ハラミTech
技術系ブログです

やっぱり自分自身色々遅れてるんかなーとか、まだまだ上には上がいるんだなーとか、そういうのを痛感しながら今日も過ごしてまぁす。

タイトルとURLをコピーしました