Kubernetesの学習のためにMastodonを構築したら勉強になった
そろそろKubernetes(以後k8s)触ってみないといかんな欲が高まってきました。
が、k8sを使ってなにを構築したものかと思ってたんですが、
Mastodonを使いたい案件(プライベートで)があったので、k8sを使ってMastodonを構築していこうと思います!
自分のメモ書きみたいな内容なので注意。
Kubernetesについて
まずk8sについて基礎的なところから学んでいきます。
k8sは一言でいうと「コンテナオーケストレーションツール」です。
しかしイメージしづらいので詳しく調べてみます。
k8sが生まれた背景
同一ホスト内に複数コンテナがあるとします。
(図を書くのが面倒なのでPlantUMLで適当に書いた図)
この同一ホスト内でのコンテナ同士はプライベートネットワークで通信ができますが、外部への通信はNATを通してしかできません。
このホストのスケールアウトを考えたとき、コンテナの連携やルーティングが複雑になるのは想像に難くないでしょう。
この問題を解決するのがKubernetesです。
ユーザに上記のような問題を意識させないように、k8sはコンテナのクラスタ化を行います。
ユーザがコンテナを起動させたいときは
- コンテナイメージ
- コンテナの台数
を指定するだけで、k8sがいい感じにクラスタ化してくれます。
仕組み
そもそもどうやってk8sにコンテナがデプロイされるんでしょう?
- ユーザはどのようなコンテナを何台起動するかという情報(Spec)をk8sに渡す
- k8sのMasterは渡されたSpecをもとに、クラスタ内の空きリソースを確認してどのNodeにどのように配置するか決定する
- 各NodeはMasterが決定した内容をもとにコンテナを起動する
Master1台が複数台あるNodeに対して指示を出しSpec通りコンテナを起動していく という流れになります。
またk8sはSpecに指示された台数などを維持します。
たとえコンテナやNodeに障害があり落ちても、k8sはこれを検知し、再起動やNodeの起動などを行います。
Pod
Podはk8sにおいての最小単位であり、コンテナの集まりです。
上記の「同一ホスト内に複数コンテナ」の図がそのまんまPodにあたります。
Podは以下の要素で構成されています。
- コンテナ
- 複数個ある場合もある。Nginx、Webアプリ、Redisなど
- Volume
- Pod内コンテナが共用する記憶領域
- Cluster IP
- Pod内コンテナが共有するIPアドレス
Deployment
じゃあPodの配置ってどうやんの?って思いますが、それがDeploymentです。
Podの起動数やコンテナイメージなどを指定します。
Service
DeploymentでPodを作っただけだと、外部からアクセス出来ないですし、
複数同じPod(Replica Set)がある場合、どれにアクセスしていいかわかりません。
そのPodへのアクセス手段を用意するのがServiceです。
Replica Setへのロードバランス機能もServiceが提供します。
実際k8sでpodを作ってみる
ここまで
- Pod
- Deployment
- Service
というものが出てきました。
それを理解するために実際にk8sになにかデプロイしてみたいと思います。
Googleがチュートリアルを提供していますのでこれを例にやっていきます。 https://cloud.google.com/kubernetes-engine/docs/tutorials/hello-app?hl=ja
また今回は定義ファイルを中心に見ていきたいので、クラスタ生成などは上記チュートリアルを見てください。
# サンプルダウンロード $ git clone https://github.com/GoogleCloudPlatform/kubernetes-engine-samples $ cd kubernetes-engine-samples/hello-app
$ vim helloweb-deployment.yaml apiVersion: apps/v1beta1 kind: Deployment # Deploymentの定義ファイルであると宣言 metadata: name: helloweb # このDeploymetの名前付け labels: app: hello # app=helloというラベルを付与 spec: # k8sに渡すSpec replicas: 3 # 以下テンプレートの内容を3つ作る template: metadata: labels: # それぞれのPodにこのラベルを付ける app: hello tier: web spec: containers: # コンテナの定義 - name: hello-app image: gcr.io/google-samples/hello-app:1.0 ports: - containerPort: 8080
ではこれをデプロイしてみます。
$ gcloud config set project ${project_id} $ gcloud container clusters create hello-app --zone asia-northeast1-a --num-nodes 3 kubeconfig entry generated for hello-app. NAME ZONE MASTER_VERSION MASTER_IP MACHINE_TYPE NODE_VERSION NUM_NODES STATUS hello-app asia-northeast1-a 1.7.8-gke.0 35.187.196.56 n1-standard-1 1.7.8-gke.0 3 RUNNING $ gcloud config set compute/zone asia-northeast1-a $ gcloud config set container/cluster hello-app $ gcloud container clusters get-credentials hello-app $ kubectl create -f helloweb-deployment.yaml deployment "helloweb" created $ kubectl get pod NAME READY STATUS RESTARTS AGE helloweb-1127322674-b3ndr 1/1 Running 0 1m helloweb-1127322674-cq9xw 1/1 Running 0 1m helloweb-1127322674-lz7h2 1/1 Running 0 1m
こんな感じでPodが3つ作成されます。 続いてServiceです。
$ vim helloweb-service-static-ip.yaml apiVersion: v1 kind: Service # Serviceの定義ファイルであると宣言 metadata: name: helloweb labels: app: hello spec: selector: # 対象とするPod(Replica Set)のラベルを指定 app: hello tier: web ports: # ロードバランサのListenPortを指定 - port: 80 targetPort: 8080 type: LoadBalancer # loadBalancerIP: "YOUR.IP.ADDRESS.HERE" # 指定なしだと勝手にIP振られる
$ kubectl create -f helloweb-service-static-ip.yaml service "helloweb" created $ kubectl get service NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE helloweb LoadBalancer 10.59.240.23 35.200.32.20 80:31174/TCP 46s $ curl 35.200.32.20 Hello, world! Version: 1.0.0 Hostname: helloweb-1127322674-b3ndr
Serviceを作ることにより、外部からアクセスできるようになりました!
まとめ
KubernetesはDockerをクラスタ化するもの。
Kubernetesは
- Master
- Node
という役割を持ち、実際にContainerが乗るのはNodeで MasterはどのクラスタにどうPodを乗せるかなどを管理しているもの。
名称 | 役割 |
---|---|
Pod | Containerの集まり。記憶領域とIPを共有している。 |
Replica Set | Podの集まり。複数のノードに跨がる。何台必要かなどはDeploymentで定義する。 |
Service | Replica Setへのルーティングを担うもの。ロードバランサ的な役割も持つ。 |
Mastodonについて
Mastodonは分散型のソーシャルネットワークで、Twitterのような短文投稿システムです。
そのMastodonサーバーのことを「インスタンス」と呼び、そのインスタンスは誰でも立てられます。
そのインスタンス同士を繋ぎ大きなソーシャルネットワークを構築することができます。
そんなMastodonですがアーキテクチャに様々な技術を使用しており、インスタンスを構築してみることでインフラの勉強になるかと思います。
ということで、内部構造を簡単に見ていきます。
内部構造
Name | Description |
---|---|
Rails | サーバーサイドアプリケーション |
Postgresql | データベース |
Redis | キャッシュ |
Node.js | ストリーミングAPI |
Sidekiq | ジョブキュー。トゥートやストリーミングのたびにキューが作られる |
k8sでMastodonを構築する
さて、ようやく本題です。k8sでMastodonを構築するにはどのようにすればよいのでしょうか?
まず下記のようなPodが必要そうです。
- Nginx Pod
- Rails Pod
- Node.js Pod
- Sidekiq Pod
- Redis Pod
- Postgresql Pod
このうちPostgreSQLはGCPのCloud SQLを使おうと思います。
が、GCPのk8sからCloud SQLに接続するにはCloud SQL ProxyというDockerコンテナが必要になるので、このPodを作成します。
またMastodonでは画像のアップロードが出来ますが、簡易に取り出したいので、GCSに保存していきたいと思います。
あとMastodonのユーザ登録にはメール送信が必要です。
これはSendGridを使用します。
k8sのレシピは下記を基本的に使用してます。
https://github.com/jviide/kubedon
上記以外で設定した項目を以下に記載していきます。
Mastodonの設定ファイル
Mastodonアプリケーションの設定ファイルを生成しますが、
k8sでは「ConfigMap」という設定ファイルリソースを生成することができます。
以下が今回作成するMastodonの設定です。
$ vim config.yaml apiVersion: v1 kind: ConfigMap metadata: name: mastodon-config data: # Service dependencies REDIS_HOST: redis REDIS_PORT: "6379" DB_HOST: cloudsql DB_NAME: mastodon DB_PORT: "5432" LOCAL_HTTPS: "true" SMTP_SERVER: "smtp.sendgrid.net" SMTP_PORT: "2525" # GCEはPort25、465、587での送信接続が出来ないためこのPortを指定 SMTP_LOGIN: "apikey" # 文字列で「apikey」を指定 SMTP_PASSWORD: "${SendGridのAPIキー}" SMTP_FROM_ADDRESS: "${所有しているドメインのメールアドレス}" SMTP_AUTH_METHOD: plain SMTP_OPENSSL_VERIFY_MODE: none SMTP_ENABLE_STARTTLS_AUTO: "true" SMTP_DELIVERY_METHOD: smtp # S3 (optional) S3_ENABLED: "true" S3_BUCKET: "${GCSのバケット名}" AWS_ACCESS_KEY_ID: "${GCEのアクセスキーID}" AWS_SECRET_ACCESS_KEY: "${GCEのシークレットキー}" S3_REGION: asia-northeast1 S3_PROTOCOL: https S3_HOSTNAME: storage.googleapis.com S3_ENDPOINT: https://storage.googleapis.com
SMTPとGCSの設定を行っています。
Mastodonは標準でS3をサポートしています。
GCSはS3のAPIインタフェースで使用することができるので、MastdonでもGCSをストレージとして使用することができます。
Mastodon作成のための手順
# 事前にCloudSQLでPostgreSQLをたてておく # CloudSQLにproxy用のユーザ作成 $ gcloud sql users create proxyuser host --instance=mastodon --password=mastodon-proxy # k8sクラスター作成 $ export PROJECT_ID="$(gcloud config get-value project -q)" $ export REGION="asia-northeast1-a" $ gcloud config set project ${PROJECT_ID} $ gcloud config set compute/zone ${REGION} $ gcloud container clusters create mastodon --zone ${REGION} --num-nodes 3 # kubectlコマンドでmastodon k8sクラスターを使用するようにする $ gcloud config set container/cluster mastodon $ gcloud container clusters get-credentials mastodon # k8sからCloudSQLにつなぐための設定。 # 事前にサービスアカウントを作成し、Keyファイルを配置しておく必要あり $ kubectl create secret generic cloudsql-secrets \ --from-file=credentials.json=credential.json \ --from-literal=instance_connection_name=${PROJECT_ID}:${REGION}:mastodon # 環境変数の設定 mastodon_domain=${Mastodonのサイトのドメイン} $ kubectl create secret generic mastodon-secrets \ --from-literal=PAPERCLIP_SECRET=mas1 \ --from-literal=SECRET_KEY_BASE=mas2 \ --from-literal=OTP_SECRET=mas3 \ --from-literal=LOCAL_DOMAIN=${mastodon_domain} \ --from-literal=DB_USER=proxyuser \ --from-literal=DB_PASS=mastodon-proxy # Let's Encryptの証明書発行 $ wget https://dl.eff.org/certbot-auto $ chmod a+x certbot-auto $ ./certbot-auto $ ./certbot-auto certonly \ --manual \ --domain ${mastodon_domain} \ -m ${自分のメールアドレス} \ --agree-tos \ --manual-public-ip-logging-ok \ --preferred-challenges dns $ sudo cat /etc/letsencrypt/live/${mastodon_domain}/fullchain.pem > fullchain.pem $ sudo cat /etc/letsencrypt/live/${mastodon_domain}/privkey.pem > privkey.pem # Nginx設定でこの証明書を参照させる設定 $ kubectl create secret generic web-certificates \ --from-file=fullchain.pem \ --from-file=privkey.pem # kubedonのチェックアウト $ git clone https://github.com/jviide/kubedon $ cd kubedon # 上記config.yamlの設定を行った後 $ kubectl create -f . # nginxのexternal ipを確認(表示されるまで時間かかる) # external ipを確認できたら、MastdonのドメインのAレコードに登録 $ kubectl get service NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE cloudsql ClusterIP 10.47.255.19 <none> 5432/TCP 11m kubernetes ClusterIP 10.47.240.1 <none> 443/TCP 14m mastodon ClusterIP 10.47.240.180 <none> 3000/TCP,4000/TCP 11m nginx LoadBalancer 10.47.243.203 35.200.14.137 80:31337/TCP,443:32088/TCP 11m redis ClusterIP 10.47.240.9 <none> 6379/TCP 11m # DBにデータセットアップ $ kubectl get pod $ kubectl exec -it ${mastodonのpod id} /bin/sh #$ kubectl exec -it mastodon bash $ RAILS_ENV=production bundle exec rails db:setup $ RAILS_ENV=production bundle exec rails assets:precompile $ exit # 完了!
ここまで出来たら、自分が設定したMastodonのドメインをブラウザで見てみましょう!
以下の画面が出てきたら成功です。
もし開かない場合、エラーが発生しています。
GKEでは「Stackdriver」というサービスでアプリケーションログの確認ができます。
大体そこに原因が出力されてるので、対応しましょう。
ここで終わりではなく、ユーザの登録とトゥートが出来ないと意味がないのでそちらも確認しましょう。
# GCSに画像が保存されてるか確認 $ gsutil ls gs://mastodon-morix/media_attachments/files/000/000/001/original gs://mastodon-morix/media_attachments/files/000/000/001/original/f0669c90da43564b.jpg
出来ました!!
最後に
今回のMastodon構築では、下記の設定ファイルを使わせていただきました。
https://github.com/jviide/kubedon
この記事では上記の設定ファイルに触れると量が膨大になるので触れませんでしたが、
k8sの基礎を学ぶにはとてもいい教材だと思うので、ぜひ読んでみてください!
AWSでもk8sでも出ましたが、インフラ屋としては今後も触っていかないといけないサービスだなぁと思ったので、引き続き勉強がんばろ!
では!