ホーム自己紹介ブログ
NO.13
DATE2019. 05. 15

一足遅れて Kubernetes を学び始める - 09. discovery&LB その2 -

ストーリー

  1. 一足遅れて Kubernetes を学び始める - 01. 環境選択編 -
  2. 一足遅れて Kubernetes を学び始める - 02. Docker For Mac -
  3. 一足遅れて Kubernetes を学び始める - 03. Raspberry Pi -
  4. 一足遅れて Kubernetes を学び始める - 04. kubectl -
  5. 一足遅れて Kubernetes を学び始める - 05. workloads その 1 -
  6. 一足遅れて Kubernetes を学び始める - 06. workloads その 2 -
  7. 一足遅れて Kubernetes を学び始める - 07. workloads その 3 -
  8. 一足遅れて Kubernetes を学び始める - 08. discovery&LB その 1 -
  9. 一足遅れて Kubernetes を学び始める - 09. discovery&LB その 2 -
  10. 一足遅れて Kubernetes を学び始める - 10. config&storage その 1 -
  11. 一足遅れて Kubernetes を学び始める - 11. config&storage その 2 -
  12. 一足遅れて Kubernetes を学び始める - 12. リソース制限 -
  13. 一足遅れて Kubernetes を学び始める - 13. ヘルスチェックとコンテナライフサイクル -
  14. 一足遅れて Kubernetes を学び始める - 14. スケジューリング -
  15. 一足遅れて Kubernetes を学び始める - 15. セキュリティ -
  16. 一足遅れて Kubernetes を学び始める - 16. コンポーネント -

前回

一足遅れて Kubernetes を学び始める - 08. discovery&LB その 1 -で Service についての概要を学びました。今回は下記を一気に学びます。

  • ExternalIP
  • NodePort
  • LocadBalancer
  • Headless
  • ExternalName
  • None-Selector
  • Ingress

※ ClusterIP を飛ばしたのは、前回使った内容で十分だと思ったため。

ExternalIP

こちらは、外向けの IP アドレスを割り振ります。

## sample-externalip.yaml
apiVersion: v1
kind: Service
metadata:
  name: sample-externalip
spec:
  type: ClusterIP
  externalIPs:
    - 192.168.3.33
  ports:
    - name: "http-port"
      protocol: "TCP"
      port: 8080
      targetPort: 80
  selector:
    app: sample-app

私の Node 情報では、下記の状態です。

hostip
raspi001(master)192.168.3.32
raspi002(worker)192.168.3.33
raspi003(worker)192.168.3.34
nfspi(NFS)192.168.3.35

ここで、spec. externalIPs に、公開したい IP アドレスを上記 Node の IP アドレスより設定します。 今回は、1つだけ(raspi002:193.168.3.33)にしました。

## sample-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: sample-deployment
spec:
  replicas: 3
  selector:
    matchLabels:
      app: sample-app
  template:
    metadata:
      labels:
        app: sample-app
    spec:
      containers:
        - name: nginx-container
          image: nginx:1.12
          ports:
            - containerPort: 80
        - name: redis-container
          image: redis:3.2

前回同様のファイルを用意します。

pi@raspi001:~/tmp $ k apply -f sample-deployment.yaml -f sample-externalip.yaml
pi@raspi001:~/tmp $ k get pod -o=wide
NAME                                      READY   STATUS    RESTARTS   AGE   IP            NODE       NOMINATED NODE   READINESS GATES
sample-deployment-9dc487867-7n2sz         2/2     Running   0          16m   10.244.1.73   raspi002   <none>           <none>
sample-deployment-9dc487867-nnnqm         2/2     Running   0          16m   10.244.1.74   raspi002   <none>           <none>
sample-deployment-9dc487867-qfdhw         2/2     Running   0          16m   10.244.2.68   raspi003   <none>           <none>
pi@raspi001:~/tmp $ k get service
NAME                TYPE        CLUSTER-IP       EXTERNAL-IP    PORT(S)    AGE
sample-externalip   ClusterIP   10.104.170.220   192.168.3.33   8080/TCP   15m

externalIP が設定されました。

