カテゴリー
Docker Kubernetes Mastodon Ubuntu

MastodonをDocker Compose環境から、Kubernetes環境にお引っ越しした

今まで数年間、Ubuntuで立てたシングルノードのDocker Compose上でMastodonを動かしていたのだが、世の中の流れに合わせてKubernetes上に移動させた。

docker-compose.ymlから自動的にKubernetesのマニフェストファイルに変換してくれるコマンドもあるけども、勉強のために手動で1個ずつ置き換えていった。

出来上がったk8s上のMastodon構成

PostgreSQLのデータをNFS上に移動する

元々はDocker Composeのローカルストレージ上に置いていたPostgreSQLのデータをNFSの共有ディレクトリに移動させた。

  1. NFSサーバーでディレクトリを共有する
  2. 共有ディレクトリ上で新DBを初期化する
  3. 移行元のDBサーバーでpg_dumpコマンドを用いてデータをエクスポートする
  4. 新DBでエクスポートしたデータをインポートする

同じCPUアーキテクチャ&バージョンであればDBのデータディレクトリをまるごとNFS上に持って行くだけでもデータベース移行出来るのだが、今回はDBバージョンが異なるのと、このタイミングで複数のDBを統合していたのを用途ごとに分割したかったので、上記のようなエクスポート&インポートを用いている。

このとき、Docker Compose上から書き込んでいたデータが、Kubernetes上のプロセスから読めないエラーメッセージが多発したので、NFSのマウントオプションでバージョン3を使う`mountOptions: nfsvers=3`にして回避した。
※NFSv4のACLベースのアクセス権の考え方に引っかかっていた模様。NFSv3まではrwxビットだけなので。。。

apiVersion: v1
kind: PersistentVolume
metadata:
  name: postgres-mstdnblue
  annotations:
    name: postgres-mstdnblue
spec:
  capacity:
    storage: 50Gi
  accessModes:
    - ReadWriteOnce
  mountOptions:
    - nfsvers=3
  nfs:
    server: 192.168.8.253
    path: /mnt/tank/share/nfs/k8s/postgres-mstdnblue-data

PV/PVCを作る

NFSv3の共有ディレクトリをPodに引き渡すためのPVCであるが、大規模環境だとサイズや速度などで階層化しつつ均一なボリュームをまとめて作って適切にPVCで引きこむような設計をすると想像する。(その方がストレージ設計者とコンピューティングリソースの提供者の役割分担がはっきりしやすい&下のレイヤーを隠蔽しやすい)

ただ、今回の個人用の環境だとどこにどのデータがあるかはっきりしたほうが良いので、NFSディレクトリの名前とannotationsを1対1で対応させてどのPVがどういうデータを持っていて、それをどのPVCで引きこむのか分かりやすいようにしている。

名前でストレージ領域の用途が分かりやすくしたPV
名前が体を表す的なPVC

複数のテナントが相乗りするような環境だとPVが再利用されないようにしたり、データの隠匿/暗号化/リサイクル時の確実な消去なども求められるだろうなとも思ったがスルー。

Redisの移行を考える

MastodonのRedis上には他の連合(フェデレーション)を結んでいるサーバーとのトゥートの伝搬等のキューイング情報やジョブ実行状態、ユーザーが最初にアクセスしたときのホームタイムラインなどのキャッシュが保持されている。

今回は御一人様インスタンスでキャッシュやジョブ状態は飛んでしまって構わないとしたので、Redisの情報は引き継いでいかないことにした。

ConfigMapに環境変数を持ち込む

Docker Composeだとini形式(変数名=値が並んだテキストファイル)で各種プロセスに引き渡す環境変数を持っていたが、KubernetesだとConfigMapなりSecretなりにする必要がある。

地道にYAML形式に書き直すことも考えたが、面倒くさかったのでいい手がないか調べたらこんな手順が。

$ kubectl create configmap key-value-sample -n configmap-example --from-env-file=sample.ini

上記のコマンドを使ってConfigMapを作成したらenvFromでPodに引き渡すことが出来る。

v1.6 で追加された envFrom

v1.6.0 で下記のように envFrom という項目で ConfigMap または Secret の内容を一度に同名または prefix 付きの環境変数として読み込むことができようになりました。

Kubernetes: ConfigMap / Secret の内容を一度に環境変数として読み込む (envFrom)

https://qiita.com/tkusumi/items/cf7b096972bfa2810800

Mastodonの各種プロセスを起動する

Sidekiq/Streaming/Webのセットはほとんど一緒で、/mastodon/public/systemを共有しながら、bundleなりnodeでプログラムが動いている感じの構成になる。

DeploymentでNFS上のボリュームをマウントしたPodを起動して、外部公開するWebsocketなりHTTPなりのサービスエンドポイントをServiceとして定義してやれば良い。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-mstdnblue
  labels:
    app: web-mstdnblue
