こんにちは。
株式会社クラスアクト インフラストラクチャ事業部の大塚です。
この記事ではmicrok8sで構築したKuberenetesクラスタ環境にMetalLBとLoadBalancer serviceをデプロイし、LB経由でpod内のコンテナにWebブラウザでアクセスしてみようと思います。
環境
k8sのクラスタ環境は以下となっております。nodeは3台。192.168.2.30のIPを持つnodeがMaster node。それ以外の31,32のIPを持つnodeがworker nodeとして稼働しています。
k8sクラスタではpodやservice等稼働させていません。(正確にいうと稼働していますが気にしなくていいです。)
何も起動していないこと及びnodeが3つあることを示す、コマンドのlogになります。
serviceが稼働していますが気にしなくて大丈夫です。またほかのnamespaceでも色々起動していますが、同様に気にしなくていいです。デフォルトで作られる奴です。
では早速構築していきましょう。
root@k8s-master:~/yaml# kubectl get all -o wide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
service/kubernetes ClusterIP 10.152.183.1 <none> 443/TCP 24h <none>
root@k8s-master:~/yaml# kubectl get node -o wide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
k8s-worker01 Ready <none> 7h32m v1.26.4 192.168.2.31 <none> Ubuntu 22.04.2 LTS 5.15.0-71-generic containerd://1.6.15
k8s-master Ready <none> 24h v1.26.4 192.168.2.30 <none> Ubuntu 22.04.2 LTS 5.15.0-71-generic containerd://1.6.15
k8s-worker02 Ready <none> 7h31m v1.26.4 192.168.2.32 <none> Ubuntu 22.04.2 LTS 5.15.0-71-generic containerd://1.6.15
用語
構築の前に、今回出てくるk8s関連の用語を軽くまとめておきます。
deployment
Deploymentは、PodやReplicaSetの更新を宣言的に行うものです。
Deploymentに必要な状態を記述すると、Deployment Controllerは実際の状態を制御された速度で必要な状態に変更します。Deploymentを定義して新しいReplicaSetを作成したり、既存のDeploymentを削除してそのリソースをすべて新しいDeploymentで採用することができます。
replicaset
ReplicaSetの目的は、任意の時間に実行されているレプリカPodのセットを安定的に維持することです。そのため、指定された数の同一のPodの可用性を保証するために使用されることがよくあります。
MetalLB
MetalLBとはベアメタル環境で使用できるKubernetesのExternal Load Balancerの実装の一種です。 Googleによって開発されたシンプルなロードバランサーで、LoadBalancerタイプのServiceに対する公開用IPアドレス(External IP)の割り当てと、 External IPに対する経路情報の広報、といった2つの機能を持ちます。
LoadBalancer service
クラスター外のクライアントから、Podグループにアクセスするために利用されます。NodePortと異なり外部のロードバランサーを利用してServiceを公開するため、ノードのIPアドレスやポート番号を意識する必要はなく、外部ロードバランサーに構成された仮想IPアドレスでアクセスすることが可能です。ロードバランサーに登録される負荷分散対象は、利用するCNIによって異なります。
Deploymentでpodをデプロイする
用意したyamlファイルは以下です。
deploymentの名前をnginx-httpd-deployment。常時展開するpod数を3としています。
podの中身は以前のqiitaのものを流用しております。つまり1つのpodの中にnginxコンテナとapache2コンテナを稼動させているものを使っているということになります。
root@k8s-master:~/yaml# cat web-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-httpd-deployment
labels:
app: web-app-deployment
spec:
replicas: 3
selector:
matchLabels:
app: web-app
template:
metadata:
labels:
app: web-app
spec:
containers:
- name: web-httpd
image: shotaohtsuka/my-httpd-image
ports:
- name: web-httpd
containerPort: 90
protocol: TCP
- name: web-nginx
image: nginx
ports:
- name: web-nginx
containerPort: 80
protocol: TCP
このyamlファイルを元にデプロイを実施。
get allコマンドの出力結果から、それぞれのnodeに1つずつpodが作成されていることが分かります。また、deploymentとdeploymentに内包されているreplicasも出力されていますね。
root@k8s-master:~/yaml# kubectl create -f web-deployment.yaml
deployment.apps/nginx-httpd-deployment created
root@k8s-master:~/yaml# kubectl get all -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod/nginx-httpd-deployment-759b9b5f-vq8rp 2/2 Running 0 7s 10.1.235.209 k8s-master <none> <none>
pod/nginx-httpd-deployment-759b9b5f-rsp86 2/2 Running 0 7s 10.1.79.68 k8s-worker01 <none> <none>
pod/nginx-httpd-deployment-759b9b5f-h8k67 2/2 Running 0 7s 10.1.69.197 k8s-worker02 <none> <none>
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
service/kubernetes ClusterIP 10.152.183.1 <none> 443/TCP 24h <none>
NAME READY UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR
deployment.apps/nginx-httpd-deployment 3/3 3 3 7s web-httpd,web-nginx shotaohtsuka/my-httpd-image,nginx app=web-app
NAME DESIRED CURRENT READY AGE CONTAINERS IMAGES SELECTOR
replicaset.apps/nginx-httpd-deployment-759b9b5f 3 3 3 7s web-httpd,web-nginx shotaohtsuka/my-httpd-image,nginx app=web-app,pod-template-hash=759b9b5f
LoadBalancer serviceをデプロイする
上記でデプロイしたpod群を外部公開する為にLoadBalancer serviceをデプロイしていきます。今回用意したyamlファイルは以下。
ザックリ解説すると、lb-service-httpd-nginxという名前のLoadBalancer serviceにしています。
LBは8080ポートと9090ポートでlistenし、nodeは30080ポートと30090ポートでlisten。podは80ポートと90ポートでlistenしています。
LBのIPに対して8080ポートで通信が飛んでくると各nodeの30080ポートに対して通信を投げ、各nodeの30080ポートに通信が来ると今度はNodePortが各podの80ポートに通信を投げ、結果pod内のnginxに接続されます。
同様にLBのIPに対して9090ポートで通信が飛んでくると各nodeの30090ポートに対して通信を投げ、各nodeの30090ポートに通信が来ると今度はNodePortが各podの90ポートに通信を投げ、結果pod内のapache2に接続されます。
私自身ごちゃごちゃしているので、後でイメージに落とし込みます。
root@k8s-master:~/yaml# cat deployment-service-httpd-nginx.yaml
apiVersion: v1
kind: Service
metadata:
name: lb-service-httpd-nginx
spec:
type: LoadBalancer
selector:
app: web-app
ports:
- name: nginx-port
port: 8080
targetPort: 80
nodePort: 30080
- name: httpd-port
port: 9090
targetPort: 90
nodePort: 30090
これをデプロイしていきます。
デプロイ自体は問題無いのですが、service/lb-service-httpd-nginxのEXTERNAL-IPがpendingとなっており、ここにLoadBalancer用のIPが欲しいです。
その為にMetalLBを有効化していきます。
root@k8s-master:~/yaml# kubectl get all -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod/nginx-httpd-deployment-759b9b5f-vq8rp 2/2 Running 0 16m 10.1.235.209 k8s-master <none> <none>
pod/nginx-httpd-deployment-759b9b5f-rsp86 2/2 Running 0 16m 10.1.79.68 k8s-worker01 <none> <none>
pod/nginx-httpd-deployment-759b9b5f-h8k67 2/2 Running 0 16m 10.1.69.197 k8s-worker02 <none> <none>
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
service/kubernetes ClusterIP 10.152.183.1 <none> 443/TCP 24h <none>
service/lb-service-httpd-nginx LoadBalancer 10.152.183.197 <pending> 8080:30080/TCP,9090:30090/TCP 6s app=web-app
NAME READY UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR
deployment.apps/nginx-httpd-deployment 3/3 3 3 16m web-httpd,web-nginx shotaohtsuka/my-httpd-image,nginx app=web-app
NAME DESIRED CURRENT READY AGE CONTAINERS IMAGES SELECTOR
replicaset.apps/nginx-httpd-deployment-759b9b5f 3 3 3 16m web-httpd,web-nginx shotaohtsuka/my-httpd-image,nginx app=web-app,pod-template-hash=759b9b5f
MetalLBを有効化します。
Enter each IP address range delimited by comma (e.g. '10.64.140.43-10.64.140.49,192.168.0.105-192.168.0.111'): でIPアドレスを入力していますが、これはk8s nodeと同じネットワーク帯で使っていないIPを使用すればいいはずです。今回は第四オクテッドが35-39をLoadBlancerの為に確保しています。
root@k8s-master:~/yaml# microk8s status
microk8s is running
high-availability: no
datastore master nodes: 192.168.2.30:19001
datastore standby nodes: none
addons:
enabled:
dashboard # (core) The Kubernetes dashboard
dns # (core) CoreDNS
ha-cluster # (core) Configure high availability on the current node
helm # (core) Helm - the package manager for Kubernetes
helm3 # (core) Helm 3 - the package manager for Kubernetes
metrics-server # (core) K8s Metrics Server for API access to service metrics
disabled:
cert-manager # (core) Cloud native certificate management
community # (core) The community addons repository
gpu # (core) Automatic enablement of Nvidia CUDA
host-access # (core) Allow Pods connecting to Host services smoothly
hostpath-storage # (core) Storage class; allocates storage from host directory
ingress # (core) Ingress controller for external access
kube-ovn # (core) An advanced network fabric for Kubernetes
mayastor # (core) OpenEBS MayaStor
metallb # (core) Loadbalancer for your Kubernetes cluster
minio # (core) MinIO object storage
observability # (core) A lightweight observability stack for logs, traces and metrics
prometheus # (core) Prometheus operator for monitoring and logging
rbac # (core) Role-Based Access Control for authorisation
registry # (core) Private image registry exposed on localhost:32000
storage # (core) Alias to hostpath-storage add-on, deprecated
root@k8s-master:~/yaml# microk8s enable metallb
Infer repository core for addon metallb
Enabling MetalLB
Enter each IP address range delimited by comma (e.g. '10.64.140.43-10.64.140.49,192.168.0.105-192.168.0.111'): 192.168.2.35-192.168.2.39
Applying Metallb manifest
customresourcedefinition.apiextensions.k8s.io/addresspools.metallb.io created
customresourcedefinition.apiextensions.k8s.io/bfdprofiles.metallb.io created
customresourcedefinition.apiextensions.k8s.io/bgpadvertisements.metallb.io created
customresourcedefinition.apiextensions.k8s.io/bgppeers.metallb.io created
customresourcedefinition.apiextensions.k8s.io/communities.metallb.io created
customresourcedefinition.apiextensions.k8s.io/ipaddresspools.metallb.io created
customresourcedefinition.apiextensions.k8s.io/l2advertisements.metallb.io created
namespace/metallb-system created
serviceaccount/controller created
serviceaccount/speaker created
clusterrole.rbac.authorization.k8s.io/metallb-system:controller unchanged
clusterrole.rbac.authorization.k8s.io/metallb-system:speaker unchanged
role.rbac.authorization.k8s.io/controller created
role.rbac.authorization.k8s.io/pod-lister created
clusterrolebinding.rbac.authorization.k8s.io/metallb-system:controller unchanged
clusterrolebinding.rbac.authorization.k8s.io/metallb-system:speaker unchanged
rolebinding.rbac.authorization.k8s.io/controller created
secret/webhook-server-cert created
service/webhook-service created
rolebinding.rbac.authorization.k8s.io/pod-lister created
daemonset.apps/speaker created
deployment.apps/controller created
validatingwebhookconfiguration.admissionregistration.k8s.io/validating-webhook-configuration configured
Waiting for Metallb controller to be ready.
deployment.apps/controller condition met
ipaddresspool.metallb.io/default-addresspool created
l2advertisement.metallb.io/default-advertise-all-pools created
MetalLB is enabled
root@k8s-master:~/yaml# microk8s status
microk8s is running
high-availability: no
datastore master nodes: 192.168.2.30:19001
datastore standby nodes: none
addons:
enabled:
dashboard # (core) The Kubernetes dashboard
dns # (core) CoreDNS
ha-cluster # (core) Configure high availability on the current node
helm # (core) Helm - the package manager for Kubernetes
helm3 # (core) Helm 3 - the package manager for Kubernetes
metallb # (core) Loadbalancer for your Kubernetes cluster
metrics-server # (core) K8s Metrics Server for API access to service metrics
disabled:
cert-manager # (core) Cloud native certificate management
community # (core) The community addons repository
gpu # (core) Automatic enablement of Nvidia CUDA
host-access # (core) Allow Pods connecting to Host services smoothly
hostpath-storage # (core) Storage class; allocates storage from host directory
ingress # (core) Ingress controller for external access
kube-ovn # (core) An advanced network fabric for Kubernetes
mayastor # (core) OpenEBS MayaStor
minio # (core) MinIO object storage
observability # (core) A lightweight observability stack for logs, traces and metrics
prometheus # (core) Prometheus operator for monitoring and logging
rbac # (core) Role-Based Access Control for authorisation
registry # (core) Private image registry exposed on localhost:32000
storage # (core) Alias to hostpath-storage add-on, deprecated
再度EXTERNAL-IPを確認します。
192.168.2.35が割り当てられていることが確認出来ますね。
root@k8s-master:~/yaml# kubectl get service -o wide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
kubernetes ClusterIP 10.152.183.1 <none> 443/TCP 24h <none>
lb-service-httpd-nginx LoadBalancer 10.152.183.197 192.168.2.35 8080:30080/TCP,9090:30090/TCP 5m40s app=web-app
通信フロー迄イメージに落とし込むと以下となります。
青矢印がnginxコンテナとの通信経路、赤矢印がapache2コンテナとの通信経路を示します。
podに接続して中身を確認したい時はLoadBalancerのIPアドレス192.168.2.35とポート番号を指定してアクセスする形になります。
実際にWebブラウザでpod内のコンテナにアクセスする
以下が結果です。
http://192.168.2.35:8080
独り言:【推測】なぜLoadBalancerが必要なのか。NodePortじゃダメなのか?
なぜLoadBalancerが必要なのか?
LBを使わないで本環境を構築する、つまりNodePortまでで実装しようとすると、手前にnginx等で構築したプロキシ等を実装する必要がありそう。。。
ただ、この場合、例えばworker01がnodeごと死んでいた場合、nginxとworker01間でハートビート等で生存を確認していないと死んでいるノードに対してまで負荷分散を行い、データを取得出来ない可能性がある?
生存確認などの実装なども考慮しないといけなくなる気がするので、結構大変そう。LoadBalancer serviceを使えばそのあたりを良い感じにしてくれるんだろうな。。。