pi@raspi001:~/tmp $ for PODNAME in `k get pods -l app=sample-app -o jsonpath='{.items[*].metadata.name}'`; do k exec -it ${PODNAME} -- cp /etc/hostname /usr/share/nginx/html/index.html; done

どこの pod かどうかわかりやすいくするため、index.html を書き換えます。 では、ブラウザからアクセスしてみます。

sample_deployment_1
sample_deployment_1
sample_deployment_2
sample_deployment_2

raspi002 を公開したので、その Node に存在する Pod がランダムに出力されている、つまりロードバランサが動作していることがわかります。

NodePort

ExternalIP のような特定 Node を公開するのと違って、NodePort は、 全て の Node を公開します。

試してみます。

## sample-nodeport.yaml
apiVersion: v1
kind: Service
metadata:
  name: sample-nodeport
spec:
  type: NodePort
  ports:
    - name: "http-port"
      protocol: "TCP"
      port: 8080
      targetPort: 80
      nodePort: 30080
  selector:
    app: sample-app
pi@raspi001:~/tmp $ k delete -f sample-externalip.yaml #しなくても良い
pi@raspi001:~/tmp $ k apply -f sample-nodeport.yaml
pi@raspi001:~/tmp $ k get service
NAME              TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
sample-nodeport   NodePort    10.96.173.243   <none>        8080:30080/TCP   66s

内向けには、10.96.173.243:8080 でアクセスでき、外向けには、各 Node の IP アドレス:30080 にアクセスします。 どちらも正常にアクセスできています。もちろん、アクセス先の Pod は、ロードバランシングされます。ロードバランシングさせるのが嫌な場合にも対応できます。アクセスされた Node の先は、その Node 内にある Pod のみにアクセスさせる「spec.externalTrafficPolicy:Local」に設定すれば大丈夫です。 注意点として、nodePort は、30000~32767 の範囲と決まっています。

LoadBalancer

ExternalIP や NodePort の場合、ロードバランシングするのはクラスタ内の Node になります。そのため、アクセスが集中することで、Node 単一障害が発生しやすいそうです。そこで、LoadBalancer を使うことで、 クラスタ外 にロードバランサを作成します。

ただ、クラスタ外にロードバランサを作成する際は、プラットフォームによって対応しているか確認が必要です。私のような raspberryPi 環境では、もちろんそういった機能がないため、準備する必要があります。

master(raspi001)に移動

pi@raspi001:~/tmp $ k apply -f https://raw.githubusercontent.com/google/metallb/v0.7.3/manifests/metallb.yaml

metallb と呼ばれるロードバランサを適用します。

MetalLB :: MetalLB, bare metal load-balancer for Kubernetes
MetalLB is a load-balancer implementation for bare metal Kubernetes clusters, using standard routing protocols. Note Despite the beta status of the project / API, MetalLB is known to be stable and reliable. The project maturity page explains what that implies. Why? Kubernetes does not offer an implementation of network load balancers (Services of type LoadBalancer) for bare-metal clusters. The implementations of network load balancers that Kubernetes does ship with are all glue code that calls out to various IaaS platforms (GCP, AWS, Azure…).
metallb.universe.tf

MetalLB is a load-balancer implementation for bare metal Kubernetes clusters, using standard routing protocols.

## l2-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  namespace: metallb-system
  name: config
data:
  config: |
    address-pools:
    - name: default
      protocol: layer2
      addresses:
      - 192.168.3.100-192.168.3.200
pi@raspi001:~/tmp $ k apply -f l2-config.yaml

これで、raspberryPi 環境でも loadBalancer が使えます。さっそくつかってみましょう。

## sample-lb.yaml
apiVersion: v1
kind: Service
metadata:
  name: sample-lb
spec:
  type: LoadBalancer
  loadBalancerIP: 192.168.3.100
  ports:
    - name: "http-port"
      protocol: "TCP"
      port: 8080
      targetPort: 80
      nodePort: 30082
  selector:
    app: sample-app
  loadBalancerSourceRanges:
    - 192.168.3.0/8
