ネットワーク

本セクションで学習すること #

  • Kubernetes の名前空間
  • Pod 間通信とCNI
  • Servie リソース
  • Ingress リソース
  • (補足)Kubernetes におけるLoadBalancer

名前空間 #

ネットワークの前に簡単にNamespace (名前空間) に関して説明します。名前空間はKubernetes におけるリソースを適切に分離する仕組みのことです。例えば、1つのKubernetes クラスタを複数で利用する際に、名前空間を分けない場合はアプリケーションの名前が被ってはいけませんが、名前空間を分けることで、このような重複を気にする必要がなくなります。他にも、名前空間の間の通信を制御したり、機能を制限したりと、Kubernetes におけるマルチテナントの提供形態の1 つと言い換えることもできます。

例えば、これまでのハンズオンの中でkubectl を使った操作の時には名前空間を意識していませんでしたが、実際はハンズオンのセッションごとに個別に割り当てられた名前空間の中で作業をしていました。

名前空間を指定しない場合:

kubectl get pod

名前空間を指定する場合:

kubectl get pod -n ${NS}
通常、何も設定しない場合のデフォルトの名前空間の名前は"default"となります。

このハンズオンの仕組みとして、ユーザーのセッションごとに個別に名前空間が割り当てられ、みなさんはその名前空間の中で作業しています。

kubectl get namespaces
NAME                   STATUS   AGE
default                Active   68d
eduk8s                 Active   68d
eduk8s-ingress         Active   68d
eduk8s-labs-ui         Active   68d
eduk8s-labs-w01        Active   68d
eduk8s-labs-w01-s283   Active   172m
eduk8s-labs-w01-s284   Active   109m
eduk8s-labs-w01-s285   Active   46m
...

ただし、他のユーザーに割り当てられた名前空間やKubernetes が内部で使用している名前空間へのアクセスは制限されています。名前空間ごとに適切にアクセスコントロールができていると言えますね。

kubectl get pod -n kube-system
Error from server (Forbidden): pods is forbidden: User "system:serviceaccount:eduk8s-labs-w01:eduk8s-labs-w01-s283" cannot list resource "pods" in API group "" in the namespace "kube-system"

Kubernetes のネットワークの基本 #

Kubernetes ではPod 単位でIP アドレスがアサインされます。各ポッドに割り当てられているIPアドレスは、次のように実行することで確認できます。

kubectl get pods -l app=blog -o wide
NAME                    READY   STATUS    RESTARTS   AGE   IP               NODE                                       NOMINATED NODE   READINESS GATES
blog-65c8484545-br9mf   1/1     Running   0          28m   192.168.12.67    ip-10-0-3-148.us-east-2.compute.internal   <none>           <none>
blog-65c8484545-qbvvp   1/1     Running   0          49m   192.168.12.111   ip-10-0-3-148.us-east-2.compute.internal   <none>           <none>

これらのIPアドレスは、Kubernetesクラスタ内でのみアクセス可能です。直接、外部からアクセスすることはできません。

Pod は複数のコンテナを含む場合は、そのコンテナ間の通信はlocalhost で実行され、待ち受けるポートは重複してはいけません。例えば、1つのPod の中に80番ポートで待ち受けるnginx コンテナを2つ以上含めることはできません。

Pod 間の通信は、CNI (Container Network Interface) プラグインが担います。つまり、CNI は、このようにPod によしなにIP アドレスを振って、Pod 同士が通信できるよう取り計らう役割を持っています。Kubernetes クラスタを作成する場合CNI プラグインのインストールが必要なため、今回のハンズオン環境では確認することはできませんが、この環境にも当然CNI はインストールされています。CNI の例としては、 Calico Antrea が挙げられます。

Service リソース #

次に、Pod と外部との通信ですが、これはKubernetes のリソースであるService リソースを介して行われます。先ほど説明した通り、Pod のIPアドレスは、Kubernetesクラスタ内でのみアクセス可能ですので、直接外部ネットワークからアクセスすることは基本的にできません。

加えて、Pod は短命です。Replicaset の役割はPod をレプリカの数だけ稼働させることであり、停止したPod を再起動させるわけではなく、再作成します。この場合はPod の名前やIPアドレスも変更されます。すなわち、通信先のコンポーネントのIP アドレスは変わるものとしてアプリケーションを設計しなければなりません。

アプリケーションに安定したIPアドレスとホスト名を割り当て、かつ外部との接続を実現するために、Kubernetes にはService リソースと呼ばれるリソースがあります。

Service という名前がややこしいですが、Pod やDeployment などと同様、Kubernetes にService という名前のリソースがあります。

さっそくService リソースのマニフェストを確認します。

