■初めに
以前に投稿した「Dockerについて学んだこと」の続きとして、Kubernetesに関して学んだことをまとめておきます。一通りKubernetesの基本となる概念やコマンドに関しては網羅できているかと思いますので、学習の際に参考にしていただけるとよいかと思います。
■Kubernetesとは
KubernetesはDockerなどのコンテナを管理するツールです。複数ホスト上でコンテナを扱う場合などに重宝されています。
docker runとdocker-composeの違いとしては、同一ホストでの複数のコンテナ管理か単数コンテナ管理かという違いです。ただし、この場合だと、ホストが消滅するといったことが起こった場合にコンテナも併せて消滅してしまいます。そのため、ホストを複数作成し、複数のコンテナをこれらの作成されたホスト群に割り当てるということを行うのが、コンテナオーケストレーションツールです。このツールとして最も利用されているのがKubernetesです。
Kubernetesを用いることのメリットとしては下記のようなことが挙げられます。
・複数ホスト上の複数コンテナへのロードバランシング/ワークロードの分散ができる。
・1つのPod(コンテナ)が停止した場合にも自不動修復できる。
・無停止更新(ローリング・アップデート)できる。
・アプリ稼働中にスケールアップ、スケールダウンできる。
■学習環境
ローカル環境にて学習環境を構築される場合は下記の記事等を参考にインストールを行ってください。
https://qiita.com/khara_nasuo486/items/08d7918690c4416ec28c
またWeb上でKubernetesを使うことができるサイトとして、下記を記載しておきます。ローカル環境に環境を構築するのが面倒な方はこちらを利用ください。
https://labs.play-with-k8s.com/
■Kubernetesにおけるノードについて
KubernetesではマスターノードというDockerでいうDaemonのような役割を持ったノードにクライアントからコマンドを送り、それを受け取ったマスターノードがワーカーノードというノードにコマンドを送るという構造になっています。
マスターノードの役割としては下記のようなものが挙げられます。
・クラスタの制御
・スケジューリングや死活監視
・負荷分散、自動修復
ワーカーノードの役割としては下記のようなものが挙げられます。
・コンテナが実行されるサーバ
・複数ノードでクラスタを形成
・マスターノードの命令に従いPodの作成、削除
クライアントは実行コマンドを書き込むコンソールのことですが、Kubernetesの場合はkubectlコマンドというコマンドを通じて、マニフェストという各種設定を記載したファイルを作成したり、マニフェストファイルに基づきマスターノードにリソースの操作を行うように命令を行ったりします。
■Hello Worldを用いたハンズオン
▶Kubernetes Pod起動
Podとはコンテナをグループ化した仮想ホストのようなものを指しています。このPodはKubernetesでの管理の基本単位になっており、仮想Network Interface(同一のIP、ファイルシステム)を共有しているため、仮想ホストのような役割を果たします。
このようなPodを作成し情報を確認していく方法を見ていきます。
●kubectl run
まずはPodの起動です。下記のコマンドを実行することによりPodが起動されます。今回の場合はPodを起動したいため、--restart Neverというオプションを入れていますが、こちらは作成したいものによって変更する必要があります。
kubectl run –image [イメージ名]:[タグ名] –restart Never [Pod名]
=====HelloWorldのイメージ=====
$kubectl run --image hello-world:latest --restart Never helloworld
#####実行結果#####
pod/helloworld created
kubectl runについてもdocker runの場合と同じく多種のオプションを指定することができます。例えば-eオプションを用いることで、環境変数を設定することができます。その他のオプションに関しては下記のページを参照してください。
https://kubernetes.io/ja/docs/reference/kubectl/overview/
●kubectl get
Podの一覧を表示するには下記のコマンドを実行します。
$kubectl get pod
#####実行結果#####
NAME READY STATUS RESTARTS AGE
helloworld 0/1 Completed 0 3m6s
●kubectl logs
Podのログを見たい場合には下記のコマンドを実行します。
kubectl logs [Pod名]
=====HelloWorldのイメージ=====
$kubectl logs helloworld
#####実行結果#####
Hello from Docker!
This message shows that your installation appears to be working correctly.
To generate this message, Docker took the following steps:
1. The Docker client contacted the Docker daemon.
2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
(amd64)
3. The Docker daemon created a new container from that image which runs the
executable that produces the output you are currently reading.
4. The Docker daemon streamed that output to the Docker client, which sent it
to your terminal.
To try something more ambitious, you can run an Ubuntu container with:
$ docker run -it ubuntu bash
Share images, automate workflows, and more with a free Docker ID:
https://hub.docker.com/
For more examples and ideas, visit:
https://docs.docker.com/get-started/
●kubectl describe
Podのメタデータを見たい場合には下記のコマンドを実行します。
kubectl describe pod [Pod名]
=====HelloWorldのイメージ=====
$kubectl describe pod helloworld
#####実行結果#####
Name: helloworld
Namespace: default
Priority: 0
Node: minikube/192.168.49.2
Start Time: Mon, 18 Jul 2022 15:18:17 +0900
Labels: run=helloworld
Annotations: <none>
Status: Succeeded
IP: 172.17.0.3
IPs:
IP: 172.17.0.3
Containers:
helloworld:
Container ID: docker://c9c20f5ebe1ffe3d5f69767e80f6f3ae0c515bb743028a38ab66f169c010b0af
Image: hello-world:latest
Image ID: docker-pullable://hello-world@sha256:53f1bbee2f52c39e41682ee1d388285290c5c8a76cc92b42687eecf38e0af3f0
###############################中略###############################
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 11m default-scheduler Successfully assigned default/helloworld to minikube
Normal Pulling 11m kubelet Pulling image "hello-world:latest"
Normal Pulled 11m kubelet Successfully pulled image "hello-world:latest" in 4.342894157s
Normal Created 11m kubelet Created container helloworld
Normal Started 11m kubelet Started container helloworld
●kubectl delete
作成したポッドの削除を行うには下記のコマンドを実行します。
kubectl delete pod [Pod名]
=====HelloWorldのイメージ=====
$kubectl delete pod helloworld
#####実行結果#####
pod "helloworld" deleted
●kubectl exec
作成したPodの中に入るためには下記のコマンドを実行します。
kubectl exec -it [Pod名] [シェル名]
=====HelloWorldのイメージ=====
$kubectl run --image gcr.io/google-samples/hello-app:1.0 --restart Never helloworld
$kubectl exec -it helloworld sh
#####実行結果#####
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
/ #
▶Serviceを用いたクラスタ内外部への公開
●クラスタ内部からの接続
まずはクラスタ内からの接続確認を行います。クラスタ内にcurlコンテナが入っているPodを作成しShell接続を行ったのちに、helloworld PodにCurlのアクセステストを行います。
$kubectl run --port 8080 --image gcr.io/google-samples/hello-app:1.0 --restart Never helloworld
$kubectl get pod
#####実行結果#####
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
helloworld 1/1 Running 0 3m25s 172.17.0.3 minikube <none> <none>
$kubectl run --image curlimages/curl:latest -it --restart Never --rm curl sh
#####開いたシェルで下記を入力#####
#curl 172.17.0.3:8080
#####実行結果#####
Hello, world!
Version: 1.0.0
Hostname: helloworld
この結果を見ると、curlにてhelloworldのIPである「172.17.0.3」を指定し、またポートを8080にて開いたため、これを指定することで、curlコンテナからhellorowldコンテナに接続ができたことがわかります。
これが内部にあることを確認するためにcurlコンテナが動作した状態で、pod一覧をIPを含めて表示(-o wideを付ける)を行うと下記のようになり、確かに同じネットワーク内に存在していることを確認できます。
$kubectl get pod -o wide
#####実行結果#####
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
curl 1/1 Running 0 2m43s 172.17.0.4 minikube <none> <none>
helloworld 1/1 Running 0 2m53s 172.17.0.3 minikube <none> <none>
●クラスタ外部からの接続
次にクラスタ外からの接続を行いますが、結論から言うと、これはそのままではできません。これはDockerの場合と同じでホストのIPとPodのIPレンジが異なるからです。そのため、アクセスできるようにするには、Serviceというものを作成してPodを外部に公開する必要があります。
ServiceとはPodをクラスタ内外へ公開する静的なIPアドレスを持っているL4ロードバランサのことを指します。Pod自体はいつ停止して無くなるかわからないので、振り分け元のServiceを公開するということを行います。
Serviceには3つの種類がありそれぞれ下記のような特徴を持ちます。
・ClusterIP Service(クラスタ内接続)
いつ消滅するか不明なPodのIPを抽象化し、静的なIPを持ったプロキシを置くことで下記のメリットがあります。
1.Podにアクセスする際に、PodのIPを知る必要がない。
2.Podにアクセスする際に、ロードバランスしてくれる。
・NodePort Service(クラスタ内外接続)
ClusterIPでは不可能だったクラスタ外へのPodの公開をNodeIPとNodePortを経由して
可能にできるという点がメリットとなります。ただし下記のデメリットがあります。
1.NodeIPを知らないといけない。
2.Nodeポートを知らないといけない。
・LoadBalancer Service(クラスタ内外接続)
プロバイダのL4ロードバランサのDNSから各ノードの特定のポートにRoutingしてPodにアクセスを行います。
そのためNodeIP及びNodeポートを知る必要がなくなる。ただし下記のデメリットもあります
1.1つのService毎に1つのロードバランサが作成されるので高コストになる。
2.L4のロードバランサなので、L7のHTTPのホスト、パスでのLB振り分けができない。
上記のServiceそれぞれについて、内容を確認していきます。
●ClusterIP Service
HelloWorld PodをClusterIPのServiceとして、クラスタの内部に公開し、同一クラスタ内の別のPodからアクセス可能なことを見ていきます。
下記のように起動しているPodに対して--type ClusterIPのオプションを指定することでClusterIPのServiceとして公開を行うことができます。
kubectl expose pod [Pod名] --type ClusterIP [Service名]
=====helloworld Podをhelloworld-clusteripというService名で公開=====
$kubectl run --image gcr.io/google-samples/hello-app:1.0 --restart Never helloworld
$kubectl expose pod helloworld --type ClusterIP --port 8080 --name helloworld-clusterip
#####実行結果#####
service/helloworld-clusterip exposed
下記コマンドを用いることでServiceの一覧を取得します。
$kubectl get service
#####実行結果#####
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
helloworld-clusterip ClusterIP 10.102.43.35 <none> 8080/TCP 4m43s
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 10h
この結果、確かにType ClusterIPのServiceが作成されていることが確認できます。
次に、curlコンテナを再び立ち上げ、そのコンテナ内からhelloworld-clusteripを経由してhelloworldにアクセスします。
$kubectl run --image curlimages/curl:7.68.0 -it --restart Never --rm curl sh
#####開いたシェルで下記を入力#####
$curl 10.102.43.35:8080
#####実行結果#####
Hello, world!
Version: 1.0.0
Hostname: helloworld
この結果、helloworldにアクセスが行えたことが確認できました。この結果は上記で説明を行った「クラスタ内部からの接続」の節の結果と同じになっています。しかし、上記で行ったのはcurlコンテナから直接helloworldコンテナに接続を行っています。
今回行った結果は、curlコンテナからServiceであるhelloworld-clusteripにアクセスし、その後ロードバランス(1つしかPodを作成していないので特定のPodになるが)機能によって、Serviceがhelloworldにアクセスしているという流れになります。
そのため、意味合いが異なりますので、注意が必要です。
●NodePort Service
HelloWorld PodをNodeIPのServiceとして、クラスタの内外に公開し、同一クラスタ内の別のPodからアクセス可能なことを見ていきます。
下記のように起動しているPodに対して--type NodePortのオプションを指定することでClusterIPのServiceとして公開を行うことができます。
kubectl expose pod [Pod名] --type NodePortIP [Service名]
=====helloworld Podをhelloworld-nodeportというService名で公開=====
$kubectl run --image gcr.io/google-samples/hello-app:1.0 --restart Never helloworld
$kubectl expose pod helloworld --type NodePort --port 8080 --name helloworld-nodeport
#####実行結果#####
service/helloworld-nodeport exposed
下記コマンドを用いることでServiceの一覧を取得します。
$kubectl get service
#####実行結果#####
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
helloworld-nodeport NodePort 10.107.162.126 <none> 8080:30224/TCP 57s
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 11h
この結果、確かにType NodeportのServiceが作成されていることが確認できます。
まずは同一クラスタ内からアクセスできることを確認します。手順はClusterIPの場合と同じです。
$kubectl run --image curlimages/curl:7.68.0 -it --restart Never --rm curl sh
#####開いたシェルで下記を入力#####
$curl 10.107.162.126:8080
#####実行結果#####
Hello, world!
Version: 1.0.0
Hostname: helloworld
この結果、helloworld-nodeportを経由してhelloworldにアクセスが行えたことが確認できました。
次にクラスタ外(ローカル環境)からアクセス可能なことを確認していきます。接続のためにminikubeのIPが必要となるので、下記のコマンドを実行しIPを取得します。
$minikube ip
#####実行結果#####
192.168.49.2
このIPに対して、curlコマンドをローカル環境で実行してアクセスを行います。この際、ポートはPod側の30224を指定します。
$curl 192.168.49.2:30224
#####実行結果#####
Hello, world!
Version: 1.0.0
Hostname: helloworld
この結果、helloworldにアクセスが行えたことが確認できました。(minikube起動時の設定によって、アクセスできない場合があるようです。その場合はminikube service --url helloworld-nodeportコマンドを実行し、出力されたurlに対してcurlコマンドを実行してください。)
●LoadBalancer Service
HelloWorld PodをNodeIPのServiceとして、クラスタの内外に公開し、同一クラスタ内の別のPodからアクセス可能なことを見ていきます。
下記のように起動しているPodに対して--type LoadBalancerオプションを指定することでClusterIPのServiceとして公開を行うことができます。
kubectl expose pod [Pod名] --type NodePortIP [Service名]
=====helloworld Podをhelloworld-loadbalancerというService名で公開=====
$kubectl run --image gcr.io/google-samples/hello-app:1.0 --restart Never helloworld
$kubectl expose pod helloworld --type LoadBalancer --port 8080 --name helloworld-loadbalancer
#####実行結果#####
service/helloworld-loadbalancer exposed
下記コマンドを用いることでServiceの一覧を取得します。
$kubectl get service
#####実行結果#####
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
helloworld-loadbalancer LoadBalancer 10.105.4.118 <pending> 8080:31006/TCP 37s
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 11h
この結果、確かにType LoadBalancerのServiceが作成されていることが確認できます。
まずは同一クラスタ内からアクセスできることを確認します。手順はClusterIPの場合と同じです。
$kubectl run --image curlimages/curl:7.68.0 -it --restart Never --rm curl sh
#####開いたシェルで下記を入力#####
$curl 10.105.4.118:8080
#####実行結果#####
Hello, world!
Version: 1.0.0
Hostname: helloworld
この結果、helloworld-LoadBalancerを経由してhelloworldにアクセスが行えたことが確認できました。
次にクラスタ外(ローカル環境)からアクセス可能なことを確認していきます。
$curl $(minikube service helloworld-loadbalancer --url)
#####実行結果#####
Hello, world!
Version: 1.0.0
Hostname: helloworld
この結果、helloworldにアクセスが行えたことが確認できました。
▶Ingressを用いたクラスタ外部への公開
IngressとはPodをクラスタ内外に公開するL7(httpに基づいて振り分け可能)のロードバランサのことです。クラスタ外部からのURLのホスト・パスによるServiceの振り分けが可能となります。外部からのアクセスはパブリックDNSを用いて行います。
これを用いる方法を見ていきます。下記コマンドを用いて、minikubeにIngressのアドオンを有効化します。
$minikube addons enable ingress
その後下記コマンドを実行し、minikubeのアドオン一覧を表示し、ingressが有効化された(enableになった)ことを確認します。
$minikube addons list
#####実行結果#####
|-----------------------------|----------|--------------|--------------------------------|
| ADDON NAME | PROFILE | STATUS | MAINTAINER |
|-----------------------------|----------|--------------|--------------------------------|
| ambassador | minikube | disabled | 3rd party (Ambassador) |
| auto-pause | minikube | disabled | Google |
| csi-hostpath-driver | minikube | disabled | Kubernetes |
| dashboard | minikube | disabled | Kubernetes |
| default-storageclass | minikube | enabled ✅ | Kubernetes |
| efk | minikube | disabled | 3rd party (Elastic) |
| freshpod | minikube | disabled | Google |
| gcp-auth | minikube | disabled | Google |
| gvisor | minikube | disabled | Google |
| headlamp | minikube | disabled | kinvolk.io |
| helm-tiller | minikube | disabled | 3rd party (Helm) |
| inaccel | minikube | disabled | InAccel <info@inaccel.com> |
| ingress | minikube | enabled ✅ | 3rd party (unknown) |
| ingress-dns | minikube | disabled | Google |
| istio | minikube | disabled | 3rd party (Istio) |
| istio-provisioner | minikube | disabled | 3rd party (Istio) |
| kong | minikube | disabled | 3rd party (Kong HQ) |
| kubevirt | minikube | disabled | 3rd party (KubeVirt) |
| logviewer | minikube | disabled | 3rd party (unknown) |
| metallb | minikube | disabled | 3rd party (MetalLB) |
| metrics-server | minikube | disabled | Kubernetes |
| nvidia-driver-installer | minikube | disabled | Google |
| nvidia-gpu-device-plugin | minikube | disabled | 3rd party (Nvidia) |
| olm | minikube | disabled | 3rd party (Operator Framework) |
| pod-security-policy | minikube | disabled | 3rd party (unknown) |
| portainer | minikube | disabled | Portainer.io |
| registry | minikube | disabled | Google |
| registry-aliases | minikube | disabled | 3rd party (unknown) |
| registry-creds | minikube | disabled | 3rd party (UPMC Enterprises) |
| storage-provisioner | minikube | enabled ✅ | Google |
| storage-provisioner-gluster | minikube | disabled | 3rd party (unknown) |
| volumesnapshots | minikube | disabled | Kubernetes |
|-----------------------------|----------|--------------|--------------------------------|
その後下記のコマンドを実行し、Ingress controller podが作成されていることを確認します。
$kubectl get pods -n ingress-nginx
#####実行結果#####
NAME READY STATUS RESTARTS AGE
ingress-nginx-admission-create-g95fq 0/1 Completed 0 27m
ingress-nginx-admission-patch-6t9vb 0/1 Completed 0 27m
ingress-nginx-controller-755dfbfc65-gflns 1/1 Running 1 (16m ago) 27m
minikubeの場合はこのようにアドオンを有効にすることで、Ingressコントローラを作成できるが、クラウド環境の場合はIngressコントローラをインストールする必要があります。
次にアクセスを公開したいパスをyamlで定義する必要があります。今回はhello-world Podを全てのパス「/」で公開するように実施を行います。そのために下記のようなyamlファイルを用います。
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: helloworld
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /$1
spec:
rules:
- http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: helloworld-nodeport
port:
number: 8080
このyamlファイルではIngressの名前をhelloworldにして、httpの下にパスに関するルールを記載しているという構造になっています。またpathとして「/」に来たらどのServiceにアクセスさせるのかということをbackendの箇所にて記載していて、この場合はhelloworld-nodeportにポート8080でアクセスすることを定義しています。
このファイルを用いてマニフェストの作成を行います。マニフェストに関しては後ほど説明を行いますが、ここでは設定を反映させる程度の認識でよいです。下記のコマンドを用いて、これを実行します。
$kubectl apply -f ingress.yaml
#####実行結果#####
ingress.networking.k8s.io/helloworld created
Ingressをリストアップし、上記で作成したhelloworldのingressが作成されていることを確認するため、下記のコマンドを実行します。
$kubectl get ingress
#####実行結果#####
NAME CLASS HOSTS ADDRESS PORTS AGE
helloworld nginx * 192.168.49.2 80 4m20s
またingress resourceの詳細を確認するには下記のコマンドを実行します。
kubectl describe ingress [Ingress名]
=====helloworldの場合=====
$kubectl describe ingress helloworld
#####実行結果#####
Name: helloworld
Labels: <none>
Namespace: default
Address: 192.168.49.2
Ingress Class: nginx
Default backend: <default>
Rules:
Host Path Backends
---- ---- --------
*
/ helloworld-nodeport:8080 (172.17.0.4:8080)
Annotations: nginx.ingress.kubernetes.io/rewrite-target: /$1
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Sync 7m50s (x2 over 7m54s) nginx-ingress-controller Scheduled for sync
また下記のコマンドを実行することでIngress コントローラのIPを得ることができ、これを用いて、Ingress経由でhelloworld-nodeport Serviceにアクセスを行います。
$kubectl get ingress | awk '{ print $4 }'|tail -1
#####実行結果#####
192.168.49.2
=====Ingress経由でアクセス=====
$curl 192.168.49.2
#####実行結果#####
Hello, world!
Version: 1.0.0
Hostname: helloworld
この結果から、無事にIngress経由で、helloworld-nodeportのServiceにアクセスし、さらにhelloworld Podにアクセスできたことがわかります。
▶Replicaによるスケールアップ
システムの冗長化を行うためにはReplicaというリソースを用います。ReplicaではSpecにて定義したレプリカの数を自動配置・維持(Podが停止した場合に自動修復)する機能を持っています。
こちらもyamlファイルに設定を記載していく必要があります。ここでSampleとして用いるのは下記のようなyamlファイルです。
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: helloworld
labels:
app: helloworld
spec:
replicas: 5
selector:
matchLabels:
app: helloworld
template:
metadata:
labels:
app: helloworld
spec:
containers:
- name: helloworld
image: gcr.io/google-samples/hello-app:1.0
このyamlファイルのreplicasの箇所で指定した値に応じて、Podが複製されます。今回のファイルでは5としているため、5つのPodが作成されます。
このyamlファイルを用いてPodをReplicasetとして起動するため下記のコマンドを実行します。
$kubectl apply -f replicaset.yaml
#####実行結果#####
replicaset.apps/helloworld created
replicasetが作成されていることを確認するため、下記のコマンドを実行します。
$kubectl get replicaset
#####実行結果#####
NAME DESIRED CURRENT READY AGE
helloworld 5 5 5 3m21s
=====Podを確認=====
$kubectl get pod
#####実行結果#####
NAME READY STATUS RESTARTS AGE
helloworld-8nkgp 1/1 Running 0 4m32s
helloworld-8rf9j 1/1 Running 0 4m32s
helloworld-jcdgd 1/1 Running 0 4m32s
helloworld-tltwb 1/1 Running 0 4m32s
helloworld-x4mf5 1/1 Running 0 4m32s
replicasetが作成されていることを確認でき、また5つのPodが作成されていると表示されているため、Podの一覧を表示した結果、確かにPodが作成されていることが確認できます。またこれらのPodはす全て、helloworld-[ハッシュ]という名前で作成されていることも確認できます。
このように作成したPodの個数を起動中に増やすということも可能です。これはkubectl scaleコマンドにて--replicasオプションを付けることでで実現できます。例として10個に増やしてみます。
$kubectl scale --replicas=10 replicaset/helloworld
=====replicasetを確認=====
$kubectl get replicaset
#####実行結果#####
NAME DESIRED CURRENT READY AGE
helloworld 10 10 10 13m
=====Podを確認=====
$kubectl get pod
#####実行結果#####
NAME READY STATUS RESTARTS AGE
helloworld-8nkgp 1/1 Running 0 14m
helloworld-8rf9j 1/1 Running 0 14m
helloworld-j6jzt 1/1 Running 0 2m15s
helloworld-jcdgd 1/1 Running 0 14m
helloworld-r8tcj 1/1 Running 0 2m15s
helloworld-tltwb 1/1 Running 0 14m
helloworld-tqb9g 1/1 Running 0 2m15s
helloworld-vmbk7 1/1 Running 0 2m15s
helloworld-wltrq 1/1 Running 0 2m15s
helloworld-x4mf5 1/1 Running 0 14m
実行結果を見るとPodの個数が10個に増えていることを確認できます。
次に起動したPodを一つ停止させてみたいと思います。この際の動きとしては、10個あるうちの1個のPodが停止したとreplicasetが判断し、Podを再作成するという動きになります。この際停止前のものとは異なるハッシュが割り当てられるので、1つ分のPodが別のハッシュに変わります。試しに上記のhelloworld-8nkgpを停止させた場合を下記に記載します。
$kubectl delete pod helloworld-8nkgp
#####実行結果#####
pod "helloworld-8nkgp" deleted
=====Podの状態確認=====
$kubectl get pod
#####実行結果#####
NAME READY STATUS RESTARTS AGE
helloworld 1/1 Running 0 23h
helloworld-8rf9j 1/1 Running 0 20m
helloworld-c7gn7 1/1 Running 0 2s
helloworld-j6jzt 1/1 Running 0 8m22s
helloworld-jcdgd 1/1 Running 0 20m
helloworld-r8tcj 1/1 Running 0 8m22s
helloworld-tltwb 1/1 Running 0 20m
helloworld-tqb9g 1/1 Running 0 8m22s
helloworld-vmbk7 1/1 Running 0 8m22s
helloworld-wltrq 1/1 Running 0 8m22s
helloworld-x4mf5 1/1 Running 0 20m
この場合、停止したhelloworld-8nkgpの代わりにhelloworld-c7gn7が自動生成されているということが確認できます。
▶Rolling Update,Roll back
次にDeploymentというリソースを用いてPodのRolling Update、Rollback(無停止更新)を行います。DeploymentではPodのDeploy時に新しいReplicaSetを作成し、古いReplicaSetで管理されているPodを停止させながら、新しいReplicaSetで管理されているPodを作成するという手順を段階的に行います。そのため、新しい(Version Up後等)ReplicaSetに更新後のアプリケーションを載せることで無停止での更新を実現します。上記はUpdateについてですが、Rollback(元の状態に戻す)こともでき、この場合にも同様に停止と起動を段階的に行うことで、無停止でのRollbackを行うことも可能です。
このDeploymentの作成にはkubectl create deploymentコマンドを使用します。
下記の例ではtagに1.0を指定したhelloworldから、tagに2.0が指定されているhelloworldに切り替えを行っています。まずはPodが一つだと切り替えがわかりにくいので、Podを5つにスケールアップを行います。
kubectl create deployment --image [イメージ名] [deployment名]
=====tag 1.0のhelloworld Podの立ち上げ=====
$kubectl create deployment --image gcr.io/google-samples/hello-app:1.0 helloworld
=====deploymentの確認=====
$kubectl get deployment
#####実行結果#####
NAME READY UP-TO-DATE AVAILABLE AGE
helloworld 1/1 1 1 17s
=====スケールアップ=====
$kubectl scale --replicas=5 deploy/helloworld
=====deploymentの確認=====
$kubectl get deployment
#####実行結果#####
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/helloworld 3/3 3 3 13m
次にRolling Updateを行いますが、そのためにはPod群のnameを取得する必要があります。(上記のやり方では各Pod名helloworldとは異なるnameが付く。合わせる方法もあるがここでは解説は行わない)そのために作成したdeploymentを構成するyamlファイルを出力します。出力は下記のコマンドで行えます。
kubectl get deployment helloworl --output yaml
#####出力結果#####
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
deployment.kubernetes.io/revision: "1"
creationTimestamp: "2022-07-20T13:57:15Z"
generation: 2
labels:
app: helloworld
name: helloworld
namespace: default
resourceVersion: "99302"
uid: 6fd303f4-c30a-433b-909f-1cb49ffe3d50
spec:
progressDeadlineSeconds: 600
replicas: 3
revisionHistoryLimit: 10
selector:
matchLabels:
app: helloworld
strategy:
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
type: RollingUpdate
template:
metadata:
creationTimestamp: null
labels:
app: helloworld
spec:
containers:
- image: gcr.io/google-samples/hello-app:1.0
imagePullPolicy: IfNotPresent
name: hello-app
resources: {}
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
dnsPolicy: ClusterFirst
restartPolicy: Always
schedulerName: default-scheduler
securityContext: {}
terminationGracePeriodSeconds: 30
status:
availableReplicas: 3
conditions:
- lastTransitionTime: "2022-07-20T13:57:15Z"
lastUpdateTime: "2022-07-20T13:57:17Z"
message: ReplicaSet "helloworld-7b8f9795b8" has successfully progressed.
reason: NewReplicaSetAvailable
status: "True"
type: Progressing
- lastTransitionTime: "2022-07-20T14:10:49Z"
lastUpdateTime: "2022-07-20T14:10:49Z"
message: Deployment has minimum availability.
reason: MinimumReplicasAvailable
status: "True"
type: Available
observedGeneration: 2
readyReplicas: 3
replicas: 3
updatedReplicas: 3
この出力結果のspec-template-spec-containers-nameがPod群の名前になります。この場合はhello-appとなっていますので、これを用います。Rolling Updateには下記のコマンドを用います。
kubectl set image deploy/[deployment名] [上記で取得したname]=[イメージ名]
=====tag2.0のhelloworld Podの立ち上げ=====
$kubectl set image deploy/helloworld helloworld=gcr.io/google-samples/hello-app:2.0
=====途中結果を出力=====
$kubectl get pod
#####出力結果#####
NAME READY STATUS RESTARTS AGE
helloworld-666776755c-4x8hl 1/1 Running 0 2s
helloworld-666776755c-qpcdv 1/1 Running 0 2s
helloworld-666776755c-qtvkh 0/1 Pending 0 0s
helloworld-666776755c-tx254 1/1 Running 0 2s
helloworld-7b8f9795b8-4fkmh 1/1 Running 0 12m
helloworld-7b8f9795b8-hdppp 1/1 Terminating 0 12m
helloworld-7b8f9795b8-p8qp6 1/1 Running 0 11m
helloworld-7b8f9795b8-ph7b9 1/1 Running 0 11m
=====最終結果を出力=====
$kubectl get pod
#####出力結果#####
NAME READY STATUS RESTARTS AGE
helloworld-666776755c-4x8hl 1/1 Running 0 2m42s
helloworld-666776755c-6cxnv 1/1 Running 0 2m40s
helloworld-666776755c-qpcdv 1/1 Running 0 2m42s
helloworld-666776755c-qtvkh 1/1 Running 0 2m40s
helloworld-666776755c-tx254 1/1 Running 0 2m42s
出力結果を見ることで、古いhelloworldが徐々に削除され、新しいhelloworldが作成されていることが確認できます。今回の出力結果ではPodとして存在している個数が途中経過の時点で8個となっています。これは停止途中に作成が行われたりするために存在しているPodの個数が増えています。この最大個数は運用環境のリソース(メモリなど)によって違いがあります。そのため、最大で何個まで存在できるのかを指定することもできます。詳細については下記記事等を参照ください。
https://qiita.com/Esfahan/items/f9c246e3c60fe8490af3
またRollbackについては下記のコマンドにて実行できます。動きとしては同様となるため、確認は記載しておりません。
$deployment.apps/helloworld rolled back
#####実行結果#####
deployment.apps/helloworld rolled back
■Kubernetesマニフェスト
ここまでの話では基本的にコマンド内にPod等の情報を記載するようにコマンドを記載し、都度ほしいものが手に入るようにコマンドの例を挙げていきました。例えば一つ前の節のDeploymentを立ち上げ、その後にPod数を増やすといったような書き方が典型例になります。このように都度行いたいことを命令することを命令的であるといいます。この方法ではhowに注目していることになります。
これに対して、最終的に得たい状態を記載して指定して作成を行うことを宣言的といいます。宣言的な場合にはwhatに注目して作成を行っていきます。
Kubernetesでは宣言的な記述方法として、yamlファイル(マニフェスト)に情報を記載するという方法を用います。このyamlファイルの中に最終的に得たいPod等の情報(what)を書き込むことで目的のものをKubernetes側で自動で作成します。このときKubernetesではどのように(how)に作るのかは考える必要はなくなるため、このhowを抽象化できるという点が宣言的な方法のメリットと言えます。
例えば以下の2つは同じことを命令的(kubectl run)に記載したものと宣言的(yaml)に記載したものの例になります。
=====宣言的=====
kubectl run --image gcr.io/google-samples/hello-app:1.0 --restart Never Helloworld
#=====命令的=====
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: helloworld
name: helloworld
spec:
containers:
- env:
- name: TEST_ENV
value: Hello_World
image: gcr.io/google-samples/hello-app:1.0
ports:
- containerPort: 8080
命令的な記載から宣言的な記載への変換は下記のコマンドを通じて行うことも可能です。
$kubectl run --image gcr.io/google-samples/hello-app:1.0 --restart Never helloworld --dry-run -o yaml helloworld > pod.yaml
このyamlを指定して、kubectl runと同じPodを作成するには、kubectl applyコマンドにて、作成したyamlファイルを指定することで可能です。
$kubectl apply -f pod.yaml
次にyamlファイルの記載内容に関してみていきたいと思います。ただし、全てをここに記載すると、量が膨大になるので一部のみ記載し、他の箇所に関しては公式ページ等を参考にしてください。上記で記載したPod.yamlがおそらく最も単純な形になります。ただし大雑把な構成としては同じです。
このyamlファイルの1段目に記載されているものから見ていきます。大きく4つの項目があることがわかると思います。これらはそれぞれ下記のような意味を持ちます。
key | 意味 |
---|---|
apiVersion | 表示させたいKubernetes ObjectのVersionを指定(以下参照) https://qiita.com/soymsk/items/69aeaa7945fe1f875822 |
kind | apiVersion で指定した Kubernetes Object セットの中から使いたいタイプを指定 例:Pod、ReplicaSet、Deployment |
metadata | オブジェクトの情報を指定 (名前など一意に特定するための情報 |
spec | オブジェクトに適用する(したい)ものを指定 |
上記の4項目はyamlファイルを作成する際には必須の項目となります。また、作成対象の種類によって2段目以降はに記載する内容は変わってくるため、詳細な説明は割愛します。下記のページなどにそれぞれの意味について記載されているので、参照してください。
https://tanakakns.github.io/kubernetes/manifest/
今回作成したファイルについては下記のような設定を記載しています。
1段目 | 2段目 | 3段目 | 説明 |
---|---|---|---|
metadata | - | - | - |
- | creationTimestamp | - | ノードの作成時刻残すかの設定 |
- | labels | - | Kubernetes ノード ラベル(下記参照) https://kubernetes.io/ja/docs/concepts/overview/working-with-objects/labels/ |
- | name | - | namespace内で一意なオブジェクトの名前を設定 |
spec | - | - | - |
- | containers | - | コンテナの仕様を設定する |
- | - | env | 環境変数をコンテナに指定する |
- | - | image | dockerイメージのリポジトリとタグ |
- | - | ports | ポッド外部からリクエストをうけとるために開いたポートのリスト |
■Kubernetesのストレージ
▶Config Map
Config Mapというリソースを用いることで、環境変数などをKey Valueペアで保存することが可能です。Config Mapでは設定した環境変数をvolumeとしてPodにマウントを行います。
環境変数の設定はマニフェスト内のspecの中にenvという項目を設けて、設定することも可能です。しかし、この場合変数の再利用ができなくなってしまします。この問題点をConfig Mapを用いることで解決を行うことができます。
具体的にどのように作成を行うのかについて見ていきます。まずはConfig Mapマニフェストの作成を行います。これはkubectl createコマンドにて、configmapを指定することで実現可能です。下記の例ではyamlファイルの作成を行っています。この場合TEST_ENVという環境変数の中にHello_worldという文字列を格納しています。
$kubectl create configmap my-config --from-literal=TEST_ENV=Hello_World --dry-run -o yaml > configmap.yaml
上記コマンドを実行して作成したyamlファイルが下記のものになります。
apiVersion: v1
data:
TEST_ENV: Hello_World
kind: ConfigMap
metadata:
creationTimestamp: null
name: my-config
今回の場合、yamlファイルの1段目にdataが作成されていますが、これは作成するノードの持つデータを指定する、という項目になります。この場合は単純にkey:TEST_ENVに対してValue:Hello_worldを対応させていることを表しています。
このように作成したyamlファイルを用いてノードを生成(kubectl apply)し、環境変数を利用する場合にはPod生成時にこのConfig Mapを引用することで利用することができます。
以下のようPod生成用のyamlファイルに記載することでConfig Mapの情報の反映を行います。
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: helloworld
name: helloworld
spec:
containers:
- env:
#Pod内で利用する環境変数名を指定
- name: TEST_ENV_POD
#valueFromにてnameで指定した環境変数に何を入れるのかを指定
valueFrom:
#configMapから引用することを指定
configMapKeyRef:
#名前がmy-config(上で作成した)というconfigMapを指定
name: my-config
#nameで指定したconfigMap内でkeyがTEST_ENVとなっている環境変数を参照することを指定
key: TEST_ENV
image: gcr.io/google-samples/hello-app:1.0
ports:
- containerPort: 8080
上記のPod.yamlの場合、helloworldという名前のPodを作成するようなyamlファイルになっていますが、helloworld内の環境変数として、TEST_ENV_PODを作成し、その中身はmy-configというCondig MapのTEST_ENVに格納されている値を用いるということを記述しています。このような指定方法をすることで、他のPodにおいても同じよう利用することができるため、再利用性が高くなったことがわかるかと思います。
続いてPodにVolumeのマウントを行う方法を見ていきます。Podは管理上の基本単位になっていて、仮想NetworkInterface(IP、ファイルシステム)を共有するといことを上記で記載しましたが、これはつまりPod自体がKubernetes上では仮想ホストのように考えられることを指しています。このようなPodにvolumeとして、Config Mapをマウントします。Config Mapでは環境変数だけでなく、Key-Valueペアを保存して、そのデータをファイルとしてvolumeへのマウントを行うことが可能です。上記で見たものではConfig Mapに保存したデータを環境変数として呼び出していました。
これを実現するためにPod.yamlファイルを下記のように修正します。
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: helloworld-configmap-volume
name: helloworld-configmap-volume
spec:
containers:
- image: gcr.io/google-samples/hello-app:1.0
name: helloworld-configmap-volume
ports:
- containerPort: 8080
resoueces: {}
volumeMounts:
#volume名を指定
- name:my-config-volume
#volumeをマウントするコンテナ内のファイルパスを指定
mountPath: /my-config/TEST_ENV
volume:
#作成するvolume名を指定
- name: my-config-volume
configMap:
#Config Map名を指定
name: my-config
items:
#Config Map内にあるKeyの名前を指定
- key: TEST_ENV
path: keys
このyamlファイルではvolumeに関して、my-configという名前のConfig Mapをmy-config-volumeという名前のvolumeで指定し、この名前を指定してマウントを行い、その際に対応するコンテナ内のパス名を/my-config/TEST_ENVにするということを行っています。これによりファイル化されているため変更を行うことができるというメリットがあります。
▶永続 Volume
続いて永続 Volumme(Persistest Volume)についてみていきます。
コンテナではデータはPod内にあるVolumeに変更を行うことでデータの書き込みを行っています。そのため、Podが消えた場合にはデータも消えます。それを防ぐために、クラスターワイドにあるNode上に存在するPersistest Volumeにデータを保存するということを行います。実際動きとしては、複数のNodeがあるクラスタ内に各Nodeが共有できるvolume(Persistest Volume)を作成し、各Nodeが必要な分だけこの共通volumeに要求を行い、得たvolumeを要求元のNodeでマウントするというものです。
そのため、共通的なvolumeを定義するyamlファイル(pv.yaml)と要求事項を記載したyamlファイル(pvc.yaml)を用意し、Pod(Node)作成時に用いるyamlファイル(Pod.yaml)には、この要求事項を参照するよう記載を行います。それぞれのファイルのサンプルは下記の通りです。
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv
spec:
storageClassName: manual
#volumeのサイズを指定
capacity: 100M
#アクセス権限範囲を指定
accessModes: ReadWriteOnce
hostPath:
#Node上のパスを指定
path: "/mnt/pvc"
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc
spec:
storageClassName: manual
#アクセス権限範囲を指定
accessModes: ReadWriteOnce
resources:
#要求事項を指定
requests:
#ほしい容量を指定
storage: 10M
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: helloworld-pvc
name: helloworld-pvc
spec:
containers:
- image: gcr.io/google-samples/hello-app:1.0
name: helloworld-pvc
ports:
- containerPort: 8080
resoueces: {}
volumeMounts:
#volume名を指定
- name:my-pv
#volumeをマウントするコンテナ内のファイルパスを指定
mountPath: /mnt/pvc
volume:
#作成するvolume名を指定
- name: my-pv
#永続volume要求名を指定
persistentVolumeClaim:
name: pvc
実行する場合には上記の3ファイルを順番にkubectl appluにて実行していくことになりますが、これに先んじて、クラスタ上にフォルダを作成しておく必要があります。そのため、minikubeを使用している場合には下記コマンドでクラスタに接続し、作成を行います。
$minikube ssh
$sudo mkdir mnt/pvc