pi@raspi001:~/tmp $ k apply -f sample-deployment.yaml
pi@raspi001:~/tmp $ k apply -f sample-lb.yaml
pi@raspi001:~/tmp $ k get services
NAME         TYPE           CLUSTER-IP      EXTERNAL-IP     PORT(S)          AGE
kubernetes   ClusterIP      10.96.0.1       <none>          443/TCP          16d
sample-lb    LoadBalancer   10.106.253.65   192.168.3.100   8080:30082/TCP   8m4s

お、192.168.3.100:8080 にアクセス可能みたいです。

iMac に移動

~ $ curl -s http://192.168.3.100:8080
<!DOCTYPE html>
...

OK

Headless

今までのロードバランスと違い、公開する IP アドレスは提供されません。 DNS ラウンドロビンによる転送先の Pod の IP アドレスを取得できます。 つまり、Headless のサービスへ問い合わせすると、spec.selector で登録した Pod の IP アドレスが手に入ります。 Pod の IP アドレスがほしいときには便利です。(Envoy とか?)

## sample-statefulset-headless.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: sample-statefulset-headless
spec:
  serviceName: sample-headless
  replicas: 3
  selector:
    matchLabels:
      app: sample-app
  template:
    metadata:
      labels:
        app: sample-app
    spec:
      containers:
        - name: nginx-container
          image: nginx:1.12
## sample-headless.yaml
apiVersion: v1
kind: Service
metadata:
  name: sample-headless
spec:
  type: ClusterIP
  clusterIP: None
  ports:
    - name: "http-port"
      protocol: "TCP"
      port: 80
      targetPort: 80
  selector:
    app: sample-app

spec.type が ClusterIP であり、spec.clusterIP が None、そして、metadata.name が statefulset の spec.serviceName と同じことで、Headless Service と呼ぶそうです。

pi@raspi001:~/tmp $ k apply -f sample-statefulset-headless.yaml
pi@raspi001:~/tmp $ k run --image=centos:7 --restart=Never --rm -i testpod  -- dig sample-headless.default.svc.cluster.local
...
;; ANSWER SECTION:
sample-headless.default.svc.cluster.local. 5 IN A 10.244.1.75
sample-headless.default.svc.cluster.local. 5 IN A 10.244.2.72
sample-headless.default.svc.cluster.local. 5 IN A 10.244.2.73
sample-headless.default.svc.cluster.local. 5 IN A 10.244.1.78
sample-headless.default.svc.cluster.local. 5 IN A 10.244.1.76
sample-headless.default.svc.cluster.local. 5 IN A 10.244.2.70

たしかに、headless のサービスに問い合わせると、IP アドレスが返ってきました。

ExternalName

外部のドメイン宛の CNAME を返すサービスです。 例えば、Pod から外部のexample.comへアクセスする場合、下記のように設定します。

## sample-externalname.yaml
kind: Service
apiVersion: v1
metadata:
  name: sample-externalname
  namespace: default
spec:
  type: ExternalName
  externalName: example.com
pi@raspi001:~/tmp $ k apply -f sample-externalname.yaml
pi@raspi001:~/tmp $ k run --image=centos:7 --restart=Never --rm -i testpod  -- dig sample-externalname.default.svc.cluster.local
...
;; ANSWER SECTION:
sample-externalname.default.svc.cluster.local. 5 IN CNAME example.com.
example.com.  5 IN A 93.184.216.34

確かに、sample-externalname.default.svc.cluster.localと問い合わせすることで、外部のexample.comへの CNAME を取得できます。また、外部のサイトを切り替えたいときは、問い合わせ先は 変わらず に、sample-externalname.yaml の spec.externalName を変更するだけで済みます。これは切り替えが楽ですね。

None-Selector

外部のサービスに対してロードバランシングします。

## sample-none-selector.yaml
---
kind: Service
apiVersion: v1
metadata:
  name: sample-none-selector
spec:
  type: ClusterIP
  ports:
    - protocol: TCP
      port: 8080
      targetPort: 80
---
kind: Endpoints
apiVersion: v1
metadata:
  name: sample-none-selector
subsets:
  - addresses:
      - ip: 172.217.31.164
      - ip: 172.217.31.165
    ports:
      - protocol: TCP
        port: 80