spec:
  selector:
    matchLabels:
      app: web-mstdnblue
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: web-mstdnblue
    spec:
      hostname: web-mstdnblue
      subdomain: local
      containers:
      - image: tootsuite/mastodon
        name: web-mstdnblue
        command: ['bash']
        args: ['-c','rm -f /mastodon/tmp/pids/server.pid; bundle exec rails s -p 3000 -b 0.0.0.0']
        envFrom:
          - configMapRef:
              name: configmap-mstdnblue
        volumeMounts:
        - name: mastodon-public-mstdnblue
          mountPath: /mastodon/public/system
        envFrom:
          - configMapRef:
              name: configmap-mstdnblue
      volumes:
      - name: mastodon-public-mstdnblue
        persistentVolumeClaim:
          claimName: mastodon-public-mstdnblue-pvc
---
apiVersion: v1
kind: Service
metadata:
  name: web-mstdnblue
spec:
  type: LoadBalancer
  selector:
    app: web-mstdnblue
  ports:
  - protocol: TCP
    port: 3000
    targetPort: 3000

最終的なマニフェスト構成

たぶん、ちゃんとした管理者のいる環境だとPVとPVC あたりに管理者の分担や責任分界線が出てきそう。

それ以外はプロセスの種類ごとに分けてはみたものの、Serviceあたりでアプリケーションエンジニアとネットワークエンジニアの縄張り争いが出そうだなとも思った。

マニフェストファイルの分け方には流儀があると感じた

まとめ

Kubernetes環境にアプリケーションを持って行くとしたら、元々Docker前提で綺麗に分かれているものでもそれなりに技量がいる。

もしもレガシーなアプリケーションを持ち込むとしたら作り直した方が早いという言説にも頷ける。

AWS上のALBから、自宅環境のKubernetesクラスターまでトラフィックを持ち込んでいるところはこの記事では省略したがTailscaleを使っていたりする。そのうち書きたい。

カテゴリー
Network Ubuntu

UbuntuでSoftEther VPNサーバを立てる

前提

今回のサーバにはVPN接続を受け付けるNICと、実際に内部でアクセスするためのNICの2つが接続されています。そのため、他のブログなどで書かれているようなブリッジ接続は実施していません。

SoftEtherの導入

$ wget https://www.softether-download.com/files/softether/v4.34-9745-rtm-2020.04.05-tree/Linux/SoftEther_VPN_Server/64bit_-_Intel_x64_or_AMD64/softether-vpnserver-v4.34-9745-rtm-2020.04.05-linux-x64-64bit.tar.gz
$ tar zxvf softether-vpnserver-v4.34-9745-rtm-2020.04.05-linux-x64-64bit.tar.gz
$ cd vpnserver/
$ make
$ sudo cp -r vpnserver /usr/local/
$ sudo chown -R root:root /usr/local/vpnserver/

SoftEtherの自動起動設定

softetherをubuntu18.04にインストールするhttps://qiita.com/rimksky/items/e169f9af83ce472b4ce3
のページを参考にさせて頂きました。

$ vim /etc/systemd/system/vpnserver.service
 [Unit]
 Description=SoftEther VPN Server
 After=network.target auditd.service
 ConditionPathExists=!/usr/local/vpnserver/do_not_run

 [Service]
 Type=forking
 EnvironmentFile=-/usr/local/vpnserver
 ExecStart=/usr/local/vpnserver/vpnserver start
 ExecStop=/usr/local/vpnserver/vpnserver stop
 KillMode=process
 Restart=on-failure
 Hardening
 PrivateTmp=yes
 ProtectHome=yes
 ProtectSystem=full
 ReadOnlyDirectories=/
 ReadWriteDirectories=-/usr/local/vpnserver
 CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_NET_BROADCAST CAP_NET_RAW CAP_SYS_NICE CAP_SYS_ADMIN CAP_SETUID

 [Install]
 WantedBy=multi-user.target
$ sudo systemctl daemon-reload
$ sudo systemctl enable vpnserver.service
$ sudo systemctl start vpnserver.service

SoftEtherのVPNサービスのセットアップ

Windows機にSoftEtherの管理ツールを導入して、そこからGUIで設定しています。

初回ログイン時は管理者パスワードの設定を求められる以外は、後は初期セットアップウィザードで設定して回ればOK。

Let’s EncryptのCertbot導入

$ sudo apt install certbot

Certbotの設定

今回は簡単にHTTP 80番ポートが空いていたのでstandaloneモードでCertbot自身が一時的にHTTPアクセスを受け付けてホスト名を検証する方式で設定します。(80番ポートを他のApacheやNginx等が使っている場合は適合するプラグインを使ってください)