cat ~/frontend/service.yaml
apiVersion: v1
kind: Service
metadata:
name: blog
labels:
    app: blog
spec:
type: ClusterIP
selector:
    app: blog
ports:
- name: 8080-tcp
    port: 8080
    protocol: TCP
    targetPort: 8080

これをkubectl apply すると、blog という名前のService が作成されたことになります。このマニフェストは「app:blog というラベルが付いたアプリケーションのためにポート8080 を公開し、Pod の中のコンテナが待ち受けているポート8080 にマッピングします」ということを意味しています。

既にこのService は作成されているため、Service の詳細を確認してみましょう。

kubectl get service --selector app=blog -o wide 

これにより、以下のような出力が表示されます。

NAME   TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE   SELECTOR
blog   ClusterIP   10.108.113.113   <none>        8080/TCP   28m   app=blog

SELECTOR に注目しましょう。これはマニフェスト中のspec.selector の値です。

selector:
    app: blog

これは、どのPod をService のエンドポイントとして登録するかを定義していて、これはラベルによって制御されます。すなわち下記のコマンドの実行で得られるPod をService のエンドポイントとして登録し、Service のIP アドレスでアクセスできるようにします。

kubectl get pods -l app=blog -o name
ラベル管理を適切に設定しないと、意図しないPod がService のバックエンドに配置される場合があります。

なお、サービスに対して登録されているポッドのIPアドレスは、次のように実行することで確認できます。

kubectl get endpoints blog

Service のIPアドレスはそのService が存続する限り変更されることはありませんが、それでもそのIP は使用すべきではありません。代わりに、Service に対応するホスト名を使用するべきです。ホスト名は自動的にKubernetesクラスタの内部DNSに登録され、クラスタ内のどのアプリケーションでも使用できます。ホスト名の命名規則については下記ドキュメントを参照してください。

参考: https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/

注意点として、今回作成したService に対して、Kubernetesクラスタの外から直接そのIP アドレスやホスト名にアクセスすることはできません。これは、今回作成したService がClusterIP と呼ばれるタイプであり、Pod のIP アドレスと同様、ClusterIP はクラスタ内で使用される安定したIP アドレスやホスト名の提供を目的としているためです。

補足

実はこの環境ではmy-svc.my-namespace.svc.cluster-domain.example (もしくはIP アドレス)のような形でターミナルからアクセスできます。本環境の場合は以下のようになります。

curl http://blog.${NS}.svc.cluster.local:8080

ただし、ターミナルの外、すなわちブラウザの別タブなどからアクセスすることはできません。これは、今回のハンズオン環境で提供されるターミナルの実体がまさにKubernetes のPod であり、そこからcurl を実行するということは、クラスタ内での通信に他ならないからです。

Ingress によるService の公開 #

Service をKubernetes クラスタの外からアクセスできるように公開するためには、Ingressリソースオブジェクトを作成する必要があります。

今回使用するIngressリソースオブジェクトの定義を見るには、runを使用します。

cat ~/frontend/ingress.yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: blog
labels:
    app: blog
spec:
rules:
- host: blog-eduk8s-labs-w01-s284.tdc-reg-prod-d66138e.tanzu-labs.esp.vmware.com
    http:
    paths:
    - path: "/"
        backend:
        serviceName: blog
        servicePort: 8080

この例では、blog-eduk8s-lab-w01-s284.tdc-reg-prod-d66138e.tanzu-lab.esp.vmware.comというホスト名で受信したHTTPリクエストは、blogという名前を持つService(つまりアプリケーション)に向けられるべきだというルールになっています。

外部のユーザーがWebブラウザからホスト名にアクセスする場合、HTTPトラフィックに標準のポート80を使用しますが、ルーターはそのトラフィックをサービスのポート8080に渡します。

Ingress も既に作成しているので、以下のコマンドで確認することができます。

kubectl get ingress -l app=blog

前の手順で試したように、Ingress によって払い出されたURL を使ってWeb ブラウザからフロントエンドのWebアプリケーションにアクセスできます。

なお、このようにIngress によって払い出されたURL でアクセスするためには、トラフィックをKubernetesクラスタに向けるために、外部のドメインネームサーバ(DNS)でワイルドカードCNAMEが設定されている必要があります(この環境では既に設定されています)。

補足

実はIngress リソースだけではアプリケーションを公開できません。実際に外部からのトラフィックを適切にルーティングしているのはLoadBalancer と呼ばれるService リソースだからです。同じService リソースでも、ClusterIP と異なり、LoadBalancer はクラスタ内部ではなく、クラスタの外と中のネットワークと繋げる役割を持ちます。

https://kubernetes.io/docs/concepts/services-networking/service/#loadbalancer