巷で話題の kubernetes ですが、とってもとってもとっつきにくいですよね
そんな kubernetes ですが手元で動かすことができたので解説してみます
(情報が間違ってたらごめんなさい! )
目標はこちら
- kubernetes の後述する基本的な概念を雰囲気理解すること
- ローカルのクラスタにデプロイして Ruby on Rails ( Rails ) の 「 Yay! 」のページを見ること
コードはこちら
https://github.com/tkhr0/hello-rails-on-k8s
早速行きましょう
今回構築する環境
図にするとこんな感じです
kubernetes 要素を抜くとこんな Ngix + Rails + MySQL のよくある形です
http サーバとして Nginx を立てて、 Rails (Puma) にリバースプロキシします
DB には MySQL を用意して Rails とやりとりします
ここに kubernetes 要素を加味してこうです
これが完成形になります
なんかいろいろでてきましたね
次で解説していきます
kubernetes の基本的な概念を雰囲気理解する
そこで今回紹介する概念はこちら
- Container
- Pod
- ReplicaSet
- Deployment
- Service
- Node
- Label
いっぱいありますね
雰囲気をお伝えするためにざっくり説明します
( kubernetes が管理する↑こういうやつをオブジェクトと呼びます)
Container
まずは Container です。
図での黄色です
これは Docker コンテナのことです
1 つの Container には 1 プロセスとすることが推奨されています
直接 kubernetes の概念ではないですが、 kubernetes はこいつを取り扱うためのツールですね
Pod
そして Pod です
図での明るい方の青です
これは Container をまとめる最小単位となります
僕の中のイメージは AWS の EC2 インスタンスです
インスタンスの中にミドルウェアとしてアプリケーションが入ってるように、 Pod の中に Container としてアプリケーションが入っているイメージです
ここで Pod は冗長化には向いていません
例えば Nginx を 2つ立てる場合には Pod を 2つ用意する必要があります
それは手間ですよね
それを解決するのが ReplicaSet です
ReplicaSet
図での緑色です
ReplicaSet は Pod をまとめて扱う役割があります
Pod を ReplicaSet 配下に置くことで ReplicaSet 単位で複製して群として扱うことができます
Deployment
次に Deployment です
図でのねずみ色です
Deployment はアプリケーションのデプロイをしてくれます
Docker Image から Pod や ReplicaSet を生成して Node に配置します
また RollingUpdate 戦略が用いられるため、ダウンタイムを発生させずにデプロイすることができます
Service
そして Service です
図での暗い方の青色です
Pod が外部とやりとりするために必要になるオブジェクトです
WAN からのリクエストを Pod に割り振ったり、 Pod から Pod への通信を仲介したりしてくれます
また Service がいることでサービスディスカバリを提供できます
※ サービスディスカバリとは、サーバでネットワークの変更があってもクライアントからは同じようにアクセスし続けられる仕組みのこと。 DNS もひとつですね。
Node
次は先ほど出てきた Node です
図でのシルバーです
Node は… Node です
物理サーバとニアリーイコールなイメージです
うまく説明できませんが kubernetes が管理している領域を表します
Label
最後に Label です
Service, ReplicaSet, Pod などのオブジェクトは Label を用いてグルーピングをすることができます
リクエストの送信先や所属関係はこの Label によるグルーピングで表します
なので、逆に入れ子関係や親子関係のような概念は kubernetes では出てきません
概念的にはすべてのオブジェクトは並列に並んでおり Label によってグルーピングがなされています
kubernetes を構成する要素はこのようになっています
kubernetes はサーバを管理するための要素を一般化するためにたくさんの機能を持っています
それらを Pod や Deployment などの単位に区切ることで疎結合なシステムを提供しています
要素数はたくさんありますが kubernetes なしでのサーバ環境と近いところがあるので少しづつ理解しましょう
またここでは紹介しませんでしたが他にもいろいろな機能が提供されています
crontab のように定期的に動作を行う Job や環境ごとの設定の違いを吸収する ConfigMap など多数の要素があります
実際に本番環境での運用をする場合には必要になってくるので一緒にググってみてください
ローカルで kubernetes を動かす
先ほどざっくりお伝えした雰囲気を携えて実際に kubernetes を動かしてみましょう
手元の環境はこちらです
$ docker -v
Docker version 18.06.1-ce, build e68fc7a
$ kubectl version
Client Version: version.Info{Major:"1", Minor:"13", GitVersion:"v1.13.2", GitCommit:"cff46ab41ff0bb44d8584413b598ad8360ec1def", GitTreeState:"clean", BuildDate:"2019-01-25T08:47:41Z", GoVersion:"go1.11.4", Compiler:"gc", Platform:"darwin/amd64"}
Server Version: version.Info{Major:"1", Minor:"10", GitVersion:"v1.10.3", GitCommit:"2bba0127d85d5a46ab4b778548be28623b32d0b0", GitTreeState:"clean", BuildDate:"2018-05-21T09:05:37Z", GoVersion:"go1.9.3", Compiler:"gc", Platform:"linux/amd64"}
下準備
まずはこちらのリポジトリをクローンしてください
https://github.com/tkhr0/hello-rails-on-k8s
そしてローカルの kubernetes 環境を起動します
Docker Desktop for Mac 18.06.0-ce-mac70 CE から kubernetes が同梱されているのでそれを起動します
Docker の設定画面から
Kubernetes
> Enable Kubernetes
> Kubernetes
とクリックして kubernetes を起動します
Docker のバージョンが古い場合には minikube というツールで代替してください
最後に kubectl
というコマンドが必要になるのでマシンにインストールしてください
これは docker
コマンドのように kubernetes エンジンとやりとりするためのコマンドです
こちらを参考にしてみてください
https://kubernetes.io/docs/tasks/tools/install-kubectl/
mac & Homebrew ならこのコマンドでインストールできます
$ brew install kubernetes-cli
とりあえず動かす
とりあえず kubernetes にデプロイしてみましょう
クローンしたリポジトリ内で以下のコマンドを打ってみてください
各種 Docker イメージの pull と build が走るので少し時間がかかります
$ docker build -f .docker/containers/app/Dockerfile -t hello-rails-on-k8s-app:latest .
$ docker build -f .docker/containers/nginx/Dockerfile -t hello-rails-on-k8s-nginx:latest .
$ kubectl apply -f .docker/kubernetes/webserver.yml
$ kubectl apply -f .docker/kubernetes/dbserver.yml
最後に下記のコマンドを打ちます
kubernetes が管理するオブジェクトを確認できます
$ kubectl get all
こんな感じの結果が返ってきたら成功です
NAME READY STATUS RESTARTS AGE
pod/dbserver-deployment-6dd6fbf959-hf52l 0/1 ErrImageNeverPull 0 4m
pod/webserver-deployment-65d64cb4d8-x9vdt 1/2 ErrImageNeverPull 0 4m
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/dbserver-service ClusterIP 10.100.147.224 <none> 3306/TCP 4m
service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 109d
service/webserver-service ClusterIP 10.106.16.206 <none> 84/TCP 4m
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
deployment.apps/dbserver-deployment 1 1 1 0 4m
deployment.apps/webserver-deployment 1 1 1 0 4m
NAME DESIRED CURRENT READY AGE
replicaset.apps/dbserver-deployment-6dd6fbf959 1 1 0 4m
replicaset.apps/webserver-deployment-65d64cb4d8 1 1 0 4m
Pod x2, Service x2, Deployment x2, ReplicaSet x2 が動いているのがわかります
いろいろイジる
kubernetes はオブジェクトの定義に基づいてオブジェクトを展開・管理します
その定義ファイルをみていじってみましょう
定義を見る
こちらが web サーバの Service, Deployment, ReplicaSet, Pod の定義です
# Service の定義
apiVersion: v1
kind: Service
metadata:
# この Service の名前
name: webserver-service
spec:
type: ClusterIP
# Label がこれらにマッチした Pod にアクセスを流す
selector:
app: hello-rails-on-k8s
server: web
# ポートの指定
ports:
- name: http
protocol: TCP
# 受信するポート
port: 84
# 送信するポート
targetPort: http
---
# Deployment の定義
apiVersion: apps/v1
kind: Deployment
metadata:
# この Deployment の名前
name: webserver-deployment
# この Deployment につけるラベル
labels:
app: hello-rails-on-k8s
# 管理する ReplicaSet の定義
spec:
# 展開するレプリカ数
replicas: 1
# Label がこれらにマッチした Pod をこの ReplicaSet の配下に置く
selector:
matchLabels:
app: hello-rails-on-k8s
server: web
# 展開する Pod の定義
template:
metadata:
# この Pod につける Label
labels:
app: hello-rails-on-k8s
server: web
spec:
volumes:
- name: sockets
emptyDir: {}
# Container の定義
containers:
- name: nginx
# イメージの指定
image: hello-rails-on-k8s-nginx:latest
imagePullPolicy: Never
ports:
# Service から受けるポート
- name: http
containerPort: 8080
volumeMounts:
- mountPath: /sockets
name: sockets
- name: rails
# イメージの指定
image: hello-rails-on-k8s-app:latest
imagePullPolicy: Never
env:
- name: DB_HOST
# Service にはドメインが割り振られる
value: dbserver-service.default.svc.cluster.local
volumeMounts:
- mountPath: /sockets
name: sockets
Rails にアクセスしてみる
動作確認のために Rails にアクセスしてみましょう
と、言いたいところですが今はアクセスできません
kubernetes のネットワークを司る Serivce の状況を見てみましょう
$ kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
dbserver-service ClusterIP 10.100.147.224 <none> 3306/TCP 18h
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 110d
webserver-service ClusterIP 10.107.232.16 <none> 84/TCP 7s
ここで TYPE
列に ClusterIP
と記載があります
ClusterIP は kubernetes の中でのみ解決できる IP になります
そのため kubernetes の外、つまりローカルマシンからアクセスできる IP が存在しない状態です
PORT(S)
列をみるとポートを EXPOSE
しているだけなのがわかります
これを解決するためには NodePort
という type を設定します
NodePort は kubernetes の外からもアクセスできる IP です
ということで NodePort を設定してみましょう
webserver-service
と dbserver-service
の 2つの Service がありますが、 webserver-service
のみに設定します
webserver-service
は Nginx に繋がっていて dbserver-service
は MySQL に繋がっています
MySQL を外に出す必要はないので dbserver-service
は ClusterIP を使うべきです
なので webserver-service
のみ設定します
( kubernetes
という Service もありますが、これは kubernetes が内部的に使用しているものです)
さて .docker/kubernetes/webserver.yml
を編集します
Service の定義の中で .spec.type
の値を ClusterIP
から NodePort
に変更します
そして下記のコマンドで変更を適用します
$ kubectl delete svc webserver-service
service "webserver-service" deleted
$ kubectl apply -f .docker/kubernetes/webserver.yml
service/webserver-service created
deployment.apps/webserver-deployment unchanged
変更を確認しましょう
$ kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
dbserver-service ClusterIP 10.100.147.224 <none> 3306/TCP 19h
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 110d
webserver-service NodePort 10.107.232.16 <none> 84:30956/TCP 20m
webserver-service
の PORT(S)
列が変わりました
84:30956
となっているので 30956
へのアクセスを 84
へ流します
実際に http://localhost:30956/
にブラウザでアクセスしてみましょう
おなじみの Yay! You're on Rails!
を見れるかと思います
冗長化してみる
ReplicaSet を 2つにすることで冗長化してみます
まずは現状を見てみます
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
dbserver-deployment-685f496464-97qk7 1/1 Running 0 1h
webserver-deployment-5595df784c-sv5q9 2/2 Running 0 1h
webserver
で始まる Pod が 1つあります
次に .docker/kubernetes/webserver.yml
を編集します
ReplicaSet の設定の中で .spec.replicas
の値を 1 から 2 に変更します
そして kubectl apply
コマンドで変更を適用します
$ kubectl apply -f .docker/kubernetes/webserver.yml
kubectl get pod
コマンドで変更を確認します
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
dbserver-deployment-685f496464-97qk7 1/1 Running 0 98m
webserver-deployment-5595df784c-crgkx 2/2 Running 0 4s
webserver-deployment-5595df784c-sv5q9 2/2 Running 0 93m
webserver
で始まる Pod が 2つになりました
この状態で puma のアクセスログをみてみましょう
$ kubectl logs -f webserver-deployment-5595df784c-crgkx rails
# 別のウィンドウで
$ kubectl logs -f webserver-deployment-5595df784c-sv5q9 rails
ブラウザでリロードしまくると kubernetes が各 Pod に対してロードバランシングするためそれぞれにアクセスがあることが確認できます
デプロイしてみる
Deployment を使って新しいバージョンのアプリケーションをデプロイしてみましょう
タグを hello-rails-on-k8s-app:v2
に変更してビルドします
docker build -f .docker/containers/app/Dockerfile -t hello-rails-on-k8s-app:v2 .
.docker/kubernetes/webserver.yml
を編集します
Deployment の設定の中で .spec.template.spec.containers[1].image
の値を変更します
hello-rails-on-k8s-app:latest
を hello-rails-on-k8s-app:v2
にします
まず、現状を確認するために kubectl describe
コマンドを打ちます
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
dbserver-deployment-6dd6fbf959-hf52l 0/1 ErrImageNeverPull 0 17h
webserver-deployment-65d64cb4d8-gwmk5 2/2 Running 6 1h
$ kubectl describe pod webserver-deployment-544cb4c74f-4qsds
~~ 略 ~~
rails:
Container ID: docker://881c9508e1df9dfeb21df52e2293ac8f75fea5b15f6a1de0a18b7ec9dbbc32d9
Image: hello-rails-on-k8s-app:latest
Image ID: docker://sha256:c482a73c4174c8e1005b8ffa58bea421ae27b4ca08be350bec31f52502e2b9fa
~~ 略 ~~
rails のイメージには hello-rails-on-k8s-app:latest
が使われています
そして kubectl apply
コマンドで変更を適用します…の前にひとつコマンドを打ちましょう
$ kubectl get pod -w
NAME READY STATUS RESTARTS AGE
dbserver-deployment-685f496464-97qk7 1/1 Running 0 98s
webserver-deployment-65d64cb4d8-gwmk5 2/2 Running 0 67s
ログが流れてくるので別のウィンドウを開いて kubectl apply
コマンドを打ちましょう
$ kubectl apply -f .docker/kubernetes/webserver.yml
そうすると kubectl get pod -w
を打ったウィンドウで Pod の作成状況が流れてきます
webserver-deployment-5595df784c-sv5q9 0/2 Pending 0 0s
webserver-deployment-5595df784c-sv5q9 0/2 Pending 0 0s
webserver-deployment-5595df784c-sv5q9 0/2 ContainerCreating 0 0s
webserver-deployment-5595df784c-sv5q9 2/2 Running 0 3s
webserver-deployment-65d64cb4d8-gwmk5 2/2 Terminating 0 4m6s
webserver-deployment-65d64cb4d8-gwmk5 0/2 Terminating 0 4m38s
webserver が 2つあって Running
と Terminating
だったりします
Deployment が RollingUpdate して新しいコンテナを展開して、展開できたら古いコンテナを削除する、という動きが見れますね
kubectl describe
を打ちましょう
~~ 略 ~~
rails:
Container ID: docker://651ad2790d7d103dbc24df0a84dbaa2e833a2180cdc384cc684090be4c20f668
Image: hello-rails-on-k8s-app:v2
Image ID: docker://sha256:2722376b4a713a3660ec74bf9febd63269dfa973f31a2dc10e48935b419952fe
~~ 略 ~~
ちゃんと rails のイメージには hello-rails-on-k8s-app:v2
が使われています
Deployment にて、使用するイメージを指定して apply
したら kubernetes がよしなにやってくれることがわかりました
オブジェクトを削除する
kubectl delete
コマンドできれいさっぱり削除できます
$ kubectl delete -f .docker/kubernetes/webserver.yml
$ kubectl delete -f .docker/kubernetes/dbserver.yml
まとめ
いかがでしたでしょうか?
この目標は達成できましたか?
- kubernetes の後述する基本的な概念を雰囲気理解すること
- ローカルのクラスタにデプロイして Ruby on Rails ( Rails ) の 「 Yay! 」のページを見ること
kubernetes の入り口をざっと解説してみました
ですが、解説できなかったものもあります
なぜ ClusterIP が 2つあるのか、 socket をどうやって繋げているのか、定義ファイルをコメントで逃げた、などなど
また、 tkhr/hello-rails-onlk8s のリポジトリでは fluentd と kibana を動かしていたり、 kubernetes のデプロイを helm で行っていたりします
それらについても触れたかったのですが、読みごたえがすごいことになってしまうので割愛しました
もっといろいろできるのが kubernetes とのことです
なので僕もこれから勉強していきたいと思います
できる人教えてください