AWSでkubernetesクラスタを構築してmisskey鯖を立てよう
前回のおさらい
k8sクラスタを立ててNodePort経由でnginx Podまで疎通が取れるところまでやりました。今回はその続き。
flannelが通信するためのポートも開放必要
Kuberntes公式にはKubernetes自体が通信に使うポートは載っているものの、cniが通信に使うポートまでは載ってなかった…
上記参考に8472番も許可するセキュリティグループを作成しておく。
cluster.localドメインの名前解決ができない!
前回k8sクラスタを立てたとき、kube-dnsによるcluster.localドメインによる名前解決が動作しなかった。
# svcのIP指定でnginxにアクセスはできる
$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 2m31s
nginx ClusterIP 10.102.226.76 <none> 80/TCP 6s
$ kubectl run alpine -it --rm --image alpine -- ash
/ # wget -O - 10.102.226.76
Connecting to 10.102.226.76 (10.102.226.76:80)
writing to stdout
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
- 100% |***************************************************************************************************************************************************| 615 0:00:00 ETA
written to stdout
# でもcluster.localドメインの名前解決はできない
/ # nslookup nginx
;; connection timed out; no servers could be reached
以下のようにkube-dnsがlistenしているPortを確認し、セキュリティグループによる許可設定も行ったのだが、それでも名前解決ができない。困った。
$ kubectl get svc -n kube-system
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kube-dns ClusterIP 10.96.0.10 <none> 53/UDP,53/TCP,9153/TCP 7m7s
情報を求めて彷徨っていたところ、興味深いページにたどり着くことができた。
なんとcorednsのdeploymentを一旦削除した後再度applyすることで名前解決ができるようになるらしい。
ものは試しとやってみることにした。
$ kubectl get deployment -n kube-system coredns -o=yaml > coredns_dep.yaml
$ kubectl delete -f coredns_dep.yaml
deployment.apps "coredns" deleted
$ kubectl apply -f coredns_dep.yaml
deployment.apps/coredns created
$ kubectl run alpine -it --rm --image alpine -- ash
/ # nslookup nginx
Server: 10.96.0.10
Address: 10.96.0.10:53
Name: nginx.default.svc.cluster.local
Address: 10.102.226.76
.
.
.
本当に名前解決ができるようになった。やはり再起動は全てを解決する…
クラスタ構成を考える
概ねインターネットからの疎通ができるk8sクラスタの構築準備が整ったので、misskey鯖を立てるk8sクラスタおよび周辺ネットワークの構成を考えていきたい。
なお構成を考えるにあたりAWSではなく他VPSサービスを利用することも検討はしたものの、ある程度触り慣れたAWSを捨てて移行するほどのメリットを感じられなかった。
サーバスペック
後述する参考資料と利用料金を鑑みて、k8sクラスタに使用するサーバスペックは以下で良いだろう。
ec2インスタンス
t3.small(CPU:2コア3.1GHz,メモリ2GB) × 3台
1台はcontrol-plane用、残り2台をworker(+ingress)に割り当てる
EBS
汎用 SSD (gp3) - ストレージ 30GB
k8s外部との接続方法
インターネットとk8sクラスタの接続方法として、2パターン考えられる。
別途ec2インスタンスを立てnginxをリバースプロキシとして使う
ELB(+Ingress)で公開する
前者のメリットは利用料金を安くできること。ELBの代わりにt3.smallのec2インスタンスを立てることでELB利用時に比べて最低1000円/月は安くでき、通信量が増えればその分ELBの利用料が増えるため実際にはもっと安くなると考えられる。
その代わり、以下デメリットがある。
nginxでSSL終端することになるため、opensslに脆弱性が見つかった場合に自前で対応が必要
仮にnginxへの(=k8s上で動作させるmisskeyへの)アクセスが増え、慢性的にnginxの処理に大幅な遅延が発生するようになってしまった場合、nginxが動作しているインスタンスのインスタンスタイプの見直し&インスタンス再作成が必要(=サービス断発生が避けられない)
無制限にユーザ登録を開放するつもりはないため2点目のデメリットが顕在化することはほとんど無いと考えられるが、1点目は結構問題である。
これが業務で立てるサービスであれば所属会社のセキュリティ部門などが定期的に行っている(であろう)脆弱性チェック結果を元に対応を行うだけなのだが、個人で立てるサービスではチェックから自分でやらなければならない。常日頃からセキュリティ関連のニュースに目を光らせ、脆弱性報告が挙がってないかを探しに行くのを個人でやるのは中々ハードルが高い。
一方ELBを用いる場合はELBでSSL終端できるため、前述のようなセキュリティ面の対応はAWS側にお任せできる。
また、仮にk8s上で動作させるmisskeyへのアクセスが増えてスペック増強が必要になった場合でも、ELB自体を止める必要が無くworker nodeおよびIngress周りのPodのreplica数を増やすことで対応可能なためサービス断が発生しない。
現状そこまで通信量は増えない見込みであることを鑑みると、ELB(+Ingress)で公開するメリットの方が大きそうである。
CDNどうする
AWSが提供しているCloudFrontを利用するのが良さそうに思われる。現在想定している使い方なら無料枠に収まりそう。
misskeyのドメインとサーバ証明書どうする
misskey-naosuki.netというドメインをRoute53で作成・登録する。
ELBを使用することでACMが無料で利用可能になるため、ACMで自動更新ありのサーバ証明書を発行して使う。
misskeyのメールサーバどうする
Cloudflare Email Routing + Gmailを用いて独自ドメインからmisskey関連のメール送受信を行いたい。そのためCloudflareのアカウント作成が別途必要。
いざ構築
ec2インスタンス作成 ~ master/worker nodeセットアップについては本記事では割愛。
なお、改めてk8sクラスタを構築しようとしたところなぜかkubeadmのapt-get installがコケるようになってしまった…
ので、以下ページを参考にkubernetes-cniをforce overrideして乗り切った。
また、dockerインストール時にcontainerdがインストールされなくなったので別途インストールするようplaybookの修正も行った。
NFSサーバセットアップ
上記を参考にセットアップする。
worker含む全nodeにnfs-commonをinstallするのを忘れないように。
なお、マウント先パスは /k8s/misskey/ にしておくと後々都合がいい。
misskeyセットアップ
上記を参考にセットアップする。
なお、t3.smallタイプだと misskey.yamlのDeploymentで指定している
`spec.template.spec.container.resource.limits` を外さないとPodがスケジューリングされなかった。
構築確認
UI表示確認
ubuntu@kube-controlplane:~$ kubectl get svc -n misskey
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
db ClusterIP 10.105.227.12 <none> 5432/TCP 44m
redis ClusterIP 10.111.88.70 <none> 6379/TCP 44m
web NodePort 10.109.209.13 <none> 3000:30100/TCP 5m
ubuntu@kube-controlplane:~$ kubectl get svc -n misskey
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
db ClusterIP 10.105.227.12 <none> 5432/TCP 44m
redis ClusterIP 10.111.88.70 <none> 6379/TCP 44m
web NodePort 10.109.209.13 <none> 3000:30100/TCP 5m
worker1ないしworker2のパブリックIPで30100ポートを指定すれば取り急ぎUIは開ける。
取り急ぎ管理者アカウントなどを作成
この辺を見ながら設定していく。
基本的にお一人様鯖、招待するにしても知り合いのみにするため、「誰でも新規登録できるようにする」は切っておく。
外部公開設定
ドメイン作成・登録
AWSのRoute53でドメインを作成。
サーバ証明書作成
上記を参考にサーバ証明書を発行。
ベースドメインを含むワイルドカード証明書を発行した。
Cloudflareアカウント作成
Freeプランで作成。Botプロテクションに使うTurnstileの作成をして、いざメール転送設定を…というところで、どうやらmisskey-naosuki.netのままメール転送設定しようと思ったらRoute53で登録したDNSレコードを移植しないといけないらしい。
どうせ個人鯖だし、不特定多数のアカウント登録を想定しているわけでもないので、普段利用している個人メールアドレスをそのまま使う方針で妥協することにした。
ELB作成
この辺りを参考に、Webコンソールからポチポチっと作成。
HTTPベースでのルーティングは不要だったのと、nginx-ingress-controllerの公式インストールガイドで紹介されているのがNLB向けの手順だったこともあり、ALBではなくNLBを採用することにした。
疎通確認のためmisskey-webサーバのデプロイ先をworker1に固定し、LBの80番ポートに来たHTTP通信をworker1の30110番ポートに転送してみたところ無事NLB経由でmisskeyのページが表示された。
試してはいないが、ターゲットグループの設定で転送先ノードを増やせばworker2にデプロイされてても問題なく表示されそうである。
これなら無理にingressを立てる必要もなさそう。
HTTPS化する場合はLBの443番ポートに来たTLS通信をworker1の30110番に転送するようにし、作成したサーバ証明書をSSL/TLS証明書に指定すればOK。
エイリアスレコードを設定
NLBに割り当てられたパブリックDNSでアクセスできるようになったので、取得した独自ドメインでもアクセスできるようにする。
上記参考にRoute53で取得したドメインをNLBのパブリックドメインのエイリアスレコードとして指定する。
ちなみに後々CloudFront噛ませる際に設定変更することになる。
メールサーバ設定
こちらを参考に、Gmailのメールサーバを利用するよう設定を行った。
オブジェクトストレージにAmazon S3を指定した場合に画像が表示されないのを修正する
この辺参考に設定を行った。
CloudFrontを噛ませる
この辺り読みながら設定を行った。
流れとしては
バージニアリージョンのACMでCloudFront用のサーバ証明書を発行
Route53で取得したドメインのサブドメインをLBのパブリックドメインのエイリアスレコードとして登録
CloudFrontのディストリビューションを作成
LBをオリジンに設定。LBを選択するのではなく上述のサブドメインを指定すること。
キャッシュビヘイビアは
CloudFront の WAFを設定する #CloudFront - Qiitaを参考に指定追記した内容を参照。料金クラスは北米、欧州、アジア、中東、アフリカを使用
代替ドメイン名を misskey-naosuki.net に指定
Route53で取得したドメインをCloudFrontドメインのエイリアスレコードとして登録
といった感じ。
(2024/2/11)追記
設定によってはCloudfrontを噛ませた瞬間に予期せぬ挙動を引き起こすことが判明した。具体的には以下。
画像投稿ができない
投稿しようとすると403エラーや504エラーが発生する
リモートのフォロワーの投稿がタイムラインに追加されない
自動承認されるはずのフォローが承認されない
アバターやサムネが全て同じ画像になる
以下のビヘイビア設定を行うことで、上記挙動が発生しなくなることを確認した。
優先順位0
パスパターン: /api/*
オリジンとオリジンパスグループ: LBを指定
オブジェクトを自動的に圧縮: No
ビューワープロトコルポリシー: Redirect HTTP to HTTPS
許可された HTTP メソッド: GET, HEAD, OPTIONS, PUT, POST, PATCH, DELETE
ビューワーのアクセスを制限する: No
キャッシュキーとオリジンリクエスト
Cache policy and origin request policy (recommended) にチェック
キャッシュポリシー: CachingDisabled
オリジンリクエストポリシー: AllViewer
優先順位1
パスパターン: デフォルト(*)
オリジンとオリジンパスグループ: LBを指定
オブジェクトを自動的に圧縮: No
ビューワープロトコルポリシー: Redirect HTTP to HTTPS
許可された HTTP メソッド: GET, HEAD, OPTIONS, PUT, POST, PATCH, DELETE
ビューワーのアクセスを制限する: No
キャッシュキーとオリジンリクエスト
Cache policy and origin request policy (recommended) にチェック
キャッシュポリシー: Amplify
オリジンリクエストポリシー: AllViewer
また、WAFを有効にすることで追加されるルールの内、以下を許可することで403エラーの発生を防ぐことができる。こちらについてはアップロードする画像によるため、DENYのままではどうしても困るといった場合に設定変更するとよい。
AWS-AWSManagedRulesCommonRuleSet
SizeRestrictions_BODY
お一人様鯖を立ててみた感想
Discordの鯖立てと違って結構かかってしまったなという感想。
AWSとk8sを触ることが主目的であったのでまあ致し方なしか。
これまでEC2はよく触っていたものの、ELBやCloudFront、ACM、Route53といったサービスは今回初めて触ることになった。
外部のドメイン登録サービスや証明書発行サービスを頼ることなく一気通貫でwebサービスを構築できるのはやはりAWS強し…といったところか。
基本的にお一人鯖として運用することになるとは思われるが、k8s上で動くサービスとして構築したおかげで割とスケールアウトは簡単に行えるようになっている。もし利用者が増えるようなことがあっても柔軟に対応できる余地があるのは良いことだ。
この記事が気に入ったらサポートをしてみませんか?