この記事の内容
k8sクラスタのServiceリソースについて、各タイプ(ClusterIP、NodePort、LoadBalancer)でリソースを作ったとき、どこからどのようにアクセスできるか、まとめます。
環境
この記事で作った環境を使います。
- AlmaLinux 9.5の3VMで、Masterノードx1、Workerノードx2でk8sクラスタを構成。
1. httpdを3つ動かすDeployment作成
検証用に、以下を用意します。
- 検証用のNamespace。名前はservice-testとします。
- (nginxではなく)httpdのPodを3つ動かすDeployment。
- そのhttpdのPodでは、httpリクエストに対し、ノードのIP、PodのIP、ノード名、Pod名を返す。
(1) 作業場所の確保
検証通して、Masterノードの~/service-test/で、ファイル作成などを行います。
(2) Namespaceの作成
以下のマニフェストファイルを作成します。
apiVersion: v1
kind: Namespace
metadata:
name: service-test
適用します。これで、「service-test」のNamespaceができます。
kubectl apply -f ./service-test-namespace.yaml
(3) 自Podの情報を応答するhttpdを3つ動かすDeployment作成
以下のマニフェストファイルを作成します。
apiVersion: apps/v1
kind: Deployment
metadata:
name: httpd-deployment
namespace: service-test
spec:
replicas: 3
selector:
matchLabels:
app: httpd
template:
metadata:
labels:
app: httpd
spec:
containers:
- name: httpd
image: httpd:latest
ports:
- containerPort: 80
volumeMounts:
- name: html-volume
mountPath: /usr/local/apache2/htdocs
volumes:
- name: html-volume
emptyDir: {}
initContainers:
- name: setup-html
image: busybox
command: ['sh', '-c', 'echo "<html><body>Node IP: $NODE_IP, Pod IP: $POD_IP, Node Hostname: $NODE_NAME, Pod Name: $POD_NAME</body></html>" > /mnt/index.html']
env:
- name: NODE_IP
valueFrom:
fieldRef:
fieldPath: status.hostIP
- name: POD_IP
valueFrom:
fieldRef:
fieldPath: status.podIP
- name: NODE_NAME
valueFrom:
fieldRef:
fieldPath: spec.nodeName
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
volumeMounts:
- name: html-volume
mountPath: /mnt```
適用します。
kubectl apply -f ./httpd-deployment.yaml
この仕組みは、こうです。
- Podを起動すると、まず、index.htmlを作成するためのコンテナが起動してコマンドを実行して終了し、続けて、httpdのコンテナが80番ポートで待ち受け開始する。
- index.htmlを作るコンテナ(名前:setup-html)は、busyboxのコンテナイメージから起動され、shコマンドを1つ実行(自Podの動作情報を示す文字列のindex.htmlを/mnt/に出力)し、終了する。
- Pod内の2つのコンテナの間で、index.htmlファイルは、ボリューム(空のディレクトリ、名前:html-volume)を使って受け渡しする。このボリュームは、setup-htmlコンテナが起動するときは/mnt/にマウントされ、httpdコンテナが起動するときは/usr/local/apache2/htdocs/にマウントされる。
- httpdコンテナは、/usr/local/apache2/htdocs/index.htmlの内容を使って、httpリクエストに応答する。
(4) Podの動作確認
作成されたものを確認します。
[root@master1 service-test]# kubectl get all -n service-test
NAME READY STATUS RESTARTS AGE
pod/httpd-deployment-6f7bcfdb54-kgzpg 1/1 Running 0 10m
pod/httpd-deployment-6f7bcfdb54-p8h7v 1/1 Running 0 10m
pod/httpd-deployment-6f7bcfdb54-zn54n 1/1 Running 0 10m
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/httpd-deployment 3/3 3 3 10m
NAME DESIRED CURRENT READY AGE
replicaset.apps/httpd-deployment-6f7bcfdb54 3 3 3 10m
[root@master1 service-test]#
[root@master1 service-test]# kubectl get all -n service-test -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod/httpd-deployment-6f7bcfdb54-kgzpg 1/1 Running 0 10m 10.244.3.7 worker2.internal <none> <none>
pod/httpd-deployment-6f7bcfdb54-p8h7v 1/1 Running 0 10m 10.244.1.13 worker1.internal <none> <none>
pod/httpd-deployment-6f7bcfdb54-zn54n 1/1 Running 0 10m 10.244.1.12 worker1.internal <none> <none>
NAME READY UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR
deployment.apps/httpd-deployment 3/3 3 3 10m httpd httpd:latest app=httpd
NAME DESIRED CURRENT READY AGE CONTAINERS IMAGES SELECTOR
replicaset.apps/httpd-deployment-6f7bcfdb54 3 3 3 10m httpd httpd:latest app=httpd,pod-template-hash=6f7bcfdb54
[root@master1 service-test]#
Podが3つあり、2つはworker1で、1つはworker2で動いています。つまり、こうです。
試しに、master1から、各Podにアクセスしてみると、以下となります。
[root@master1 service-test]# curl http://10.244.1.12
<html><body>Node IP: 192.168.11.132, Pod IP: 10.244.1.12, Node Hostname: worker1.internal, Pod Name: httpd-deployment-6f7bcfdb54-zn54n</body></html>
[root@master1 service-test]#
[root@master1 service-test]# curl http://10.244.1.13
<html><body>Node IP: 192.168.11.132, Pod IP: 10.244.1.13, Node Hostname: worker1.internal, Pod Name: httpd-deployment-6f7bcfdb54-p8h7v</body></html>
[root@master1 service-test]#
[root@master1 service-test]# curl http://10.244.3.7
<html><body>Node IP: 192.168.11.133, Pod IP: 10.244.3.7, Node Hostname: worker2.internal, Pod Name: httpd-deployment-6f7bcfdb54-kgzpg</body></html>
[root@master1 service-test]#
自Podが起動しているノードのIPアドレス、自PodのIPアドレスなどが、表示されています。
2. type:ClusterIPのService作成
最もシンプルにServiceリソースを作成する場合、type: ClusterIPとします。以下のマニフェストファイルを作成します。
apiVersion: v1
kind: Service
metadata:
name: httpd-service
namespace: service-test
spec:
selector:
app: httpd
ports:
- protocol: TCP
port: 8080
targetPort: 80
type: ClusterIP
適用します。これで、ClusterIPタイプの「httpd-service」のServiceリソースができます。
kubectl apply -f ./clusterip-service.yaml
作成されたものを確認します。
[root@master1 service-test]# kubectl get all -n service-test
NAME READY STATUS RESTARTS AGE
pod/httpd-deployment-6f7bcfdb54-kgzpg 1/1 Running 0 18m
pod/httpd-deployment-6f7bcfdb54-p8h7v 1/1 Running 0 18m
pod/httpd-deployment-6f7bcfdb54-zn54n 1/1 Running 0 18m
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/httpd-service ClusterIP 10.98.56.15 <none> 8080/TCP 5s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/httpd-deployment 3/3 3 3 18m
NAME DESIRED CURRENT READY AGE
replicaset.apps/httpd-deployment-6f7bcfdb54 3 3 3 18m
[root@master1 service-test]#
service/httpd-serviceが作られ、CLUSTER-IPがついていることがわかります。図にするとこうです。
k8sクラスタ内からアクセスできます。master1から6回curlすると、以下のように、毎回、3つのPodのどれかに、httpリクエストが振り分けられていることがわかります。
[root@master1 service-test]# curl http://10.98.56.15:8080
<html><body>Node IP: 192.168.11.132, Pod IP: 10.244.1.12, Node Hostname: worker1.internal, Pod Name: httpd-deployment-6f7bcfdb54-zn54n</body></html>
[root@master1 service-test]#
[root@master1 service-test]# curl http://10.98.56.15:8080
<html><body>Node IP: 192.168.11.133, Pod IP: 10.244.3.7, Node Hostname: worker2.internal, Pod Name: httpd-deployment-6f7bcfdb54-kgzpg</body></html>
[root@master1 service-test]#
[root@master1 service-test]# curl http://10.98.56.15:8080
<html><body>Node IP: 192.168.11.133, Pod IP: 10.244.3.7, Node Hostname: worker2.internal, Pod Name: httpd-deployment-6f7bcfdb54-kgzpg</body></html>
[root@master1 service-test]#
[root@master1 service-test]# curl http://10.98.56.15:8080
<html><body>Node IP: 192.168.11.133, Pod IP: 10.244.3.7, Node Hostname: worker2.internal, Pod Name: httpd-deployment-6f7bcfdb54-kgzpg</body></html>
[root@master1 service-test]#
[root@master1 service-test]# curl http://10.98.56.15:8080
<html><body>Node IP: 192.168.11.133, Pod IP: 10.244.3.7, Node Hostname: worker2.internal, Pod Name: httpd-deployment-6f7bcfdb54-kgzpg</body></html>
[root@master1 service-test]#
[root@master1 service-test]# curl http://10.98.56.15:8080
<html><body>Node IP: 192.168.11.132, Pod IP: 10.244.1.13, Node Hostname: worker1.internal, Pod Name: httpd-deployment-6f7bcfdb54-p8h7v</body></html>
[root@master1 service-test]#
k8sクラスタ内からアクセスする分には、これで問題ありません。Serviceを作ることで、個々のPodのIPアドレス(再起動で変化する)をターゲットにせずとも、ServiceのIPアドレスをターゲットに、アクセスすることができます。
ただし、k8sクラスタの外、例えば、rhel8hostやal9hostからは、アクセスできません。
3. type:NodePortのService作成
k8sクラスタの外から、Serviceにアクセスするための、比較的簡単な方法として、type: NodePortのServiceを使うことができます。
一度、ClusterIPのServiceを消します。
kubectl delete -f clusterip-service.yaml
これとは別の名前で、以下のマニフェストファイルを作成します。
apiVersion: v1
kind: Service
metadata:
name: httpd-service
namespace: service-test
spec:
selector:
app: httpd
ports:
- protocol: TCP
port: 8080
targetPort: 80
nodePort: 30080
type: NodePort
適用します。これで、NodePortタイプの「httpd-service」のServiceリソースができます。
kubectl apply -f ./nodeport-service.yaml
作成されたものを確認します。
[root@master1 service-test]# kubectl get all -n service-test
NAME READY STATUS RESTARTS AGE
pod/httpd-deployment-6f7bcfdb54-kgzpg 1/1 Running 0 61m
pod/httpd-deployment-6f7bcfdb54-p8h7v 1/1 Running 0 61m
pod/httpd-deployment-6f7bcfdb54-zn54n 1/1 Running 0 61m
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/httpd-service NodePort 10.106.205.22 <none> 8080:30080/TCP 5s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/httpd-deployment 3/3 3 3 61m
NAME DESIRED CURRENT READY AGE
replicaset.apps/httpd-deployment-6f7bcfdb54 3 3 3 61m
[root@master1 service-test]#
service/httpd-serviceが作られ、TYPEがNodePortとなり、PORT(S)が、「8080:30080/TCP」と表示が変わったことがわかります。図にするとこうです。
この場合、各ノードの30080番へのアクセスが、httpd-serviceのアクセスとなります。
[root@master1 service-test]# curl http://192.168.11.131:30080
<html><body>Node IP: 192.168.11.132, Pod IP: 10.244.1.12, Node Hostname: worker1.internal, Pod Name: httpd-deployment-6f7bcfdb54-zn54n</body></html>
[root@master1 service-test]#
[root@master1 service-test]# curl http://192.168.11.131:30080
<html><body>Node IP: 192.168.11.133, Pod IP: 10.244.3.7, Node Hostname: worker2.internal, Pod Name: httpd-deployment-6f7bcfdb54-kgzpg</body></html>
[root@master1 service-test]#
[root@master1 service-test]# curl http://192.168.11.131:30080
<html><body>Node IP: 192.168.11.133, Pod IP: 10.244.3.7, Node Hostname: worker2.internal, Pod Name: httpd-deployment-6f7bcfdb54-kgzpg</body></html>
[root@master1 service-test]#
[root@master1 service-test]# curl http://192.168.11.131:30080
<html><body>Node IP: 192.168.11.132, Pod IP: 10.244.1.13, Node Hostname: worker1.internal, Pod Name: httpd-deployment-6f7bcfdb54-p8h7v</body></html>
[root@master1 service-test]#
[root@master1 service-test]# curl http://192.168.11.132:30080
<html><body>Node IP: 192.168.11.132, Pod IP: 10.244.1.13, Node Hostname: worker1.internal, Pod Name: httpd-deployment-6f7bcfdb54-p8h7v</body></html>
[root@master1 service-test]#
[root@master1 service-test]# curl http://192.168.11.132:30080
<html><body>Node IP: 192.168.11.132, Pod IP: 10.244.1.12, Node Hostname: worker1.internal, Pod Name: httpd-deployment-6f7bcfdb54-zn54n</body></html>
[root@master1 service-test]#
[root@master1 service-test]# curl http://192.168.11.132:30080
<html><body>Node IP: 192.168.11.132, Pod IP: 10.244.1.12, Node Hostname: worker1.internal, Pod Name: httpd-deployment-6f7bcfdb54-zn54n</body></html>
[root@master1 service-test]#
[root@master1 service-test]# curl http://192.168.11.132:30080
<html><body>Node IP: 192.168.11.133, Pod IP: 10.244.3.7, Node Hostname: worker2.internal, Pod Name: httpd-deployment-6f7bcfdb54-kgzpg</body></html>
[root@master1 service-test]#
[root@master1 service-test]# curl http://192.168.11.133:30080
<html><body>Node IP: 192.168.11.132, Pod IP: 10.244.1.12, Node Hostname: worker1.internal, Pod Name: httpd-deployment-6f7bcfdb54-zn54n</body></html>
[root@master1 service-test]#
[root@master1 service-test]# curl http://192.168.11.133:30080
<html><body>Node IP: 192.168.11.132, Pod IP: 10.244.1.12, Node Hostname: worker1.internal, Pod Name: httpd-deployment-6f7bcfdb54-zn54n</body></html>
[root@master1 service-test]#
[root@master1 service-test]# curl http://192.168.11.133:30080
<html><body>Node IP: 192.168.11.132, Pod IP: 10.244.1.13, Node Hostname: worker1.internal, Pod Name: httpd-deployment-6f7bcfdb54-p8h7v</body></html>
[root@master1 service-test]#
[root@master1 service-test]# curl http://192.168.11.133:30080
<html><body>Node IP: 192.168.11.133, Pod IP: 10.244.3.7, Node Hostname: worker2.internal, Pod Name: httpd-deployment-6f7bcfdb54-kgzpg</body></html>
[root@master1 service-test]#
また、ClusterIPが無くなったわけではないので、ClusterIPを使ってアクセスすることもできます。
[root@master1 service-test]# curl http://10.106.205.22:8080
<html><body>Node IP: 192.168.11.132, Pod IP: 10.244.1.13, Node Hostname: worker1.internal, Pod Name: httpd-deployment-6f7bcfdb54-p8h7v</body></html>
[root@master1 service-test]#
[root@master1 service-test]# curl http://10.106.205.22:8080
<html><body>Node IP: 192.168.11.132, Pod IP: 10.244.1.13, Node Hostname: worker1.internal, Pod Name: httpd-deployment-6f7bcfdb54-p8h7v</body></html>
[root@master1 service-test]#
[root@master1 service-test]# curl http://10.106.205.22:8080
<html><body>Node IP: 192.168.11.133, Pod IP: 10.244.3.7, Node Hostname: worker2.internal, Pod Name: httpd-deployment-6f7bcfdb54-kgzpg</body></html>
[root@master1 service-test]#
[root@master1 service-test]# curl http://10.106.205.22:8080
<html><body>Node IP: 192.168.11.132, Pod IP: 10.244.1.12, Node Hostname: worker1.internal, Pod Name: httpd-deployment-6f7bcfdb54-zn54n</body></html>
[root@master1 service-test]#
これで、k8sクラスタ外からも、Serviceを介して、各Podにアクセスできるようになります。ただし、宛先が特定のノードのIPアドレスなので、例えば、ノードがメンテナンス中などで停止していると、その間、アクセスができません。
4. LoadBalancer(ロードバランサなし)
特定のノードを宛先とせず、k8sクラスタ全体が持つリソースを宛先に、k8sクラスタ外から、Serviceにアクセスできるようにする方法が、type: LoadBalancerを使う方法です。ただし、この方法は、パブリッククラウドに作られたk8sクラスタが、クラウドプロバイダが提供するロードバランサを利用して動作するものなので、単純に、マニフェストファイルにtype: LoadBalancerと記述した場合、k8sクラスタ外に見せるIPアドレスは、ずっと、Pending表示のままとなり、利用できません。一旦、その、Pending表示になるところまで、設定してみます。
一度、既存のServiceを消します。
kubectl delete -f nodeport-service.yaml
これとは別の名前で、以下のマニフェストファイルを作成します。
apiVersion: v1
kind: Service
metadata:
name: httpd-service
namespace: service-test
spec:
selector:
app: httpd
ports:
- protocol: TCP
port: 8080
targetPort: 80
type: LoadBalancer
適用します。これで、LoadBalancerタイプの「httpd-service」のServiceリソースができます。
kubectl apply -f ./loadbalancer-service.yaml
作成されたものを確認します。
[root@master1 service-test]# kubectl get all -n service-test
NAME READY STATUS RESTARTS AGE
pod/httpd-deployment-6f7bcfdb54-kgzpg 1/1 Running 0 69m
pod/httpd-deployment-6f7bcfdb54-p8h7v 1/1 Running 0 69m
pod/httpd-deployment-6f7bcfdb54-zn54n 1/1 Running 0 69m
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/httpd-service LoadBalancer 10.111.8.127 <pending> 8080:31073/TCP 12s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/httpd-deployment 3/3 3 3 69m
NAME DESIRED CURRENT READY AGE
replicaset.apps/httpd-deployment-6f7bcfdb54 3 3 3 69m
[root@master1 service-test]#
service/httpd-serviceが作られ、TYPEがLoadBalancerとなり、PORT(S)が、「8080:31073/TCP」と表示が変わったことがわかります。図にするとこうです。
type: LoadBalancerの場合、type: ClusterIPのように、ServiceのClusterIPを宛先に、:8080でアクセスすることはできます。また、type: NodePortのように、各ノードの待ち受けポート(ここでは:31073)でアクセスすることもできます。
オンプレミス環境でtype: LoadBalancerを使いたいならば、MetalLBを利用し、クラウドプロバイダが提供するロードバランサと同様のものを作って使うことができます。
5. LoadBalancer(MetalLBによるロードバランサ作成可)
MetalLBのインストールは、flannelのように、コミュニティが提供するマニフェストファイルを取得し、kubectl apply -fで適用すると、MetalLBのコンポーネントが起動されます。その上で、初期設定として、どのIPアドレスを、ロードバランサに割り当てるか、といった設定を記述したマニフェストファイルを作る必要があります。
(1) マニフェストファイルの取得
上記インストール手順を参考に実施します。現時点の最新は、v0.14.9でした。
curl -O https://raw.githubusercontent.com/metallb/metallb/v0.14.9/config/manifests/metallb-native.yaml
適用します。
kubectl apply -f ./metallb-native.yaml
(2) MetalLBの設定ファイルの作成と適用
以下の2ファイルを作成します。
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: first-pool
namespace: metallb-system
spec:
addresses:
- 192.168.11.136-192.168.11.139
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
name: default-advertisement
namespace: metallb-system
spec:
ipAddressPools:
- first-pool
ipaddresspool.yamlの、spec.addressesには、ロードバランサが使ってよいIPアドレス範囲(IPアドレスプール)を指定します。この環境では、192.168.11.136~.139が、他に使っておらず、ロードバランサとして使うことにしています。
適用します。
kubectl apply -f ./ipaddresspool.yaml
kubectl apply -f ./l2advertisement.yaml
これで、httpd-serviceは、pendingが解消され、IPアドレスプールから1つ、割り当てられます。
[root@master1 service-test]# kubectl get all -n service-test
NAME READY STATUS RESTARTS AGE
pod/httpd-deployment-6f7bcfdb54-kgzpg 1/1 Running 0 84m
pod/httpd-deployment-6f7bcfdb54-p8h7v 1/1 Running 0 84m
pod/httpd-deployment-6f7bcfdb54-zn54n 1/1 Running 0 84m
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/httpd-service LoadBalancer 10.111.8.127 192.168.11.136 8080:31073/TCP 15m
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/httpd-deployment 3/3 3 3 84m
NAME DESIRED CURRENT READY AGE
replicaset.apps/httpd-deployment-6f7bcfdb54 3 3 3 84m
[root@master1 service-test]#
図にすると、こういう状態です。
k8sクラスタ外のrhel8host(192.168.11.201)は、このロードバランサのIPアドレスを宛先に、k8sクラスタ内のServiceに、アクセスすることができるようになります。
[root@rhel8host ~]# curl http://192.168.11.136:8080
<html><body>Node IP: 192.168.11.132, Pod IP: 10.244.1.12, Node Hostname: worker1.internal, Pod Name: httpd-deployment-6f7bcfdb54-zn54n</body></html>
[root@rhel8host ~]#
[root@rhel8host ~]# curl http://192.168.11.136:8080
<html><body>Node IP: 192.168.11.132, Pod IP: 10.244.1.13, Node Hostname: worker1.internal, Pod Name: httpd-deployment-6f7bcfdb54-p8h7v</body></html>
[root@rhel8host ~]#
[root@rhel8host ~]# curl http://192.168.11.136:8080
<html><body>Node IP: 192.168.11.132, Pod IP: 10.244.1.12, Node Hostname: worker1.internal, Pod Name: httpd-deployment-6f7bcfdb54-zn54n</body></html>
[root@rhel8host ~]#
[root@rhel8host ~]# curl http://192.168.11.136:8080
<html><body>Node IP: 192.168.11.133, Pod IP: 10.244.3.7, Node Hostname: worker2.internal, Pod Name: httpd-deployment-6f7bcfdb54-kgzpg</body></html>
[root@rhel8host ~]#
もちろん、k8sクラスタ内のmaster1からも、このロードバランサのIPアドレスでアクセスできます。
[root@master1 service-test]# curl http://192.168.11.136:8080
<html><body>Node IP: 192.168.11.133, Pod IP: 10.244.3.7, Node Hostname: worker2.internal, Pod Name: httpd-deployment-6f7bcfdb54-kgzpg</body></html>
[root@master1 service-test]#
[root@master1 service-test]# curl http://192.168.11.136:8080
<html><body>Node IP: 192.168.11.132, Pod IP: 10.244.1.13, Node Hostname: worker1.internal, Pod Name: httpd-deployment-6f7bcfdb54-p8h7v</body></html>
[root@master1 service-test]#
[root@master1 service-test]# curl http://192.168.11.136:8080
<html><body>Node IP: 192.168.11.132, Pod IP: 10.244.1.12, Node Hostname: worker1.internal, Pod Name: httpd-deployment-6f7bcfdb54-zn54n</body></html>
[root@master1 service-test]#
[root@master1 service-test]# curl http://192.168.11.136:8080
<html><body>Node IP: 192.168.11.132, Pod IP: 10.244.1.13, Node Hostname: worker1.internal, Pod Name: httpd-deployment-6f7bcfdb54-p8h7v</body></html>
[root@master1 service-test]#
まとめ
k8sクラスタのServiceリソースについて、Serviceなし、type: ClusterIP、type: NodePort、type: LoadBalancerの順に、どこからどのようにアクセスできるか、まとめました。
図中のアイコンについて
以下から使わせていただきました。ありがとうございます。