👻

Kubernetes PodにてRailsアプリを起動

2023/05/21に公開約6,900字

目的

Railsの実行環境・デプロイ環境を整えてみたい。
暇なので、KubernetesやDockerを勉強。

やりたいこと

  • Railsを動かすコンテナイメージを自力で作成する。
  • KubernetesのManifestを作成して、Railsを動かすPodを立ててみる。
  • Railsのコードに更新が入ると、Pod上のスクリプトに自動で更新される。

今回やりたいこと

今回は第二章。

KubernetesのManifestを作成して、Railsを動かすPodを立ててみる。

Kubernetesに関する個人的質問の数々

Kubernetesを勉強しようとしたが、ガチで意味不明。
ドキュメントを読んでみて、調べてみる。

Q) そもそもManifestを作成する意味は?

Kubernetesオブジェクトは「理想の記録」であり、オブジェクトの通りに、クラスタの状態を実現したい。オブジェクトを作成するためには、開発者の「理想状態」を定義する必要がある。
それをYML manifestに記載する。

Q) Podとは?

Podは、1つ以上のアプリケーションコンテナ(Dockerなど)のグループとそれらのコンテナの共有リソースを表すKubernetesの抽象概念

  • 複数のコンテナをまとめた単位。単位は大きい順から、クラスタ > ノード > Pod > コンテナ。
  • 各Pod内で実行されるプロセスは独立している。
  • 同一Pod内では、ネットワーク・ストレージを共有できる。
  • 同一ネットワーク空間に存在するため、IPとポートは重複できない。

Q) ノードとは?

Podは常にノード上で動作します。ノードは複数のPodを持つことができる。レジストリからコンテナイメージを取得し、コンテナを解凍し、アプリケーションを実行することを担当する。

Q) Kuberletとは?

Kubernetesマスターとノード間の通信を担当するプロセス。レジストリからコンテナイメージを取得し、コンテナを解凍し、アプリケーションを実行することを担当する、Dockerのようなコンテナランタイム。

Manifestを作成してみる

NameSpace

同一物理マシン上で仮想クラスタの運用ができる。名前空間とは仮想クラスタのことを指す。
一度作成してしまえばおk。

https://kubernetes.io/ja/docs/concepts/overview/working-with-objects/namespaces/

namespace.yml
apiVersion: v1
kind: Namespace
metadata:
  name: MyApp

Deployment

各Podの状態を更新したいときに有効らしい。デプロイやロールバックの方法とか。

Deploymentコントローラーは指定された頻度で現在の状態を理想的な状態に変更します。Deploymentを定義することによって、新しいReplicaSetを作成したり、既存のDeploymentを削除して新しいDeploymentで全てのリソースを適用できます。

https://kubernetes.io/ja/docs/concepts/workloads/controllers/deployment/

ReplicaSet

Kubernetesオブジェクトは、クラスタの理想状態であるイメージだが、「Podが複数存在するとき」にどんな各Podが挙動するかを定義しておきたい。この場合に「ReplicaSet」を活用する。

deployment.yml
apiVersion: apps/v1 # どのバージョンのKubernetesAPIを利用してオブジェクトを作成するか
kind: Deployment # どの種類のオブジェクトを作成するか(例:Service など)
metadata: # オブジェクトを一意に特定するための情報(名前空間)
  name: web-deploy  # オブジェクトの名前
  labels: # オブジェクトを選択するために使うラベル
    app: MyApp # 任意のキー・
spec:
  replicas: 3 # 3つのレプリカPodを作成
  selector:
    matchLabels: # spec.template.metadata.labelsと一致させる必要がある
      app: web # タグ"web"のPodのみデプロイを適用
  template: # 実際に適用するデプロイ方法テンプレート
    metadata:
      labels:
        app: MyApp # ReplicaSetとPodに付けられるラベル
    spec:
      restartPolicy: OnFailure # コンテナの再起動ポリシー(default: Always)
      containers:
      - name: MyApp # 適当に設定
        image: myapp:[tagneme] # 自作のコンテナイメージを設定
        ports:
        - containerPort: 3000 # コンテナ開放ポートは3000だったことを確認済

Service

要するに、Podへの接続したいときに必要な情報を保持し、接続を解決してくれる。
ロードバランサの設定やDNS設定などもやってくれる。

  • 各NodeのIPにて、静的なポート(NodePort)上でServiceを公開(NodePort)
  • ラウドプロバイダーのロードバランサーを使用して、Serviceを外部に公開(LoadBalancer)

https://sysdig.jp/blog/kubernetes-services-clusterip-nodeport-loadbalancer/

KubernetesにおけるServiceとは、 クラスター内で1つ以上のPodとして実行されているネットワークアプリケーションを公開する。KubernetesはPodにそれぞれのIPアドレス割り振りや、Podのセットに対する単一のDNS名を提供したり、それらのPodのセットに対する負荷分散が可能です。

https://kubernetes.io/ja/docs/concepts/services-networking/service/

service.yml
apiVersion: v1
kind: Service
metadata:
  name: web-app
spec:
  type: NodePort
  selector: # リクエストをタグに一致するPodに送信
    app: MyApp # タグ"MyApp"のPod  
  ports:
  - nodePort: 3000 # 静的なポート(NodePort)上でServiceを公開
    port: 80
    protocol: TCP