$ sudo certbot --standalone
imksoo@lainhv01:~$ sudo certbot certonly --standalone
 Saving debug log to /var/log/letsencrypt/letsencrypt.log
 Plugins selected: Authenticator standalone, Installer None
 Please enter in your domain name(s) (comma and/or space separated)  (Enter 'c'
 to cancel): hostname.example.com,vpn.example.com
 Obtaining a new certificate
 IMPORTANT NOTES:
 Congratulations! Your certificate and chain have been saved at:
 /etc/letsencrypt/live/hostname.example.com/fullchain.pem
 Your key file has been saved at:
 /etc/letsencrypt/live/hostname.example.com/privkey.pem
 Your cert will expire on 2021-04-08. To obtain a new or tweaked
 version of this certificate in the future, simply run certbot
 again. To non-interactively renew all of your certificates, run
 "certbot renew"
 If you like Certbot, please consider supporting our work by:
 Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
 Donating to EFF:                    https://eff.org/donate-le 
 imksoo@lainhv01:~$

上記でサーバ証明書には複数のドメイン名を持たせるため、certbotコマンドの中でカンマ区切りで複数指定しています。

SoftEtherが利用するサーバ証明書の更新を定期的に実施する

UbuntuのCertbotが定期的にSSL証明書の更新をするやり方をパクりました。SystemdでTimer実行します。

$ vim /lib/systemd/system/renew_vpnserver.timer
 [Unit]
 Description=Renew VPN Server certificate file.
 [Timer]
 OnCalendar=--* 04,16:00:00
 RandomizedDelaySec=43200
 Persistent=true
 [Install]
 WantedBy=renew_vpnserver.target
$ vim /lib/systemd/system/renew_vpnserver.service
 [Unit]
 Description=Renew VPN Server certificate file.
 [Service]
 Type=oneshot
 ExecStart=/usr/local/vpnserver/vpncmd localhost:5555 /server /password:P@ssw0rd /CMD ServerCertSet /LOADCERT:/etc/letsencrypt/live/hostname.example.com/fullchain.pem /LOADKEY:/etc/letsencrypt/live/hostname.example.com/privkey.pem
 PrivateTmp=true

後はTimerとServiceを有効化して実行しておきます。

$ sudo systemctl daemon-reload
$ sudo systemctl enable renew_vpnserver.timer
$ sudo systemctl start renew_vpnserver.timer
$ sudo systemctl start renew_vpnserver.service
$ sudo systemctl status renew_vpnserver.service
 ● renew_vpnserver.service - Renew VPN Server certificate file.
      Loaded: loaded (/lib/systemd/system/renew_vpnserver.service; static; vendor preset: enabled)
      Active: inactive (dead) since Fri 2021-01-08 22:39:20 JST; 6s ago
 TriggeredBy: ● renew_vpnserver.timer
     Process: 695134 ExecStart=/usr/local/vpnserver/vpncmd localhost:5555 /server /password:P@ssw0rd /CMD ServerCertSet >
    Main PID: 695134 (code=exited, status=0/SUCCESS)
 Jan 08 22:39:20 lainhv01 vpncmd[695134]: Version 4.34 Build 9745   (English)
 Jan 08 22:39:20 lainhv01 vpncmd[695134]: Compiled 2020/04/05 23:39:56 by buildsan at crosswin
 Jan 08 22:39:20 lainhv01 vpncmd[695134]: Copyright (c) SoftEther VPN Project. All Rights Reserved.
 Jan 08 22:39:20 lainhv01 vpncmd[695134]: Connection has been established with VPN Server "localhost" (port 5555).
 Jan 08 22:39:20 lainhv01 vpncmd[695134]: You have administrator privileges for the entire VPN Server.
 Jan 08 22:39:20 lainhv01 vpncmd[695134]: VPN Server>ServerCertSet /LOADCERT:/etc/letsencrypt/live/hostname.example.com/fullc>
 Jan 08 22:39:20 lainhv01 vpncmd[695134]: ServerCertSet command - Set SSL Certificate and Private Key of VPN Server
 Jan 08 22:39:20 lainhv01 vpncmd[695134]: The command completed successfully.
 Jan 08 22:39:20 lainhv01 systemd[1]: renew_vpnserver.service: Succeeded.
 Jan 08 22:39:20 lainhv01 systemd[1]: Finished Renew VPN Server certificate file..
カテゴリー
Ubuntu

microk8sでダッシュボードを表示させる

microk8sでダッシュボード機能を有効化する

microk8s.enable dashboard

ダッシュボードに外部からの通信を転送(port-forward)するコマンドを実行する。

このときに、--address 0.0.0.0を指定して、他のマシンからもアクセス出来るようにして、コマンドの最後に & を付けてバックグラウンドで実行させておく。

microk8s.kubectl port-forward --address 0.0.0.0 -n kube-system service/kubernetes-dashboard 10443:443 &

ログイン用のトークン文字列を取得する。

token=$(microk8s kubectl -n kube-system get secret | grep default-token | cut -d " " -f1)
microk8s.kubectl -n kube-system describe secret $token

ウェブブラウザからアクセスする

microk8sを実行しているマシンに対して、ウェブブラウザから https://IPアドレス:ポート番号 でアクセスするとログイン画面が出てくる。
(プロトコルはhttps、ポート番号はport-forwardで指定した10443番などの数字)

トークン文字列はかなり長いので途切れないように気を付けてコピペする。

ログイン出来たらKubernetesの画面が表示される。