172.217.31.164 と 172.217.31.165 は、どちらもwww.google.comを指します。

pi@raspi001:~/tmp $ k apply -f sample-none-selector.yaml
pi@raspi001:~/tmp $ k get service
NAME                   TYPE           CLUSTER-IP      EXTERNAL-IP     PORT(S)          AGE
sample-none-selector   ClusterIP      10.102.225.99   <none>          8080/TCP         88s
pi@raspi001:~/tmp $ k describe svc sample-none-selector
Name:              sample-none-selector
...
Type:              ClusterIP
IP:                10.102.225.99
Port:              <unset>  8080/TCP
TargetPort:        80/TCP
Endpoints:         172.217.31.164:80,172.217.31.165:80
...

ClusterIP なので、内部で公開されていますね。

pi@raspi001:~/tmp $ curl 10.102.225.99:8080
<HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8">
<TITLE>301 Moved</TITLE></HEAD><BODY>
<H1>301 Moved</H1>
The document has moved
<A HREF="http://www.google.com:8080/">here</A>.
</BODY></HTML>
...

少し結果が不自然だったのですが、確かに google.com へアクセスしました。 外部サービスへのロードバランシングも容易に実現できます。

※ 172.217.31.164へアクセスすると、リダイレクトがかかります。Status Code: 301

Ingress

今までのロードバランサは、l4 レイヤーのロードバランサです。(IP アドレスとポート番号による負荷分散) Ingress では、l7 レイヤーのロードバランサを提供します。(URL や HTTP ヘッダーで負荷分散が可能)

Ingress を置く場所は、クラスタ内、外の2つあります。 クラスタ外の場合は、使うプラットフォームによります。 クラスタ内の場合は、Nginx Ingress を使うことができます。

raspberryPi 環境では、Ingress-Nginx-Controller を使うことで、Ingress を使えるそうです。 NGINX Ingress Controller - Installation Guide を参考にして進めたのですが、arm64 環境では動きませんでした。

そこで、下記の yaml を発見し、試してみると動作します。ぜひ、お試しあれ。 hectcastro/mandatory.yaml

※ namespace を削除できない場合は、こちらを参考下さい。

お片付け

pi@raspi001:~/tmp $ k delete -f sample-externalip.yaml -f sample-deployment.yaml -f sample-nodeport.yaml -f sample-lb.yaml -f sample-statefulset-headless.yaml -f sample-headless.yaml -f sample-none-selector.yaml -f sample-externalname.yaml

最後に

Service について学びました。 様々な用途に応じて、エンドポイントを公開する手段を学びました。 手を動かして確認してみると、理解が深まりました。 本番で k8s を使った経験はありませんが、今後必要に迫られた際に、こちらの記事を思い返そうと思います。

次回はこちらです。

クラウドインフラ

-

シェアする

フォローする

次のページ

Go Conference 2019 Spring - 2019年5月18日 参加レポート

前のページ

Algolia Community Party in 京都 - 2019年5月10日 参加レポート

関連する記事

タグ「クラウドインフラ」の記事

Docker Image に 構造化テスト container-structure-test を試してみた

Dockerイメージ内の構造や設定が期待通りかどうかを検証する `container-structure-test` を知りました。container-structure-test GitHub リポジトリ。せっかくなので、試してみました。

2024年03月29日

テスト
クラウドインフラ
BigQueryだけで完結するモック可能なユニットテスト手法

BigQuery、皆さん使っていますか? 私は、業務でBigQueryを使ったデータ構築をしています。品質担保のため、BigQueryのSQLに対してテストをしたいと考えています。本記事では、BigQueryだけで完結し、かつ、Mockデータを差し替え可能なユニットテスト手法について、紹介します。

2021年11月26日

フロントエンド
クラウドインフラ
テスト
TikTokスクレイプ基盤をGCP上で構築してハマったこと

TikTokへスクレイプするバッチをGCP上で構築しました。GCP構築のシステム設計話と、その構築時に、ハマったことを共有します。

2021年08月28日

サービス
クラウドインフラ
成果物
クローリング
← ブログ一覧へ