KubectlでPodを作成

Kubectlとは?

kube-apiserverにHTTPリクエストを送るコマンドツール。

https://kubernetes.io/ja/docs/reference/kubectl/cheatsheet/

kube-apiserverとは?

Kubernetes APIを外部に提供するKubernetesコントロールプレーンのコンポーネント。
ワーカーノードを管理するコントロールプレーンにのみ存在している

minikubeとは?

ローカル環境でKubernetesを簡単に実行するためのツール。

  • クラスタの立ち上げ
  • クラスタの管理
  • クラスタの削除

https://kubernetes.io/ja/docs/setup/learning-environment/minikube

必要なツールの準備

# Dockerコンテナイメージが作成されていることを確認
$ docker images
REPOSITORY                  TAG        IMAGE ID       CREATED         SIZE
myapp                       1.0.0      810d5e926918   44 hours ago    991MB

# Asdfでkubectl,minikubeをインストール
$ asdf plugin-add kubectl
$ asdf install kubectl 1.27.0
$ asdf global kubectl 1.27.0
$ asdf plugin-add minikube
$ asdf install minikube 1.30.1
$ asdf global minikube 1.30.1

# minikubeダッシュボードを開いて、クラスタの情報を可視化
$ minikube start
[色々出力される]
$ minikube dashboard
[色々出力される]

デプロイ!

$ kubectl create namespace myapp
namespace/myapp created

# deployマニフェストを実行
$ kubectl apply -f deployment.yaml --namespace=myapp
deployment.apps/webdeploy created

デプロイできていなかった・・・。

原因1 イメージの名前が悪い説

Failed to apply default image tag "MyApp:1.0.0": couldn't parse image reference "MyApp:1.0.0": invalid reference format: repository name must be lowercase
-> タグ名をアルファベット小文字にして解決

原因2:コンテナに指定されたイメージを取得またはプルできない説

Error: ImagePullBackOff
# 解決策 -> dokcerhubにプッシュしたイメージを取得
$ docker login
$ docker push [dockerhub username]/myapp:[tagneme]
$ kubectl apply -f deployment.yaml --namespace=myapp

再度デプロイしたが失敗。でも状態は進んだ。

原因3:KubernetesがDockerhubにアクセスできていなかった説

調べてみると、CrashLoopBackOff。なんでやねーん!
Podで発生している再起動ループらしい。それが徐々に実行期限をすぎて、エラーを発出。

$ kubectl get pods --namespace myapp
NAME                         READY   STATUS             RESTARTS        AGE
webdeploy-6bf4975977-5cvm6   0/1     CrashLoopBackOff   6 (2m44s ago)   11m
webdeploy-6bf4975977-s9hw5   0/1     CrashLoopBackOff   6 (2m46s ago)   11m
webdeploy-6bf4975977-w85v5   0/1     CrashLoopBackOff   6 (2m34s ago)   11m

# デプロイ用アプリのディレクトリをmanifestを配置したディレクトリから参照できるようにした
# -> これダメ!親ディレクトリをコピーできない。(後述)
$ ln -s ../app ./app

# KubernetesがDockerhubにアクセスできていなかったかも?
$ kubectl create secret docker-registry NAME --docker-username=user --docker-password=password --docker-email=email

これでなんとかPodは作成されたっぽい。なおデプロイには失敗する模様。

原因4:そもそもアプリのフォルダがコンテナ内にコピーできてない説

$ kubectl describe pods webdeploy-6bf4975977-fxks4

# 色々調査
$ kubectl get po -o wide
NAME                         READY   STATUS             RESTARTS      AGE   IP            NODE       NOMINATED NODE   READINESS GATES
webdeploy-6bf4975977-fxks4   0/1     CrashLoopBackOff   8 (21s ago)   16m   10.244.0.10   minikube   <none>           <none>
# -> Podは作成できている

$  kubectl logs webdeploy-6bf4975977-fxks4
Usage:
  rails new APP_PATH [options]
#・・・(省略
# -> なぜかrails new〜コマンドのUsageが出力されていた。
# -> どうやらGemfileやサンプルアプリがコンテナ内にコピーされていなかった。
# -> manifestからコピー対象が親フォルダだと、コピーしてくれない。
# -> manifestディレクトリにsampleAppを配置して解決!

稼働した!

内部公開

# PodのIPを調べる
$ kubectl get ep webdeploy
NAME        ENDPOINTS         AGE
webdeploy   10.244.0.6:3000   49m
# NodeのIP
$ kubectl get svc webdeploy
NAME        TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
webdeploy   NodePort   10.97.209.198   <none>        80:30314/TCP   49m

$ minikube service webdeploy --url -n <namespace>
http://127.0.0.1:54308

イメージとして、Podの外側にNodeがいる感じなので、10.97.209.198:80,30314->10.244.0.6:3000へアクセスしているのだろうか。

http://127.0.0.1:54308 にアクセスすると、Podで構築したRailsアプリにアクセスできた。
各Serviceは、ローカルのNode上でポート(ランダムに選ばれたもの)を公開されるっぽい。

Discussion

ログインするとコメントできます