Kubernetesのネットワーキングは4つの懸念事項に対処します。
- Pod内のコンテナは、ネットワーキングを利用してループバック経由の通信を行います。
- クラスターネットワーキングは、異なるPod間の通信を提供します。
- Serviceリソースは、Pod内で動作しているアプリケーションへクラスターの外部から到達可能なように露出を許可します。
- Serviceを利用して、クラスタ内部のみで使用するServiceの公開も可能です。
これは、このセクションの複数ページの印刷可能なビューです。 印刷するには、ここをクリックしてください.
Kubernetesのネットワーキングは4つの懸念事項に対処します。
Kubernetesでは、なじみのないサービスディスカバリーのメカニズムを使用するためにユーザーがアプリケーションの修正をする必要はありません。 KubernetesはPodにそれぞれのIPアドレス割り振りや、Podのセットに対する単一のDNS名を提供したり、それらのPodのセットに対する負荷分散が可能です。
Kubernetes Podはクラスターの状態に合わせて作成され削除されます。Podは揮発的なリソースです。 Deploymentをアプリケーションを稼働させるために使用すると、Podを動的に作成・削除してくれます。
各Podはそれ自身のIPアドレスを持ちます。しかしDeploymentでは、ある時点において同時に稼働しているPodのセットは、その後のある時点において稼働しているPodのセットとは異なる場合があります。
この仕組みはある問題を引き起こします。もし、あるPodのセット(ここでは"バックエンド"と呼びます)がクラスター内で他のPodのセット(ここでは"フロントエンド"と呼びます)に対して機能を提供する場合、フロントエンドのPodがワークロードにおけるバックエンドを使用するために、バックエンドのPodのIPアドレスを探し出したり、記録し続けるためにはどうすればよいでしょうか?
ここで Service について説明します。
Kubernetesにおいて、ServiceはPodの論理的なセットや、そのPodのセットにアクセスするためのポリシーを定義します(このパターンはよくマイクロサービスと呼ばることがあります)。 ServiceによってターゲットとされたPodのセットは、たいてい セレクターによって定義されます。 その他の方法について知りたい場合はセレクターなしのServiceを参照してください。
例えば、3つのレプリカが稼働しているステートレスな画像処理用のバックエンドを考えます。これらのレプリカは代替可能です。— フロントエンドはバックエンドが何であろうと気にしません。バックエンドのセットを構成する実際のPodのセットが変更された際、フロントエンドクライアントはその変更を気にしたり、バックエンドのPodのセットの情報を記録しておく必要はありません。
Serviceによる抽象化は、クライアントからバックエンドのPodの管理する責務を分離することを可能にします。
アプリケーション内でサービスディスカバリーのためにKubernetes APIが使える場合、ユーザーはエンドポイントをAPI Serverに問い合わせることができ、またService内のPodのセットが変更された時はいつでも更新されたエンドポイントの情報を取得できます。
非ネイティブなアプリケーションのために、KubernetesはアプリケーションとバックエンドPodの間で、ネットワークポートやロードバランサーを配置する方法を提供します。
KubernetesのServiceはPodと同様にRESTのオブジェクトです。他のRESTオブジェクトと同様に、ユーザーはServiceの新しいインスタンスを作成するためにAPIサーバーに対してServiceの定義をPOST
できます。Serviceオブジェクトの名前は、有効なDNSラベル名である必要があります。
例えば、TCPで9376番ポートで待ち受けていて、app=Myapp
というラベルをもつPodのセットがあるとします。
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
selector:
app: MyApp
ports:
- protocol: TCP
port: 80
targetPort: 9376
この定義では、"my-service"という名前のついた新しいServiceオブジェクトを作成します。これはapp=Myapp
ラベルのついた各Pod上でTCPの9376番ポートをターゲットとします。
Kubernetesは、このServiceに対してIPアドレス("clusterIP"とも呼ばれます)を割り当てます。これはServiceのプロキシーによって使用されます(下記の仮想IPとServiceプロキシーを参照ください)。
Serviceセレクターのコントローラーはセレクターに一致するPodを継続的にスキャンし、“my-service”という名前のEndpointsオブジェクトに対して変更をPOSTします。
port
からtargetPort
へのマッピングを行います。デフォルトでは、利便性のためにtargetPort
フィールドはport
フィールドと同じ値で設定されます。
Pod内のポートの定義は名前を設定でき、ServiceのtargetPort
属性にてその名前を参照できます。これは単一の設定名をもつService内で、複数の種類のPodが混合していたとしても有効で、異なるポート番号を介することによって利用可能な、同一のネットワークプロトコルを利用します。
この仕組みはServiceをデプロイしたり、設定を追加する場合に多くの点でフレキシブルです。例えば、バックエンドソフトウェアにおいて、次のバージョンでPodが公開するポート番号を変更するときに、クライアントの変更なしに行えます。
ServiceのデフォルトプロトコルはTCPです。また、他のサポートされているプロトコルも利用可能です。
多くのServiceが、1つ以上のポートを公開する必要があるように、Kubernetesは1つのServiceオブジェクトに対して複数のポートの定義をサポートしています。
各ポート定義は同一のprotocol
または異なる値を設定できます。
Serviceは多くの場合、KubernetesのPodに対するアクセスを抽象化しますが、他の種類のバックエンドも抽象化できます。 例えば:
このような場合において、ユーザーはPodセレクターなしでServiceを定義できます。
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
ports:
- protocol: TCP
port: 80
targetPort: 9376
このServiceはセレクターがないため、対応するEndpointsオブジェクトは自動的に作成されません。 ユーザーはEndpointsオブジェクトを手動で追加することにより、向き先のネットワークアドレスとポートを手動でマッピングできます。
apiVersion: v1
kind: Endpoints
metadata:
name: my-service
subsets:
- addresses:
- ip: 192.0.2.42
ports:
- port: 9376
Endpointsオブジェクトの名前は、有効なDNSサブドメイン名である必要があります。
Endpointsのipは、loopback (127.0.0.0/8 for IPv4, ::1/128 for IPv6), や link-local (169.254.0.0/16 and 224.0.0.0/24 for IPv4, fe80::/64 for IPv6)に設定することができません。
kube-proxyが仮想IPを最終的な到達先に設定することをサポートしていないため、Endpointsのipアドレスは他のKubernetes ServiceのClusterIPにすることができません。
セレクターなしのServiceへのアクセスは、セレクターをもっているServiceと同じようにふるまいます。上記の例では、トラフィックはYAMLファイル内で192.0.2.42:9376
(TCP)で定義された単一のエンドポイントにルーティングされます。
ExternalName Serviceはセレクターの代わりにDNS名を使用する特殊なケースのServiceです。さらなる情報は、このドキュメントの後で紹介するExternalNameを参照ください。
Kubernetes v1.17 [beta]
エンドポイントスライスは、Endpointsに対してよりスケーラブルな代替手段を提供できるAPIリソースです。概念的にはEndpointsに非常に似ていますが、エンドポイントスライスを使用すると、ネットワークエンドポイントを複数のリソースに分割できます。デフォルトでは、エンドポイントスライスは、100個のエンドポイントに到達すると「いっぱいである」と見なされ、その時点で追加のエンドポイントスライスが作成され、追加のエンドポイントが保存されます。
エンドポイントスライスは、エンドポイントスライスのドキュメントにて詳しく説明されている追加の属性と機能を提供します。
Kubernetes v1.19 [beta]
AppProtocol
フィールドによってServiceの各ポートに対して特定のアプリケーションプロトコルを指定することができます。
この値は、対応するEndpointsオブジェクトとEndpointSliceオブジェクトに反映されます。
Kubernetesクラスターの各Nodeはkube-proxy
を稼働させています。kube-proxy
はExternalName
タイプ以外のService
用に仮想IPを実装する責務があります。
ここで湧き上がる質問として、なぜKubernetesは内部のトラフィックをバックエンドへ転送するためにプロキシーに頼るのでしょうか。 他のアプローチはどうなのでしょうか。例えば、複数のAバリュー(もしくはIPv6用にAAAAバリューなど)をもつDNSレコードを設定し、ラウンドロビン方式で名前を解決することは可能でしょうか。
Serviceにおいてプロキシーを使う理由はいくつかあります。
このモードでは、kube-proxyはServiceやEndpointsオブジェクトの追加・削除をチェックするために、Kubernetes Masterを監視します。
各Serviceは、ローカルのNode上でポート(ランダムに選ばれたもの)を公開します。この"プロキシーポート"に対するどのようなリクエストも、そのServiceのバックエンドPodのどれか1つにプロキシーされます(Endpointsを介して通知されたPodに対して)。
kube-proxyは、どのバックエンドPodを使うかを決める際にServiceのSessionAffinity
項目の設定を考慮に入れます。
最後に、user-spaceプロキシーはServiceのclusterIP
(仮想IP)とport
に対するトラフィックをキャプチャするiptablesルールをインストールします。
そのルールは、トラフィックをバックエンドPodにプロキシーするためのプロキシーポートにリダイレクトします。
デフォルトでは、user-spaceモードにおけるkube-proxyはラウンドロビンアルゴリズムによってバックエンドPodを選択します。
iptables
プロキシーモードこのモードでは、kube-proxyはServiceやEndpointsオブジェクトの追加・削除のチェックのためにKubernetesコントロールプレーンを監視します。
各Serviceでは、そのServiceのclusterIP
とport
に対するトラフィックをキャプチャするiptablesルールをインストールし、そのトラフィックをServiceのあるバックエンドのセットに対してリダイレクトします。
各Endpointsオブジェクトは、バックエンドのPodを選択するiptablesルールをインストールします。
デフォルトでは、iptablesモードにおけるkube-proxyはバックエンドPodをランダムで選択します。
トラフィックのハンドリングのためにiptablesを使用すると、システムのオーバーヘッドが少なくなります。これは、トラフィックがLinuxのnetfilterによってuser-spaceとkernel-spaceを切り替える必要がないためです。 このアプローチは、オーバーヘッドが少ないことに加えて、より信頼できる方法でもあります。
kube-proxyがiptablesモードで稼働し、最初に選択されたPodが応答しない場合、そのコネクションは失敗します。 これはuser-spaceモードでの挙動と異なります: user-spaceモードにおいては、kube-proxyは最初のPodに対するコネクションが失敗したら、自動的に他のバックエンドPodに対して再接続を試みます。
iptablesモードのkube-proxyが正常なバックエンドPodのみをリダイレクト対象とするために、PodのReadinessProbeを使用してバックエンドPodが正常に動作しているか確認できます。これは、ユーザーがkube-proxyを介して、コネクションに失敗したPodに対してトラフィックをリダイレクトするのを除外することを意味します。
Kubernetes v1.11 [stable]
ipvs
モードにおいて、kube-proxyはServiceとEndpointsオブジェクトを監視し、IPVSルールを作成するためにnetlink
インターフェースを呼び出し、定期的にKubernetesのServiceとEndpointsとIPVSルールを同期させます。
このコントロールループはIPVSのステータスが理想的な状態になることを保証します。
Serviceにアクセスするとき、IPVSはトラフィックをバックエンドのPodに向けます。
IPVSプロキシーモードはiptablesモードと同様に、netfilterのフック関数に基づいています。ただし、基礎となるデータ構造としてハッシュテーブルを使っているのと、kernel-spaceで動作します。 これは、IPVSモードにおけるkube-proxyはiptablesモードに比べてより低いレイテンシーでトラフィックをリダイレクトし、プロキシーのルールを同期する際にはよりパフォーマンスがよいことを意味します。 他のプロキシーモードと比較して、IPVSモードはより高いネットワークトラフィックのスループットをサポートしています。
IPVSはバックエンドPodに対するトラフィックのバランシングのために多くのオプションを下記のとおりに提供します。
rr
: ラウンドロビンlc
: 最低コネクション数(オープンされているコネクション数がもっとも小さいもの)dh
: 送信先IPによって割り当てられたハッシュ値をもとに割り当てる(Destination Hashing)sh
: 送信元IPによって割り当てられたハッシュ値をもとに割り当てる(Source Hashing)sed
: 見込み遅延が最小なものnq
: キューなしスケジューリングIPVSモードでkube-proxyを稼働させるためには、kube-proxyを稼働させる前にNode上でIPVSを有効にしなければなりません。
kube-proxyはIPVSモードで起動する場合、IPVSカーネルモジュールが利用可能かどうかを確認します。 もしIPVSカーネルモジュールが見つからなかった場合、kube-proxyはiptablesモードで稼働するようにフォールバックされます。
このダイアグラムのプロキシーモデルにおいて、ServiceのIP:Portに対するトラフィックは、クライアントがKubernetesのServiceやPodについて何も知ることなく適切にバックエンドにプロキシーされています。
特定のクライアントからのコネクションが、毎回同一のPodにリダイレクトされるようにするためには、service.spec.sessionAffinity
に"ClientIP"を設定することにより、クライアントのIPアドレスに基づいたSessionAffinityを選択することができます(デフォルトは"None")。
また、service.spec.sessionAffinityConfig.clientIP.timeoutSeconds
を適切に設定することにより、セッションのタイムアウト時間を設定できます(デフォルトではこの値は18,000で、3時間となります)。
いくつかのServiceにおいて、ユーザーは1つ以上のポートを公開する必要があります。Kubernetesは、Serviceオブジェクト上で複数のポートを定義するように設定できます。 Serviceで複数のポートを使用するとき、どのポートかを明確にするために、複数のポート全てに対して名前をつける必要があります。 例えば:
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
selector:
app: MyApp
ports:
- name: http
protocol: TCP
port: 80
targetPort: 9376
- name: https
protocol: TCP
port: 443
targetPort: 9377
KubernetesのPod名と同様に、ポート名は小文字の英数字と-
のみ含める必要があります。また、ポート名の最初と最後の文字は英数字である必要があります。
例えば、123-abc
やweb
という名前は有効で、123_abc
や-web
は無効です。
Service
を作成するリクエストの一部として、ユーザー所有のclusterIPアドレスを指定することができます。
これを行うためには.spec.clusterIP
フィールドにセットします。
使用例として、もしすでに再利用したいDNSエントリーが存在していた場合や、特定のIPアドレスを設定されたレガシーなシステムや、IPの再設定が難しい場合です。
ユーザーが指定したIPアドレスは、そのAPIサーバーのために設定されているservice-cluster-ip-range
というCIDRレンジ内の有効なIPv4またはIPv6アドレスである必要があります。
もし無効なclusterIPアドレスの値を設定してServiceを作成した場合、問題があることを示すためにAPIサーバーはHTTPステータスコード422を返します。
Kubernetesは、Serviceオブジェクトを見つけ出すために2つの主要なモードをサポートしています。 - それは環境変数とDNSです。
PodがNode上で稼働するとき、kubeletはアクティブな各Serviceに対して、環境変数のセットを追加します。
これはDocker links互換性のある変数(
makeLinkVariables関数を確認してください)や、より簡単な{SVCNAME}_SERVICE_HOST
や、{SVCNAME}_SERVICE_PORT
変数をサポートします。この変数名で使われるService名は大文字に変換され、-
は_
に変換されます。
例えば、TCPポート6379番を公開していて、さらにclusterIPが10.0.0.11に割り当てられているredis-master
というServiceは、下記のような環境変数を生成します。
REDIS_MASTER_SERVICE_HOST=10.0.0.11
REDIS_MASTER_SERVICE_PORT=6379
REDIS_MASTER_PORT=tcp://10.0.0.11:6379
REDIS_MASTER_PORT_6379_TCP=tcp://10.0.0.11:6379
REDIS_MASTER_PORT_6379_TCP_PROTO=tcp
REDIS_MASTER_PORT_6379_TCP_PORT=6379
REDIS_MASTER_PORT_6379_TCP_ADDR=10.0.0.11
Serviceにアクセスする必要のあるPodがあり、クライアントであるそのPodに対して環境変数を使ってポートとclusterIPを公開する場合、クライアントのPodが存在する前に Serviceを作成しなくてはなりません。 そうでない場合、クライアントのPodはそれらの環境変数を作成しません。
ServiceのclusterIPを発見するためにDNSのみを使う場合、このような問題を心配する必要はありません。
ユーザーはアドオンを使ってKubernetesクラスターにDNS Serviceをセットアップできます(常にセットアップすべきです)。
CoreDNSなどのクラスター対応のDNSサーバーは新しいServiceや、各Service用のDNSレコードのセットのためにKubernetes APIを常に監視します。 もしクラスターを通してDNSが有効になっている場合、全てのPodはDNS名によって自動的にServiceに対する名前解決をするようにできるはずです。
例えば、Kubernetesのmy-ns
というNamespace内でmy-service
というServiceがある場合、KubernetesコントロールプレーンとDNS Serviceが協調して動作し、my-service.my-ns
というDNSレコードを作成します。
my-ns
というNamespace内のPodはmy-service
という名前で簡単に名前解決できるはずです(my-service.my-ns
でも動作します)。
他のNamespace内でのPodはmy-service.my-ns
といった形で指定しなくてはなりません。これらのDNS名は、そのServiceのclusterIPに名前解決されます。
Kubernetesは名前付きのポートに対するDNS SRV(Service)レコードもサポートしています。もしmy-service.my-ns
というServiceがhttp
という名前のTCPポートを持っていた場合、IPアドレスと同様に、http
のポート番号を探すために_http._tcp.my-service.my-ns
というDNS SRVクエリを実行できます。
KubernetesのDNSサーバーはExternalName
Serviceにアクセスする唯一の方法です。
DNS Pods と ServiceにてExternalName
による名前解決に関するさらなる情報を確認できます。
場合によっては、負荷分散と単一のService IPは不要です。このケースにおいて、clusterIP(.spec.clusterIP
)の値を"None"
に設定することにより、"Headless"とよばれるServiceを作成できます。
ユーザーは、Kubernetesの実装と紐づくことなく、他のサービスディスカバリーのメカニズムと連携するためにHeadless Serviceを使用できます。 例えば、ユーザーはこのAPI上でカスタムオペレーターを実装することができます。
このService
においては、clusterIPは割り当てられず、kube-proxyはこのServiceをハンドリングしないのと、プラットフォームによって行われるはずの
ロードバランシングやプロキシーとしての処理は行われません。DNSがどのように自動で設定されるかは、定義されたServiceが定義されたラベルセレクターを持っているかどうかに依存します。
ラベルセレクターを定義したHeadless Serviceにおいて、EndpointsコントローラーはAPIにおいてEndpoints
レコードを作成し、Service
のバックエンドにあるPod
へのIPを直接指し示すためにDNS設定を修正します。
ラベルセレクターを定義しないHeadless Serviceにおいては、EndpointsコントローラーはEndpoints
レコードを作成しません。
しかしDNSのシステムは下記の2つ両方を探索し、設定します。
ExternalName
タイプのServiceに対するCNAMEレコードEndpoints
レコードユーザーのアプリケーションのいくつかの部分において(例えば、frontendsなど)、ユーザーのクラスターの外部にあるIPアドレス上でServiceを公開したい場合があります。
KubernetesのServiceTypes
によって、ユーザーがどのような種類のServiceを使いたいかを指定することが可能です。
デフォルトではClusterIP
となります。
Type
項目の値と、そのふるまいは以下のようになります。
ClusterIP
: クラスター内部のIPでServiceを公開する。このタイプではServiceはクラスター内部からのみ疎通性があります。このタイプはデフォルトのServiceType
です。NodePort
: 各NodeのIPにて、静的なポート(NodePort
)上でServiceを公開します。そのNodePort
のServiceが転送する先のClusterIP
Serviceが自動的に作成されます。<NodeIP>:<NodePort>
にアクセスすることによってNodePort
Serviceにアクセスできるようになります。LoadBalancer
: クラウドプロバイダーのロードバランサーを使用して、Serviceを外部に公開します。クラスター外部にあるロードバランサーが転送する先のNodePort
とClusterIP
Serviceは自動的に作成されます。ExternalName
: CNAME
レコードを返すことにより、externalName
フィールドに指定したコンテンツ(例: foo.bar.example.com
)とServiceを紐づけます。しかし、いかなる種類のプロキシーも設定されません。
ExternalName
タイプのServiceを利用するためには、kube-dnsのバージョン1.7かCoreDNSのバージョン0.0.8以上が必要となります。
また、Serviceを公開するためにIngressも利用可能です。IngressはServiceのタイプではありませんが、クラスターに対するエントリーポイントとして動作します。 Ingressは同一のIPアドレスにおいて、複数のServiceを公開するように、ユーザーの設定した転送ルールを1つのリソースにまとめることができます。
もしtype
フィールドの値をNodePort
に設定すると、Kubernetesコントロールプレーンは--service-node-port-range
フラグによって指定されたレンジのポート(デフォルト: 30000-32767)を割り当てます。
各Nodeはそのポート(各Nodeで同じポート番号)への通信をServiceに転送します。
作成したServiceは、.spec.ports[*].nodePort
フィールド内に割り当てられたポートを記述します。
もしポートへの通信を転送する特定のIPを指定したい場合、特定のIPブロックをkube-proxyの--nodeport-address
フラグで指定できます。これはKubernetes v1.10からサポートされています。
このフラグは、コンマ区切りのIPブロックのリスト(例: 10.0.0./8, 192.0.2.0/25)を使用し、kube-proxyがこのNodeに対してローカルとみなすべきIPアドレスの範囲を指定します。
例えば、--nodeport-addresses=127.0.0.0/8
というフラグによってkube-proxyを起動した時、kube-proxyはNodePort Serviceのためにループバックインターフェースのみ選択します。--nodeport-addresses
のデフォルト値は空のリストになります。これはkube-proxyがNodePort Serviceに対して全てのネットワークインターフェースを利用可能とするべきということを意味します(これは以前のKubernetesのバージョンとの互換性があります)。
もしポート番号を指定したい場合、nodePort
フィールドに値を指定できます。コントロールプレーンは指定したポートを割り当てるか、APIトランザクションが失敗したことを知らせるかのどちらかになります。
これは、ユーザーが自分自身で、ポート番号の衝突に関して気をつける必要があることを意味します。
また、ユーザーは有効なポート番号を指定する必要があり、NodePortの使用において、設定された範囲のポートを指定する必要があります。
NodePortの使用は、Kubernetesによって完全にサポートされていないようなユーザー独自の負荷分散を設定をするための有効な方法や、1つ以上のNodeのIPを直接公開するための方法となりえます。
注意点として、このServiceは<NodeIP>:spec.ports[*].nodePort
と、.spec.clusterIP:spec.ports[*].port
として疎通可能です。
(もしkube-proxyにおいて--nodeport-addressses
が設定された場合、
例えば:
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
type: NodePort
selector:
app: MyApp
ports:
# デフォルトでは利便性のため、 `targetPort` は `port` と同じ値にセットされます。
- port: 80
targetPort: 80
# 省略可能なフィールド
# デフォルトでは利便性のため、Kubernetesコントロールプレーンはある範囲から1つポートを割り当てます(デフォルト値の範囲:30000-32767)
nodePort: 30007
外部のロードバランサーをサポートするクラウドプロバイダー上で、type
フィールドにLoadBalancer
を設定すると、Service用にロードバランサーがプロビジョニングされます。
実際のロードバランサーの作成は非同期で行われ、プロビジョンされたバランサーの情報は、Serviceの.status.loadBalancer
フィールドに記述されます。
例えば:
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
selector:
app: MyApp
ports:
- protocol: TCP
port: 80
targetPort: 9376
clusterIP: 10.0.171.239
type: LoadBalancer
status:
loadBalancer:
ingress:
- ip: 192.0.2.127
外部のロードバランサーからのトラフィックはバックエンドのPodに直接転送されます。クラウドプロバイダーはどのようにそのリクエストをバランシングするかを決めます。
LoadBalancerタイプのサービスで複数のポートが定義されている場合、すべてのポートが同じプロトコルである必要があり、さらにそのプロトコルはTCP
、UDP
、SCTP
のいずれかである必要があります。
いくつかのクラウドプロバイダーにおいて、loadBalancerIP
の設定をすることができます。このようなケースでは、そのロードバランサーはユーザーが指定したloadBalancerIP
に対してロードバランサーを作成します。
もしloadBalancerIP
フィールドの値が指定されていない場合、そのロードバランサーはエフェメラルなIPアドレスに対して作成されます。もしユーザーがloadBalancerIP
を指定したが、使っているクラウドプロバイダーがその機能をサポートしていない場合、そのloadBalancerIP
フィールドに設定された値は無視されます。
LoadBalancer
タイプのServiceに関する使用上の警告を参照してください。
Azure において、もしユーザーが指定するloadBalancerIP
を使用したい場合、最初に静的なパブリックIPアドレスのリソースを作成する必要があります。
このパブリックIPアドレスのリソースは、クラスター内で自動的に作成された他のリソースと同じグループに作られるべきです。
例: MC_myResourceGroup_myAKSCluster_eastus
割り当てられたIPアドレスをloadBalancerIPとして指定してください。クラウドプロバイダーの設定ファイルにおいてsecurityGroupNameを更新したことを確認してください。
CreatingLoadBalancerFailed
というパーミッションの問題に対するトラブルシューティングの情報は、Azure Kubernetes Service(AKS)のロードバランサーで静的IPアドレスを使用する や、高度なネットワークを使用したAKSクラスターでのCreatingLoadBalancerFailedを参照してください。
複雑な環境において、同一の(仮想)ネットワークアドレスブロック内のServiceからのトラフィックを転送する必要がでてきます。
Split-HorizonなDNS環境において、ユーザーは2つのServiceを外部と内部の両方からのトラフィックをエンドポイントに転送させる必要がでてきます。
ユーザーは、Serviceに対して下記のアノテーションを1つ追加することでこれを実現できます。 追加するアノテーションは、ユーザーが使っているクラウドプロバイダーに依存しています。
タブを選択してください。
[...]
metadata:
name: my-service
annotations:
cloud.google.com/load-balancer-type: "Internal"
[...]
[...]
metadata:
name: my-service
annotations:
service.beta.kubernetes.io/aws-load-balancer-internal: 0.0.0.0/0
[...]
[...]
metadata:
name: my-service
annotations:
service.beta.kubernetes.io/azure-load-balancer-internal: "true"
[...]
[...]
metadata:
name: my-service
annotations:
service.kubernetes.io/ibm-load-balancer-cloud-provider-ip-type: "private"
[...]
[...]
metadata:
name: my-service
annotations:
service.beta.kubernetes.io/openstack-internal-load-balancer: "true"
[...]
[...]
metadata:
name: my-service
annotations:
service.beta.kubernetes.io/cce-load-balancer-internal-vpc: "true"
[...]
[...]
metadata:
annotations:
service.kubernetes.io/qcloud-loadbalancer-internal-subnetid: subnet-xxxxx
[...]
AWS上で稼働しているクラスターにおいて、部分的なTLS/SSLのサポートをするには、LoadBalancer
Serviceに対して3つのアノテーションを追加できます。
metadata:
name: my-service
annotations:
service.beta.kubernetes.io/aws-load-balancer-ssl-cert: arn:aws:acm:us-east-1:123456789012:certificate/12345678-1234-1234-1234-123456789012
1つ目は、使用する証明書のARNです。これはIAMにアップロードされたサードパーティーが発行した証明書か、AWS Certificate Managerで作成された証明書になります。
metadata:
name: my-service
annotations:
service.beta.kubernetes.io/aws-load-balancer-backend-protocol: (https|http|ssl|tcp)
2つ目のアノテーションはPodが利用するプロトコルを指定するものです。HTTPSとSSLの場合、ELBはそのPodが証明書を使って暗号化されたコネクションを介して自分自身のPodを認証すると推測します。
HTTPとHTTPSでは、レイヤー7でのプロキシーを選択します。ELBはユーザーとのコネクションを切断し、リクエストを転送するときにリクエストヘッダーをパースして、X-Forwarded-For
ヘッダーにユーザーのIPを追加します(Podは接続相手のELBのIPアドレスのみ確認可能です)。
TCPとSSLでは、レイヤー4でのプロキシーを選択します。ELBはヘッダーの値を変更せずにトラフィックを転送します。
いくつかのポートがセキュアに保護され、他のポートではセキュアでないような混合した環境において、下記のようにアノテーションを使うことができます。
metadata:
name: my-service
annotations:
service.beta.kubernetes.io/aws-load-balancer-backend-protocol: http
service.beta.kubernetes.io/aws-load-balancer-ssl-ports: "443,8443"
上記の例では、もしServiceが80
、443
、8443
と3つのポートを含んでいる場合、443
と8443
はSSL証明書を使いますが、80
では単純にHTTPでのプロキシーとなります。
Kubernetes v1.9以降のバージョンからは、Serviceのリスナー用にHTTPSやSSLと事前定義されたAWS SSLポリシーを使用できます。
どのポリシーが使用できるかを確認するために、aws
コマンドラインツールを使用できます。
aws elb describe-load-balancer-policies --query 'PolicyDescriptions[].PolicyName'
ユーザーは"service.beta.kubernetes.io/aws-load-balancer-ssl-negotiation-policy
"というアノテーションを使用することにより、複数のポリシーの中からどれか1つを指定できます。
例えば:
metadata:
name: my-service
annotations:
service.beta.kubernetes.io/aws-load-balancer-ssl-negotiation-policy: "ELBSecurityPolicy-TLS-1-2-2017-01"
AWS上で稼働するクラスターでPROXY protocolのサポートを有効にするために、下記のServiceのアノテーションを使用できます。
metadata:
name: my-service
annotations:
service.beta.kubernetes.io/aws-load-balancer-proxy-protocol: "*"
Kubernetesバージョン1.3.0からは、このアノテーションを使用するとELBによってプロキシーされた全てのポートが対象になり、そしてそれ以外の場合は構成されません。
AWS上でのELB Service用のアクセスログを管理するためにはいくつかのアノテーションが使用できます。
service.beta.kubernetes.io/aws-load-balancer-access-log-enabled
というアノテーションはアクセスログを有効にするかを設定できます。
service.beta.kubernetes.io/aws-load-balancer-access-log-emit-interval
というアノテーションはアクセスログをパブリッシュするためのインターバル(分)を設定できます。
ユーザーはそのインターバルで5分もしくは60分で設定できます。
service.beta.kubernetes.io/aws-load-balancer-access-log-s3-bucket-name
というアノテーションはロードバランサーのアクセスログが保存されるAmazon S3のバケット名を設定できます。
service.beta.kubernetes.io/aws-load-balancer-access-log-s3-bucket-prefix
というアノテーションはユーザーが作成したAmazon S3バケットの論理的な階層を指定します。
metadata:
name: my-service
annotations:
service.beta.kubernetes.io/aws-load-balancer-access-log-enabled: "true"
# ロードバランサーのアクセスログが有効かどうか。
service.beta.kubernetes.io/aws-load-balancer-access-log-emit-interval: "60"
# アクセスログをパブリッシュするためのインターバル(分)。ユーザーはそのインターバルで5分もしくは60分で設定できます。
service.beta.kubernetes.io/aws-load-balancer-access-log-s3-bucket-name: "my-bucket"
# ロードバランサーのアクセスログが保存されるAmazon S3のバケット名。
service.beta.kubernetes.io/aws-load-balancer-access-log-s3-bucket-prefix: "my-bucket-prefix/prod"
# ユーザーが作成したAmazon S3バケットの論理的な階層。例えば: `my-bucket-prefix/prod`
古いタイプのELBでの接続の中断は、service.beta.kubernetes.io/aws-load-balancer-connection-draining-enabled
というアノテーションを"true"
に設定することで管理できます。
service.beta.kubernetes.io/aws-load-balancer-connection-draining-timeout
というアノテーションで、インスタンスを登録解除するまえに既存の接続をオープンにし続けるための最大時間(秒)を指定できます。
metadata:
name: my-service
annotations:
service.beta.kubernetes.io/aws-load-balancer-connection-draining-enabled: "true"
service.beta.kubernetes.io/aws-load-balancer-connection-draining-timeout: "60"
古いタイプのELBを管理するためのアノテーションは他にもあり、下記で紹介します。
metadata:
name: my-service
annotations:
service.beta.kubernetes.io/aws-load-balancer-connection-idle-timeout: "60"
# ロードバランサーによってクローズされる前にアイドル状態(コネクションでデータは送信されない)になれる秒数
service.beta.kubernetes.io/aws-load-balancer-cross-zone-load-balancing-enabled: "true"
# ゾーンを跨いだロードバランシングが有効かどうか
service.beta.kubernetes.io/aws-load-balancer-additional-resource-tags: "environment=prod,owner=devops"
# ELBにおいて追加タグとして保存されるキー・バリューのペアのコンマ区切りのリスト
service.beta.kubernetes.io/aws-load-balancer-healthcheck-healthy-threshold: ""
# バックエンドへのトラフィックが正常になったと判断するために必要なヘルスチェックの連続成功数
# デフォルトでは2 この値は2から10の間で設定可能
service.beta.kubernetes.io/aws-load-balancer-healthcheck-unhealthy-threshold: "3"
# バックエンドへのトラフィックが異常になったと判断するために必要なヘルスチェックの連続失敗数
# デフォルトでは6 この値は2から10の間で設定可能
service.beta.kubernetes.io/aws-load-balancer-healthcheck-interval: "20"
# 各インスタンスのヘルスチェックのおよそのインターバル(秒)
# デフォルトでは10 この値は5から300の間で設定可能
service.beta.kubernetes.io/aws-load-balancer-healthcheck-timeout: "5"
# ヘルスチェックが失敗したと判断されるレスポンスタイムのリミット(秒)
# この値はservice.beta.kubernetes.io/aws-load-balancer-healthcheck-intervalの値以下である必要があります。
# デフォルトでは5 この値は2から60の間で設定可能
service.beta.kubernetes.io/aws-load-balancer-security-groups: "sg-53fae93f"
# ELBが作成される際に追加されるセキュリティグループのリスト
# service.beta.kubernetes.io/aws-load-balancer-extra-security-groupsアノテーションと異なり
# 元々ELBに付与されていたセキュリティグループを置き換えることになります。
service.beta.kubernetes.io/aws-load-balancer-extra-security-groups: "sg-53fae93f,sg-42efd82e"
# ELBに追加される予定のセキュリティーグループのリスト
service.beta.kubernetes.io/aws-load-balancer-target-node-labels: "ingress-gw,gw-name=public-api"
# ロードバランサーがターゲットノードを指定する際に利用するキーバリューのペアのコンマ区切りリストです。
Kubernetes v1.15 [beta]
AWSでNetwork Load Balancerを使用するには、値をnlb
に設定してアノテーションservice.beta.kubernetes.io/aws-load-balancer-type
を付与します。
metadata:
name: my-service
annotations:
service.beta.kubernetes.io/aws-load-balancer-type: "nlb"
古いタイプのElastic Load Balancersとは異なり、Network Load Balancers (NLBs)はクライアントのIPアドレスをNodeに転送します。
もしServiceの.spec.externalTrafficPolicy
の値がCluster
に設定されていた場合、クライアントのIPアドレスは末端のPodに伝播しません。
.spec.externalTrafficPolicy
をLocal
に設定することにより、クライアントIPアドレスは末端のPodに伝播します。しかし、これにより、トラフィックの分配が不均等になります。
特定のLoadBalancer Serviceに紐づいたPodがないNodeでは、自動的に割り当てられた.spec.healthCheckNodePort
に対するNLBのターゲットグループのヘルスチェックが失敗し、トラフィックを全く受信しません。
均等なトラフィックの分配を実現するために、DaemonSetの使用や、同一のNodeに配備しないようにPodのanti-affinityを設定します。
また、内部のロードバランサーのアノテーションとNLB Serviceを使用できます。
NLBの背後にあるインスタンスに対してクライアントのトラフィックを転送するために、Nodeのセキュリティーグループは下記のようなIPルールに従って変更されます。
Rule | Protocol | Port(s) | IpRange(s) | IpRange Description |
---|---|---|---|---|
ヘルスチェック | TCP | NodePort(s) (.spec.healthCheckNodePort for .spec.externalTrafficPolicy = Local ) |
VPC CIDR | kubernetes.io/rule/nlb/health=<loadBalancerName> |
クライアントのトラフィック | TCP | NodePort(s) | .spec.loadBalancerSourceRanges (デフォルト: 0.0.0.0/0 ) |
kubernetes.io/rule/nlb/client=<loadBalancerName> |
MTUによるサービスディスカバリー | ICMP | 3,4 | .spec.loadBalancerSourceRanges (デフォルト: 0.0.0.0/0 ) |
kubernetes.io/rule/nlb/mtu=<loadBalancerName> |
どのクライアントIPがNLBにアクセス可能かを制限するためには、loadBalancerSourceRanges
を指定してください。
spec:
loadBalancerSourceRanges:
- "143.231.0.0/16"
.spec.loadBalancerSourceRanges
が設定されていない場合、KubernetesはNodeのセキュリティーグループに対して0.0.0.0/0
からのトラフィックを許可します。
もしNodeがパブリックなIPアドレスを持っていた場合、NLBでないトラフィックも修正されたセキュリティーグループ内の全てのインスタンスにアクセス可能になってしまうので注意が必要です。
以下に示すように、TKEでCloud Load Balancerを管理するためのその他のアノテーションがあります。
metadata:
name: my-service
annotations:
# 指定したノードでロードバランサーをバインドします
service.kubernetes.io/qcloud-loadbalancer-backends-label: key in (value1, value2)
# 既存のロードバランサーのID
service.kubernetes.io/tke-existed-lbid: lb-6swtxxxx
# ロードバランサー(LB)のカスタムパラメーターは、LBタイプの変更をまだサポートしていません
service.kubernetes.io/service.extensiveParameters: ""
# LBリスナーのカスタムパラメーター
service.kubernetes.io/service.listenerParameters: ""
# ロードバランサーのタイプを指定します
# 有効な値: classic(Classic Cloud Load Balancer)またはapplication(Application Cloud Load Balancer)
service.kubernetes.io/loadbalance-type: xxxxx
# パブリックネットワーク帯域幅の課金方法を指定します
# 有効な値: TRAFFIC_POSTPAID_BY_HOUR(bill-by-traffic)およびBANDWIDTH_POSTPAID_BY_HOUR(bill-by-bandwidth)
service.kubernetes.io/qcloud-loadbalancer-internet-charge-type: xxxxxx
# 帯域幅の値を指定します(値の範囲:[1-2000] Mbps)。
service.kubernetes.io/qcloud-loadbalancer-internet-max-bandwidth-out: "10"
# この注釈が設定されている場合、ロードバランサーはポッドが実行されているノードのみを登録します
# そうでない場合、すべてのノードが登録されます
service.kubernetes.io/local-svc-only-bind-node-with-pod: true
ExternalNameタイプのServiceは、ServiceをDNS名とマッピングし、my-service
やcassandra
というような従来のラベルセレクターとはマッピングしません。
ユーザーはこれらのServiceにおいてspec.externalName
フィールドの値を指定します。
このServiceの定義では、例えばprod
というNamespace内のmy-service
というServiceをmy.database.example.com
にマッピングします。
apiVersion: v1
kind: Service
metadata:
name: my-service
namespace: prod
spec:
type: ExternalName
externalName: my.database.example.com
my-service.prod.svc.cluster.local
というホストをルックアップするとき、クラスターのDNS Serviceはmy.database.example.com
という値をもつCNAME
レコードを返します。
my-service
へのアクセスは、他のServiceと同じ方法ですが、再接続する際はプロキシーや転送を介して行うよりも、DNSレベルで行われることが決定的に異なる点となります。
後にユーザーが使用しているデータベースをクラスター内に移行することになった場合は、Podを起動させ、適切なラベルセレクターやEndpointsを追加し、Serviceのtype
を変更します。
HTTPやHTTPSなどの一般的なプロトコルでExternalNameを使用する際に問題が発生する場合があります。ExternalNameを使用する場合、クラスター内のクライアントが使用するホスト名は、ExternalNameが参照する名前とは異なります。
ホスト名を使用するプロトコルの場合、この違いによりエラーまたは予期しない応答が発生する場合があります。HTTPリクエストがオリジンサーバーが認識しないHost:
ヘッダーを持っていたなら、TLSサーバーはクライアントが接続したホスト名に一致する証明書を提供できません。
もし1つ以上のクラスターNodeに転送するexternalIPが複数ある場合、Kubernetes ServiceはexternalIPs
に指定したIPで公開されます。
そのexternalIP(到達先のIPとして扱われます)のServiceのポートからトラフィックがクラスターに入って来る場合、ServiceのEndpointsのどれか1つに対して転送されます。
externalIPs
はKubernetesによって管理されず、それを管理する責任はクラスターの管理者にあります。
Serviceのspecにおいて、externalIPs
は他のどのServiceTypes
と併用して設定できます。
下記の例では、"my-service
"は"80.11.12.10:80
" (externalIP:port
)のクライアントからアクセス可能です。
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
selector:
app: MyApp
ports:
- name: http
protocol: TCP
port: 80
targetPort: 9376
externalIPs:
- 80.11.12.10
仮想IP用にuserspaceモードのプロキシーを使用すると、小規模もしくは中規模のスケールでうまく稼働できますが、1000以上のServiceがあるようなとても大きなクラスターではうまくスケールしません。 これについては、Serviceのデザインプロポーザルにてさらなる詳細を確認できます。
userspaceモードのプロキシーの使用は、Serviceにアクセスするパケットの送信元IPアドレスが不明瞭になります。 これは、いくつかの種類のネットワークフィルタリング(ファイアウォールによるフィルタリング)を不可能にします。 iptablesプロキシーモードはクラスター内の送信元IPを不明瞭にはしませんが、依然としてロードバランサーやNodePortへ疎通するクライアントに影響があります。
Type
フィールドはネストされた機能としてデザインされています。 - 各レベルの値は前のレベルに対して追加します。
これは全てのクラウドプロバイダーにおいて厳密に要求されていません(例: Google Compute EngineはLoadBalancer
を動作させるためにNodePort
を割り当てる必要はありませんが、AWSではその必要があります)が、現在のAPIでは要求しています。
これより前の情報は、ただServiceを使いたいという多くのユーザーにとっては有益かもしれません。しかし、その裏側では多くのことが行われており、理解する価値があります。
Kubernetesの主要な哲学のうちの一つは、ユーザーは、ユーザー自身のアクションによるミスでないものによって、ユーザーのアクションが失敗するような状況に晒されるべきでないことです。 Serviceリソースの設計において、これはユーザーの指定したポートが衝突する可能性がある場合はそのポートのServiceを作らないことを意味します。これは障害を分離することとなります。
Serviceのポート番号を選択できるようにするために、我々はどの2つのServiceでもポートが衝突しないことを保証します。 Kubernetesは各Serviceに、それ自身のIPアドレスを割り当てることで実現しています。
各Serviceが固有のIPを割り当てられるのを保証するために、内部のアロケーターは、Serviceを作成する前に、etcd内のグローバルの割り当てマップをアトミックに更新します。 そのマップオブジェクトはServiceのIPアドレスの割り当てのためにレジストリー内に存在しなくてはならず、そうでない場合は、Serviceの作成時にIPアドレスが割り当てられなかったことを示すエラーメッセージが表示されます。
コントロールプレーンにおいて、バックグラウンドのコントローラーはそのマップを作成する責務があります(インメモリーのロックが使われていた古いバージョンのKubernetesからのマイグレーションをサポートすることも必要です)。 また、Kubernetesは(例えば、管理者の介入によって)無効な割り当てがされているかをチェックすることと、現時点でどのServiceにも使用されていない割り当て済みIPアドレスのクリーンアップのためにコントローラーを使用します。
実際に固定された向き先であるPodのIPアドレスとは異なり、ServiceのIPは実際には単一のホストによって応答されません。 その代わり、kube-proxyは必要な時に透過的にリダイレクトされる仮想IPアドレスを定義するため、iptables(Linuxのパケット処理ロジック)を使用します。 クライアントがVIPに接続する時、そのトラフィックは自動的に適切なEndpointsに転送されます。 Service用の環境変数とDNSは、Serviceの仮想IPアドレス(とポート)の面において、自動的に生成されます。
kube-proxyは3つの微妙に異なった動作をするプロキシーモード— userspace、iptablesとIPVS — をサポートしています。
例として、上記で記述されている画像処理のアプリケーションを考えます。 バックエンドのServiceが作成されたとき、KubernetesのMasterは仮想IPを割り当てます。例えば10.0.0.1などです。 そのServiceのポートが1234で、そのServiceはクラスター内の全てのkube-proxyインスタンスによって監視されていると仮定します。 kube-proxyが新しいServiceを見つけた時、kube-proxyは新しいランダムポートをオープンし、その仮想IPアドレスの新しいポートにリダイレクトするようにiptablesを更新し、そのポート上で新しい接続を待ち受けを開始します。
クライアントがServiceの仮想IPアドレスに接続したとき、iptablesルールが有効になり、そのパケットをプロキシー自身のポートにリダイレクトします。 その"Service プロキシー"はバックエンドPodの対象を選択し、クライアントのトラフィックをバックエンドPodに転送します。
これはServiceのオーナーは、衝突のリスクなしに、求めるどのようなポートも選択できることを意味します。 クライアントは単純にそのIPとポートに対して接続すればよく、実際にどのPodにアクセスしているかを意識しません。
また画像処理のアプリケーションについて考えます。バックエンドServiceが作成された時、そのKubernetesコントロールプレーンは仮想IPアドレスを割り当てます。例えば10.0.0.1などです。 Serviceのポートが1234で、そのServiceがクラスター内のすべてのkube-proxyインスタンスによって監視されていると仮定します。 kube-proxyが新しいServiceを見つけた時、kube-proxyは仮想IPから各Serviceのルールにリダイレクトされるような、iptablesルールのセットをインストールします。 Service毎のルールは、トラフィックをバックエンドにリダイレクト(Destination NATを使用)しているEndpoints毎のルールに対してリンクしています。
クライアントがServiceの仮想IPアドレスに対して接続しているとき、そのiptablesルールが有効になります。 バックエンドのPodが選択され(SessionAffinityに基づくか、もしくはランダムで選択される)、パケットはバックエンドにリダイレクトされます。 userspaceモードのプロキシーとは異なり、パケットは決してuserspaceにコピーされず、kube-proxyは仮想IPのために稼働される必要はなく、またNodeでは変更されていないクライアントIPからトラフィックがきます。
このように同じ基本的なフローは、NodePortまたはLoadBalancerを介してトラフィックがきた場合に、実行され、ただクライアントIPは変更されます。
iptablesの処理は、大規模なクラスターの場合劇的に遅くなります。例としてはServiceが10,000ほどある場合です。 IPVSは負荷分散のために設計され、カーネル内のハッシュテーブルに基づいています。そのためIPVSベースのkube-proxyによって、多数のServiceがある場合でも一貫して高パフォーマンスを実現できます。 次第に、IPVSベースのkube-proxyは負荷分散のアルゴリズムはさらに洗練されています(最小接続数、位置ベース、重み付け、永続性など)。
ServiceはKubernetesのREST APIにおいてトップレベルのリソースです。ユーザーはそのAPIオブジェクトに関して、Service API objectでさらなる情報を確認できます。
ユーザーはどの種類のServiceにおいてもTCPを利用できます。これはデフォルトのネットワークプロトコルです。
ユーザーは多くのServiceにおいてUDPを利用できます。 type=LoadBalancerのServiceにおいては、UDPのサポートはこの機能を提供しているクラウドプロバイダーに依存しています。
もしクラウドプロバイダーがサポートしている場合、ServiceのEndpointsに転送される外部のHTTP/HTTPSでのリバースプロキシーをセットアップするために、LoadBalancerモードでServiceを作成可能です。
もしクラウドプロバイダーがサポートしている場合、Kubernetesクラスターの外部のロードバランサーを設定するためにLoadBalancerモードでServiceを利用できます。これはPROXY protocolがついた接続を転送します。
ロードバランサーは、最初の一連のオクテットを送信します。 下記のような例となります。
PROXY TCP4 192.0.2.202 10.0.42.7 12345 7\r\n
クライアントからのデータのあとに追加されます。
Kubernetes v1.19 [beta]
KubernetesはService、Endpoints、EndpointSlice、NetworkPolicyとPodの定義においてprotocol
フィールドの値でSCTPをサポートしています。ベータ版の機能のため、この機能はデフォルトで有効になっています。SCTPをクラスターレベルで無効にするには、クラスター管理者はAPI ServerにおいてSCTPSupport
フィーチャーゲートを--feature-gates=SCTPSupport=false,…
と設定して無効にする必要があります。
そのフィーチャーゲートが有効になった時、ユーザーはService、Endpoints、EndpointSlice、NetworkPolicy、またはPodのprotocol
フィールドにSCTP
を設定できます。
Kubernetesは、TCP接続と同様に、SCTPアソシエーションに応じてネットワークをセットアップします。
マルチホームSCTPアソシエーションのサポートは、複数のインターフェースとPodのIPアドレスの割り当てをサポートできるCNIプラグインを要求します。
マルチホームSCTPアソシエーションにおけるNATは、対応するカーネルモジュール内で特別なロジックを要求します。
type
がLoadBalancerで protocol
がSCTPの場合でのみサービスを作成できます。
そうでない場合、Serviceの作成要求はリジェクトされます。現時点でのクラウドのロードバランサーのプロバイダー(Azure、AWS、CloudStack、GCE、OpenStack)は全てSCTPのサポートをしていません。
Kubernetes v1.17 [alpha]
Serviceトポロジーを利用すると、Serviceのトラフィックをクラスターのノードトポロジーに基づいてルーティングできるようになります。たとえば、あるServiceのトラフィックに対して、できるだけ同じノードや同じアベイラビリティゾーン上にあるエンドポイントを優先してルーティングするように指定できます。
デフォルトでは、ClusterIP
やNodePort
Serviceに送信されたトラフィックは、Serviceに対応する任意のバックエンドのアドレスにルーティングされる可能性があります。しかし、Kubernetes 1.7以降では、「外部の」トラフィックをそのトラフィックを受信したノード上のPodにルーティングすることが可能になりました。しかし、この機能はClusterIP
Serviceでは対応しておらず、ゾーン内ルーティングなどのより複雑なトポロジーは実現不可能でした。Serviceトポロジーの機能を利用すれば、Serviceの作者が送信元ノードと送信先ノードのNodeのラベルに基づいてトラフィックをルーティングするためのポリシーを定義できるようになるため、この問題を解決できます。
送信元と送信先の間のNodeラベルのマッチングを使用することにより、オペレーターは、そのオペレーターの要件に適したメトリクスを使用して、お互いに「より近い」または「より遠い」ノードのグループを指定できます。たとえば、パブリッククラウド上のさまざまなオペレーターでは、Serviceのトラフィックを同一ゾーン内に留めようとする傾向があります。パブリッククラウドでは、ゾーンをまたぐトラフィックでは関連するコストがかかる一方、ゾーン内のトラフィックにはコストがかからない場合があるからです。その他のニーズとしては、DaemonSetが管理するローカルのPodにトラフィックをルーティングできるようにしたり、レイテンシーを低く抑えるために同じラック上のスイッチに接続されたノードにトラフィックを限定したいというものがあります。
クラスターのServiceトポロジーが有効になっていれば、ServiceのspecにtopologyKeys
フィールドを指定することで、Serviceのトラフィックのルーティングを制御できます。このフィールドは、Nodeラベルの優先順位リストで、このServiceにアクセスするときにエンドポイントをソートするために使われます。トラフィックは、最初のラベルの値が送信元Nodeのものと一致するNodeに送信されます。一致したノード上にServiceに対応するバックエンドが存在しなかった場合は、2つ目のラベルについて検討が行われ、同様に、残っているラベルが順番に検討されまます。
一致するキーが1つも見つからなかった場合、トラフィックは、Serviceに対応するバックエンドが存在しなかったかのように拒否されます。言い換えると、エンドポイントは、利用可能なバックエンドが存在する最初のトポロジーキーに基づいて選択されます。このフィールドが指定され、すべてのエントリーでクライアントのトポロジーに一致するバックエンドが存在しない場合、そのクライアントに対するバックエンドが存在しないものとしてコネクションが失敗します。「任意のトポロジー」を意味する特別な値"*"
を指定することもできます。任意の値にマッチするこの値に意味があるのは、リストの最後の値として使った場合だけです。
topologyKeys
が未指定または空の場合、トポロジーの制約は適用されません。
ホスト名、ゾーン名、リージョン名のラベルが付いたNodeを持つクラスターについて考えてみましょう。このとき、ServiceのtopologyKeys
の値を設定することで、トラフィックの向きを以下のように制御できます。
["kubernetes.io/hostname"]
。["kubernetes.io/hostname", "topology.kubernetes.io/zone", "topology.kubernetes.io/region"]
。これは、たとえばデータのローカリティが非常に重要である場合などに役に立ちます。["topology.kubernetes.io/zone", "*"]
。ServiceトポロジーはexternalTrafficPolicy=Local
と互換性がないため、Serviceは2つの機能を同時に利用できません。2つの機能を同じクラスター上の異なるServiceでそれぞれ利用することは可能ですが、同一のService上では利用できません。
有効なトポロジーキーは、現在はkubernetes.io/hostname
、topology.kubernetes.io/zone
、およびtopology.kubernetes.io/region
に限定されています。しかし、将来は一般化され、他のノードラベルも使用できるようになる予定です。
トポロジーキーは有効なラベルのキーでなければならず、最大で16個のキーまで指定できます。
すべての値をキャッチする"*"
を使用する場合は、トポロジーキーの最後の値として指定しなければなりません。
以下では、Serviceトポロジーの機能を利用したよくある例を紹介します。
ノードローカルのエンドポイントのみにルーティングするServiceの例です。もし同一ノード上にエンドポイントが存在しない場合、トラフィックは損失します。
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
selector:
app: my-app
ports:
- protocol: TCP
port: 80
targetPort: 9376
topologyKeys:
- "kubernetes.io/hostname"
ノードローカルのエンドポイントを優先して使用しますが、ノードローカルのエンドポイントが存在しない場合にはクラスター全体のエンドポイントにフォールバックするServiceの例です。
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
selector:
app: my-app
ports:
- protocol: TCP
port: 80
targetPort: 9376
topologyKeys:
- "kubernetes.io/hostname"
- "*"
同一リージョンのエンドポイントより同一ゾーンのエンドポイントを優先するServiceの例です。もしいずれのエンドポイントも存在しない場合、トラフィックは損失します。
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
selector:
app: my-app
ports:
- protocol: TCP
port: 80
targetPort: 9376
topologyKeys:
- "topology.kubernetes.io/zone"
- "topology.kubernetes.io/region"
ノードローカル、同一ゾーン、同一リージョンのエンドポイントを順番に優先し、クラスター全体のエンドポイントにフォールバックするServiceの例です。
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
selector:
app: my-app
ports:
- protocol: TCP
port: 80
targetPort: 9376
topologyKeys:
- "kubernetes.io/hostname"
- "topology.kubernetes.io/zone"
- "topology.kubernetes.io/region"
- "*"
このページではKubernetesによるDNSサポートについて概観します。
KubernetesのDNSはクラスター上でDNS PodとServiceをスケジュールし、DNSの名前解決をするために各コンテナに対してDNS ServiceのIPを使うようにKubeletを設定します。
クラスター内(DNSサーバーそれ自体も含む)で定義された全てのServiceはDNS名を割り当てられます。デフォルトでは、クライアントPodのDNSサーチリストはPod自身のネームスペースと、クラスターのデフォルトドメインを含みます。
下記の例でこの仕組みを説明します。
Kubernetesのbar
というネームスペース内でfoo
という名前のServiceがあると仮定します。bar
ネームスペース内で稼働しているPodは、foo
に対してDNSクエリを実行するだけでこのServiceを探すことができます。bar
とは別のquux
ネームスペース内で稼働しているPodは、foo.bar
に対してDNSクエリを実行するだけでこのServiceを探すことができます。
下記のセクションでは、サポートされているレコードタイプとレイアウトについて詳しくまとめています。
うまく機能する他のレイアウト、名前、またはクエリーは、実装の詳細を考慮し、警告なしに変更されることがあります。
最新の仕様に関する詳細は、KubernetesにおけるDNSベースのServiceディスカバリを参照ください。
"通常の"(Headlessでない)Serviceは、my-svc.my-namespace.svc.cluster.local
という形式のDNS A(AAAA)レコードを、ServiceのIPバージョンに応じて割り当てられます。このAレコードはそのServiceのClusterIPへと名前解決されます。
"Headless"(ClusterIPなしの)Serviceもまたmy-svc.my-namespace.svc.cluster.local
という形式のDNS A(AAAA)レコードを、ServiceのIPバージョンに応じて割り当てられます。通常のServiceとは異なり、このレコードはServiceによって選択されたPodのIPの一覧へと名前解決されます。クライアントはこの一覧のIPを使うか、その一覧から標準のラウンドロビン方式によって選択されたIPを使います。
SRVレコードは、通常のServiceもしくはHeadless
Servicesの一部である名前付きポート向けに作成されます。それぞれの名前付きポートに対して、そのSRVレコードは_my-port-name._my-port-protocol.my-svc.my-namespace.svc.cluster.local
という形式となります。
通常のServiceに対しては、このSRVレコードはmy-svc.my-namespace.svc.cluster.local
という形式のドメイン名とポート番号へ名前解決します。
Headless Serviceに対しては、このSRVレコードは複数の結果を返します。それはServiceの背後にある各Podの1つを返すのと、auto-generated-name.my-svc.my-namespace.svc.cluster.local
という形式のPodのドメイン名とポート番号を含んだ結果を返します。
一般的にPodは下記のDNS解決となります。
pod-ip-address.my-namespace.pod.cluster-domain.example
例えば、default
ネームスペースのpodのIPアドレスが172.17.0.3で、クラスターのドメイン名がcluster.local
の場合、PodのDNS名は以下になります。
172-17-0-3.default.pod.cluster.local
DeploymentかDaemonSetに作成され、Serviceに公開されるどのPodも以下のDNS解決が利用できます。
pod-ip-address.deployment-name.my-namespace.svc.cluster-domain.example
現在、Podが作成されたとき、そのPodのホスト名はPodのmetadata.name
フィールドの値となります。
Pod Specは、オプションであるhostname
フィールドを持ち、Podのホスト名を指定するために使うことができます。hostname
が指定されたとき、hostname
はそのPodの名前よりも優先されます。例えば、hostname
フィールドが"my-host
"にセットされたPodを考えると、Podはそのhostnameが"my-host
"に設定されます。
Pod Specはまた、オプションであるsubdomain
フィールドも持ち、Podのサブドメイン名を指定するために使うことができます。例えば、"my-namespace
"というネームスペース内でhostname
がfoo
とセットされていて、subdomain
がbar
とセットされているPodの場合、そのPodは"foo.bar.my-namespace.svc.cluster.local
"という名前の完全修飾ドメイン名(FQDN)を持つことになります。
例:
apiVersion: v1
kind: Service
metadata:
name: default-subdomain
spec:
selector:
name: busybox
clusterIP: None
ports:
- name: foo # 実際は、portは必要ありません。
port: 1234
targetPort: 1234
---
apiVersion: v1
kind: Pod
metadata:
name: busybox1
labels:
name: busybox
spec:
hostname: busybox-1
subdomain: default-subdomain
containers:
- image: busybox:1.28
command:
- sleep
- "3600"
name: busybox
---
apiVersion: v1
kind: Pod
metadata:
name: busybox2
labels:
name: busybox
spec:
hostname: busybox-2
subdomain: default-subdomain
containers:
- image: busybox:1.28
command:
- sleep
- "3600"
name: busybox
もしそのPodと同じネームスペース内で、同じサブドメインを持ったHeadless Serviceが存在していた場合、クラスターのDNSサーバーもまた、そのPodの完全修飾ドメイン名(FQDN)に対するA(AAAA)レコードを返します。
例えば、"busybox-1
"というホスト名で、"default-subdomain
"というサブドメインを持ったPodと、そのPodと同じネームスペース内にある"default-subdomain
"という名前のHeadless Serviceがあると考えると、そのPodは自身の完全修飾ドメイン名(FQDN)を"busybox-1.default-subdomain.my-namespace.svc.cluster.local
"として扱います。DNSはそのPodのIPを指し示すA(AAAA)レコードを返します。"busybox1
"と"busybox2
"の両方のPodはそれぞれ独立したA(AAAA)レコードを持ちます。
そのエンドポイントオブジェクトはそのIPに加えてhostname
を任意のエンドポイントアドレスに対して指定できます。
hostname
はPodのA(AAAA)レコードが作成されるために必須となります。hostname
を持たないがsubdomain
を持つようなPodは、そのPodのIPアドレスを指し示すHeadless Service(default-subdomain.my-namespace.svc.cluster.local
)に対するA(AAAA)レコードのみ作成します。
Kubernetes v1.19 [alpha]
前提条件: API Serverに対してSetHostnameAsFQDN
フィーチャーゲートを有効にする必要があります。
Podが完全修飾ドメイン名(FQDN)を持つように構成されている場合、そのホスト名は短いホスト名です。
例えば、FQDNがbusybox-1.default-subdomain.my-namespace.svc.cluster-domain.example
のPodがある場合、
デフォルトではそのPod内のhostname
コマンドはbusybox-1
を返し、hostname --fqdn
コマンドはFQDNを返します。
PodのspecでsetHostnameAsFQDN: true
を設定した場合、そのPodの名前空間に対してkubeletはPodのFQDNをホスト名に書き込みます。
この場合、hostname
とhostname --fqdn
の両方がPodのFQDNを返します。
Linuxでは、カーネルのホスト名のフィールド(struct utsname
のnodename
フィールド)は64文字に制限されています。
Podがこの機能を有効にしていて、そのFQDNが64文字より長い場合、Podは起動に失敗します。
PodはPending
ステータス(kubectl
でみられるContainerCreating
)のままになり、「Podのホスト名とクラスタードメインからFQDNを作成できなかった」や、「FQDNlong-FQDN
が長すぎる(64文字が最大, 70文字が要求された)」などのエラーイベントが生成されます。
このシナリオのユーザー体験を向上させる1つの方法は、admission webhook controllerを作成して、ユーザーがDeploymentなどのトップレベルのオブジェクトを作成するときにFQDNのサイズを制御することです。
DNSポリシーはPod毎に設定できます。現在のKubernetesでは次のようなPod固有のDNSポリシーをサポートしています。これらのポリシーはPod SpecのdnsPolicy
フィールドで指定されます。
Default
": そのPodはPodが稼働しているNodeから名前解決の設定を継承します。詳細に関しては、関連する議論を参照してください。ClusterFirst
": "www.kubernetes.io
"のようなクラスタードメインのサフィックスにマッチしないようなDNSクエリーは、Nodeから継承された上流のネームサーバーにフォワーディングされます。クラスター管理者は、追加のstubドメインと上流のDNSサーバーを設定できます。このような場合におけるDNSクエリー処理の詳細に関しては、関連する議論を参照してください。ClusterFirstWithHostNet
": hostNetworkによって稼働しているPodに対しては、ユーザーは明示的にDNSポリシーを"ClusterFirstWithHostNet
"とセットするべきです。None
": この設定では、Kubernetesの環境からDNS設定を無視することができます。全てのDNS設定は、Pod Spec内のdnsConfig
フィールドを指定して提供することになっています。下記のセクションのPod's DNS configを参照ください。dnsPolicy
が明示的に指定されていない場合、"ClusterFirst"が使用されます。
下記の例では、hostNetwork
フィールドがtrue
にセットされているため、dnsPolicy
が"ClusterFirstWithHostNet
"とセットされているPodを示します。
apiVersion: v1
kind: Pod
metadata:
name: busybox
namespace: default
spec:
containers:
- image: busybox:1.28
command:
- sleep
- "3600"
imagePullPolicy: IfNotPresent
name: busybox
restartPolicy: Always
hostNetwork: true
dnsPolicy: ClusterFirstWithHostNet
PodのDNS設定は、ユーザーがPodに対してそのDNS設定上でさらに制御するための手段を提供します。
dnsConfig
フィールドはオプションで、どのような設定のdnsPolicy
でも共に機能することができます。しかし、PodのdnsPolicy
が"None
"にセットされていたとき、dnsConfig
フィールドは必ず指定されなくてはなりません。
下記の項目は、ユーザーがdnsConfig
フィールドに指定可能なプロパティーとなります。
nameservers
: そのPodに対するDNSサーバーとして使われるIPアドレスのリストです。これは最大で3つのIPアドレスを指定することができます。PodのdnsPolicy
が"None
"に指定されていたとき、そのリストは最低1つのIPアドレスを指定しなければならず、もし指定されていなければ、それ以外のdnsPolicy
の値の場合は、このプロパティーはオプションとなります。searches
: Pod内のホスト名のルックアップのためのDNSサーチドメインのリストです。このプロパティーはオプションです。指定されていたとき、このリストは選択されたDNSポリシーから生成されたサーチドメイン名のベースとなるリストにマージされます。重複されているドメイン名は削除されます。Kubernetesでは最大6つのサーチドメインの設定を許可しています。options
: name
プロパティー(必須)とvalue
プロパティー(オプション)を持つような各オプジェクトのリストで、これはオプションです。このプロパティー内の内容は指定されたDNSポリシーから生成されたオプションにマージされます。重複されたエントリーは削除されます。下記のファイルはカスタムDNS設定を持ったPodの例です。
apiVersion: v1
kind: Pod
metadata:
namespace: default
name: dns-example
spec:
containers:
- name: test
image: nginx
dnsPolicy: "None"
dnsConfig:
nameservers:
- 1.2.3.4
searches:
- ns1.svc.cluster.local
- my.dns.search.suffix
options:
- name: ndots
value: "2"
- name: edns0
上記のPodが作成されたとき、test
コンテナは、コンテナ内の/etc/resolv.conf
ファイル内にある下記の内容を取得します。
nameserver 1.2.3.4
search ns1.svc.cluster.local my.dns.search.suffix
options ndots:2 edns0
IPv6用のセットアップのためには、サーチパスとname serverは下記のようにセットアップするべきです。
$ kubectl exec -it dns-example -- cat /etc/resolv.conf
nameserver 2001:db8:30::a
search default.svc.cluster.local svc.cluster.local cluster.local
options ndots:5
PodのDNS設定と"None
"というDNSポリシーの利用可能なバージョンに関しては下記の通りです。
k8s version | Feature support |
---|---|
1.14 | ステーブル |
1.10 | β版 (デフォルトで有効) |
1.9 | α版 |
DNS設定の管理方法に関しては、DNS Serviceの設定 を確認してください。
継続的に実行され、複製されたアプリケーションの準備ができたので、ネットワーク上で公開することが可能になります。 Kubernetesのネットワークのアプローチについて説明する前に、Dockerの「通常の」ネットワーク手法と比較することが重要です。
デフォルトでは、Dockerはホストプライベートネットワーキングを使用するため、コンテナは同じマシン上にある場合にのみ他のコンテナと通信できます。 Dockerコンテナがノード間で通信するには、マシンのIPアドレスにポートを割り当ててから、コンテナに転送またはプロキシする必要があります。 これは明らかに、コンテナが使用するポートを非常に慎重に調整するか、ポートを動的に割り当てる必要があることを意味します。
コンテナを提供する複数の開発者やチーム間でポートの割り当てを調整することは、規模的に大変困難であり、ユーザが制御できないクラスターレベルの問題にさらされます。 Kubernetesでは、どのホストで稼働するかに関わらず、Podが他のPodと通信できると想定しています。 すべてのPodに独自のクラスタープライベートIPアドレスを付与するため、Pod間のリンクを明示的に作成したり、コンテナポートをホストポートにマップしたりする必要はありません。 これは、Pod内のコンテナがすべてlocalhostの相互のポートに到達でき、クラスター内のすべてのPodがNATなしで相互に認識できることを意味します。 このドキュメントの残りの部分では、このようなネットワークモデルで信頼できるサービスを実行する方法について詳しく説明します。
このガイドでは、シンプルなnginxサーバーを使用して概念実証を示します。
前の例でネットワークモデルを紹介しましたが、再度ネットワークの観点に焦点を当てましょう。 nginx Podを作成し、コンテナポートの仕様を指定していることに注意してください。
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-nginx
spec:
selector:
matchLabels:
run: my-nginx
replicas: 2
template:
metadata:
labels:
run: my-nginx
spec:
containers:
- name: my-nginx
image: nginx
ports:
- containerPort: 80
これにより、クラスター内のどのノードからでもアクセスできるようになります。 Podが実行されているノードを確認します:
kubectl apply -f ./run-my-nginx.yaml
kubectl get pods -l run=my-nginx -o wide
NAME READY STATUS RESTARTS AGE IP NODE
my-nginx-3800858182-jr4a2 1/1 Running 0 13s 10.244.3.4 kubernetes-minion-905m
my-nginx-3800858182-kna2y 1/1 Running 0 13s 10.244.2.5 kubernetes-minion-ljyd
PodのIPを確認します:
kubectl get pods -l run=my-nginx -o yaml | grep podIP
podIP: 10.244.3.4
podIP: 10.244.2.5
クラスター内の任意のノードにSSH接続し、両方のIPにcurl接続できるはずです。 コンテナはノードでポート80を使用していないことに注意してください。 また、Podにトラフィックをルーティングする特別なNATルールもありません。 つまり、同じcontainerPortを使用して同じノードで複数のnginx Podを実行し、IPを使用してクラスター内の他のPodやノードからそれらにアクセスできます。 Dockerと同様に、ポートは引き続きホストノードのインターフェイスに公開できますが、ネットワークモデルにより、この必要性は根本的に減少します。
興味があれば、これをどのように達成するかについて詳しく読むことができます。
そのため、フラットでクラスター全体のアドレス空間でnginxを実行するPodがあります。 理論的には、これらのPodと直接通信することができますが、ノードが停止するとどうなりますか? Podはそれで死に、Deploymentは異なるIPを持つ新しいものを作成します。 これは、Serviceが解決する問題です。
Kubernetes Serviceは、クラスター内のどこかで実行されるPodの論理セットを定義する抽象化であり、すべて同じ機能を提供します。 作成されると、各Serviceには一意のIPアドレス(clusterIPとも呼ばれます)が割り当てられます。 このアドレスはServiceの有効期間に関連付けられており、Serviceが動作している間は変更されません。 Podは、Serviceと通信するように構成でき、Serviceへの通信は、ServiceのメンバーであるPodに自動的に負荷分散されることを認識できます。
2つのnginxレプリカのサービスをkubectl expose
で作成できます:
kubectl expose deployment/my-nginx
service/my-nginx exposed
これは次のyamlをkubectl apply -f
することと同等です:
apiVersion: v1
kind: Service
metadata:
name: my-nginx
labels:
run: my-nginx
spec:
ports:
- port: 80
protocol: TCP
selector:
run: my-nginx
この仕様は、run:my-nginx
ラベルを持つ任意のPodのTCPポート80をターゲットとするサービスを作成し、抽象化されたサービスポートでPodを公開します(targetPort
:はコンテナがトラフィックを受信するポート、port
:は抽象化されたServiceのポートであり、他のPodがServiceへのアクセスに使用する任意のポートにすることができます)。
サービス定義でサポートされているフィールドのリストはService APIオブジェクトを参照してください。
Serviceを確認します:
kubectl get svc my-nginx
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
my-nginx ClusterIP 10.0.162.149 <none> 80/TCP 21s
前述のように、ServiceはPodのグループによってサポートされています。
これらのPodはエンドポイントを通じて公開されます。
Serviceのセレクターは継続的に評価され、結果はmy-nginx
という名前のEndpointsオブジェクトにPOSTされます。
Podが終了すると、エンドポイントから自動的に削除され、Serviceのセレクターに一致する新しいPodが自動的にエンドポイントに追加されます。
エンドポイントを確認し、IPが最初のステップで作成されたPodと同じであることを確認します:
kubectl describe svc my-nginx
Name: my-nginx
Namespace: default
Labels: run=my-nginx
Annotations: <none>
Selector: run=my-nginx
Type: ClusterIP
IP: 10.0.162.149
Port: <unset> 80/TCP
Endpoints: 10.244.2.5:80,10.244.3.4:80
Session Affinity: None
Events: <none>
kubectl get ep my-nginx
NAME ENDPOINTS AGE
my-nginx 10.244.2.5:80,10.244.3.4:80 1m
クラスター内の任意のノードから、<CLUSTER-IP>:<PORT>
でnginx Serviceにcurl接続できるようになりました。
Service IPは完全に仮想的なもので、ホスト側のネットワークには接続できないことに注意してください。
この仕組みに興味がある場合は、サービスプロキシーの詳細をお読みください。
Kubernetesは、環境変数とDNSの2つの主要なService検索モードをサポートしています。 前者はそのまま使用でき、後者はCoreDNSクラスタアドオンを必要とします。
enableServiceLinks
フラグをfalse
に設定することでこのモードを無効にできます。
ノードでPodが実行されると、kubeletはアクティブな各サービスの環境変数のセットを追加します。 これにより、順序付けの問題が発生します。 理由を確認するには、実行中のnginx Podの環境を調べます(Pod名は環境によって異なります):
kubectl exec my-nginx-3800858182-jr4a2 -- printenv | grep SERVICE
KUBERNETES_SERVICE_HOST=10.0.0.1
KUBERNETES_SERVICE_PORT=443
KUBERNETES_SERVICE_PORT_HTTPS=443
サービスに言及がないことに注意してください。これは、サービスの前にレプリカを作成したためです。 これのもう1つの欠点は、スケジューラーが両方のPodを同じマシンに配置し、サービスが停止した場合にサービス全体がダウンする可能性があることです。 2つのPodを強制終了し、Deploymentがそれらを再作成するのを待つことで、これを正しい方法で実行できます。 今回は、サービスはレプリカの「前」に存在します。 これにより、スケジューラーレベルのサービスがPodに広がり(すべてのノードの容量が等しい場合)、適切な環境変数が提供されます:
kubectl scale deployment my-nginx --replicas=0; kubectl scale deployment my-nginx --replicas=2;
kubectl get pods -l run=my-nginx -o wide
NAME READY STATUS RESTARTS AGE IP NODE
my-nginx-3800858182-e9ihh 1/1 Running 0 5s 10.244.2.7 kubernetes-minion-ljyd
my-nginx-3800858182-j4rm4 1/1 Running 0 5s 10.244.3.8 kubernetes-minion-905m
Podは強制終了されて再作成されるため、異なる名前が付いていることに気付くでしょう。
kubectl exec my-nginx-3800858182-e9ihh -- printenv | grep SERVICE
KUBERNETES_SERVICE_PORT=443
MY_NGINX_SERVICE_HOST=10.0.162.149
KUBERNETES_SERVICE_HOST=10.0.0.1
MY_NGINX_SERVICE_PORT=80
KUBERNETES_SERVICE_PORT_HTTPS=443
Kubernetesは、DNS名を他のServiceに自動的に割り当てるDNSクラスターアドオンサービスを提供します。 クラスターで実行されているかどうかを確認できます:
kubectl get services kube-dns --namespace=kube-system
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kube-dns ClusterIP 10.0.0.10 <none> 53/UDP,53/TCP 8m
このセクションの残りの部分は、寿命の長いIP(my-nginx)を持つServiceと、そのIPに名前を割り当てたDNSサーバーがあることを前提にしています。ここではCoreDNSクラスターアドオン(アプリケーション名: kube-dns
)を使用しているため、標準的なメソッド(gethostbyname()
など) を使用してクラスター内の任意のPodからServiceに通信できます。CoreDNSが起動していない場合、CoreDNS READMEまたはInstalling CoreDNSを参照し、有効にする事ができます。curlアプリケーションを実行して、これをテストしてみましょう。
kubectl run curl --image=radial/busyboxplus:curl -i --tty
Waiting for pod default/curl-131556218-9fnch to be running, status is Pending, pod ready: false
Hit enter for command prompt
次に、Enterキーを押してnslookup my-nginx
を実行します:
[ root@curl-131556218-9fnch:/ ]$ nslookup my-nginx
Server: 10.0.0.10
Address 1: 10.0.0.10
Name: my-nginx
Address 1: 10.0.162.149
これまでは、クラスター内からnginxサーバーにアクセスしただけでした。 サービスをインターネットに公開する前に、通信チャネルが安全であることを確認する必要があります。 これには、次のものが必要です:
これらはすべてnginx httpsの例から取得できます。 これにはツールをインストールする必要があります。 これらをインストールしたくない場合は、後で手動の手順に従ってください。つまり:
make keys KEY=/tmp/nginx.key CERT=/tmp/nginx.crt
kubectl create secret tls nginxsecret --key /tmp/nginx.key --cert /tmp/nginx.crt
secret/nginxsecret created
kubectl get secrets
NAME TYPE DATA AGE
default-token-il9rc kubernetes.io/service-account-token 1 1d
nginxsecret kubernetes.io/tls 2 1m
configmapも作成します:
kubectl create configmap nginxconfigmap --from-file=default.conf
configmap/nginxconfigmap created
kubectl get configmaps
NAME DATA AGE
nginxconfigmap 1 114s
以下は、(Windows上など)makeの実行で問題が発生した場合に実行する手動の手順です:
# 公開秘密鍵ペアを作成します
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /d/tmp/nginx.key -out /d/tmp/nginx.crt -subj "/CN=my-nginx/O=my-nginx"
# キーをbase64エンコードに変換します
cat /d/tmp/nginx.crt | base64
cat /d/tmp/nginx.key | base64
前のコマンドの出力を使用して、次のようにyamlファイルを作成します。 base64でエンコードされた値はすべて1行である必要があります。
apiVersion: "v1"
kind: "Secret"
metadata:
name: "nginxsecret"
namespace: "default"
type: kubernetes.io/tls
data:
nginx.crt: "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURIekNDQWdlZ0F3SUJBZ0lKQUp5M3lQK0pzMlpJTUEwR0NTcUdTSWIzRFFFQkJRVUFNQ1l4RVRBUEJnTlYKQkFNVENHNW5hVzU0YzNaak1SRXdEd1lEVlFRS0V3aHVaMmx1ZUhOMll6QWVGdzB4TnpFd01qWXdOekEzTVRKYQpGdzB4T0RFd01qWXdOekEzTVRKYU1DWXhFVEFQQmdOVkJBTVRDRzVuYVc1NGMzWmpNUkV3RHdZRFZRUUtFd2h1CloybHVlSE4yWXpDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBSjFxSU1SOVdWM0IKMlZIQlRMRmtobDRONXljMEJxYUhIQktMSnJMcy8vdzZhU3hRS29GbHlJSU94NGUrMlN5ajBFcndCLzlYTnBwbQppeW1CL3JkRldkOXg5UWhBQUxCZkVaTmNiV3NsTVFVcnhBZW50VWt1dk1vLzgvMHRpbGhjc3paenJEYVJ4NEo5Ci82UVRtVVI3a0ZTWUpOWTVQZkR3cGc3dlVvaDZmZ1Voam92VG42eHNVR0M2QURVODBpNXFlZWhNeVI1N2lmU2YKNHZpaXdIY3hnL3lZR1JBRS9mRTRqakxCdmdONjc2SU90S01rZXV3R0ljNDFhd05tNnNTSzRqYUNGeGpYSnZaZQp2by9kTlEybHhHWCtKT2l3SEhXbXNhdGp4WTRaNVk3R1ZoK0QrWnYvcW1mMFgvbVY0Rmo1NzV3ajFMWVBocWtsCmdhSXZYRyt4U1FVQ0F3RUFBYU5RTUU0d0hRWURWUjBPQkJZRUZPNG9OWkI3YXc1OUlsYkROMzhIYkduYnhFVjcKTUI4R0ExVWRJd1FZTUJhQUZPNG9OWkI3YXc1OUlsYkROMzhIYkduYnhFVjdNQXdHQTFVZEV3UUZNQU1CQWY4dwpEUVlKS29aSWh2Y05BUUVGQlFBRGdnRUJBRVhTMW9FU0lFaXdyMDhWcVA0K2NwTHI3TW5FMTducDBvMm14alFvCjRGb0RvRjdRZnZqeE04Tzd2TjB0clcxb2pGSW0vWDE4ZnZaL3k4ZzVaWG40Vm8zc3hKVmRBcStNZC9jTStzUGEKNmJjTkNUekZqeFpUV0UrKzE5NS9zb2dmOUZ3VDVDK3U2Q3B5N0M3MTZvUXRUakViV05VdEt4cXI0Nk1OZWNCMApwRFhWZmdWQTRadkR4NFo3S2RiZDY5eXM3OVFHYmg5ZW1PZ05NZFlsSUswSGt0ejF5WU4vbVpmK3FqTkJqbWZjCkNnMnlwbGQ0Wi8rUUNQZjl3SkoybFIrY2FnT0R4elBWcGxNSEcybzgvTHFDdnh6elZPUDUxeXdLZEtxaUMwSVEKQ0I5T2wwWW5scE9UNEh1b2hSUzBPOStlMm9KdFZsNUIyczRpbDlhZ3RTVXFxUlU9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K"
nginx.key: "LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRQ2RhaURFZlZsZHdkbFIKd1V5eFpJWmVEZWNuTkFhbWh4d1NpeWF5N1AvOE9ta3NVQ3FCWmNpQ0RzZUh2dGtzbzlCSzhBZi9WemFhWm9zcApnZjYzUlZuZmNmVUlRQUN3WHhHVFhHMXJKVEVGSzhRSHA3VkpMcnpLUC9QOUxZcFlYTE0yYzZ3MmtjZUNmZitrCkU1bEVlNUJVbUNUV09UM3c4S1lPNzFLSWVuNEZJWTZMMDUrc2JGQmd1Z0ExUE5JdWFubm9UTWtlZTRuMG4rTDQKb3NCM01ZUDhtQmtRQlAzeE9JNHl3YjREZXUraURyU2pKSHJzQmlIT05Xc0RadXJFaXVJMmdoY1kxeWIyWHI2UAozVFVOcGNSbC9pVG9zQngxcHJHclk4V09HZVdPeGxZZmcvbWIvNnBuOUYvNWxlQlkrZStjSTlTMkQ0YXBKWUdpCkwxeHZzVWtGQWdNQkFBRUNnZ0VBZFhCK0xkbk8ySElOTGo5bWRsb25IUGlHWWVzZ294RGQwci9hQ1Zkank4dlEKTjIwL3FQWkUxek1yall6Ry9kVGhTMmMwc0QxaTBXSjdwR1lGb0xtdXlWTjltY0FXUTM5SjM0VHZaU2FFSWZWNgo5TE1jUHhNTmFsNjRLMFRVbUFQZytGam9QSFlhUUxLOERLOUtnNXNrSE5pOWNzMlY5ckd6VWlVZWtBL0RBUlBTClI3L2ZjUFBacDRuRWVBZmI3WTk1R1llb1p5V21SU3VKdlNyblBESGtUdW1vVlVWdkxMRHRzaG9reUxiTWVtN3oKMmJzVmpwSW1GTHJqbGtmQXlpNHg0WjJrV3YyMFRrdWtsZU1jaVlMbjk4QWxiRi9DSmRLM3QraTRoMTVlR2ZQegpoTnh3bk9QdlVTaDR2Q0o3c2Q5TmtEUGJvS2JneVVHOXBYamZhRGR2UVFLQmdRRFFLM01nUkhkQ1pKNVFqZWFKClFGdXF4cHdnNzhZTjQyL1NwenlUYmtGcVFoQWtyczJxWGx1MDZBRzhrZzIzQkswaHkzaE9zSGgxcXRVK3NHZVAKOWRERHBsUWV0ODZsY2FlR3hoc0V0L1R6cEdtNGFKSm5oNzVVaTVGZk9QTDhPTm1FZ3MxMVRhUldhNzZxelRyMgphRlpjQ2pWV1g0YnRSTHVwSkgrMjZnY0FhUUtCZ1FEQmxVSUUzTnNVOFBBZEYvL25sQVB5VWs1T3lDdWc3dmVyClUycXlrdXFzYnBkSi9hODViT1JhM05IVmpVM25uRGpHVHBWaE9JeXg5TEFrc2RwZEFjVmxvcG9HODhXYk9lMTAKMUdqbnkySmdDK3JVWUZiRGtpUGx1K09IYnRnOXFYcGJMSHBzUVpsMGhucDBYSFNYVm9CMUliQndnMGEyOFVadApCbFBtWmc2d1BRS0JnRHVIUVV2SDZHYTNDVUsxNFdmOFhIcFFnMU16M2VvWTBPQm5iSDRvZUZKZmcraEppSXlnCm9RN3hqWldVR3BIc3AyblRtcHErQWlSNzdyRVhsdlhtOElVU2FsbkNiRGlKY01Pc29RdFBZNS9NczJMRm5LQTQKaENmL0pWb2FtZm1nZEN0ZGtFMXNINE9MR2lJVHdEbTRpb0dWZGIwMllnbzFyb2htNUpLMUI3MkpBb0dBUW01UQpHNDhXOTVhL0w1eSt5dCsyZ3YvUHM2VnBvMjZlTzRNQ3lJazJVem9ZWE9IYnNkODJkaC8xT2sybGdHZlI2K3VuCnc1YytZUXRSTHlhQmd3MUtpbGhFZDBKTWU3cGpUSVpnQWJ0LzVPbnlDak9OVXN2aDJjS2lrQ1Z2dTZsZlBjNkQKckliT2ZIaHhxV0RZK2Q1TGN1YSt2NzJ0RkxhenJsSlBsRzlOZHhrQ2dZRUF5elIzT3UyMDNRVVV6bUlCRkwzZAp4Wm5XZ0JLSEo3TnNxcGFWb2RjL0d5aGVycjFDZzE2MmJaSjJDV2RsZkI0VEdtUjZZdmxTZEFOOFRwUWhFbUtKCnFBLzVzdHdxNWd0WGVLOVJmMWxXK29xNThRNTBxMmk1NVdUTThoSDZhTjlaMTltZ0FGdE5VdGNqQUx2dFYxdEYKWSs4WFJkSHJaRnBIWll2NWkwVW1VbGc9Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K"
ファイルを使用してSecretを作成します:
kubectl apply -f nginxsecrets.yaml
kubectl get secrets
NAME TYPE DATA AGE
default-token-il9rc kubernetes.io/service-account-token 1 1d
nginxsecret kubernetes.io/tls 2 1m
次に、nginxレプリカを変更して、シークレットの証明書とServiceを使用してhttpsサーバーを起動し、両方のポート(80と443)を公開します:
apiVersion: v1
kind: Service
metadata:
name: my-nginx
labels:
run: my-nginx
spec:
type: NodePort
ports:
- port: 8080
targetPort: 80
protocol: TCP
name: http
- port: 443
protocol: TCP
name: https
selector:
run: my-nginx
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-nginx
spec:
selector:
matchLabels:
run: my-nginx
replicas: 1
template:
metadata:
labels:
run: my-nginx
spec:
volumes:
- name: secret-volume
secret:
secretName: nginxsecret
containers:
- name: nginxhttps
image: bprashanth/nginxhttps:1.0
ports:
- containerPort: 443
- containerPort: 80
volumeMounts:
- mountPath: /etc/nginx/ssl
name: secret-volume
nginx-secure-appマニフェストに関する注目すべき点:
/etc/nginx/ssl
にマウントされたボリュームを介してキーにアクセスできます。
これは、nginxサーバーが起動する前にセットアップされます。kubectl delete deployments,svc my-nginx; kubectl create -f ./nginx-secure-app.yaml
この時点で、任意のノードからnginxサーバーに到達できます。
kubectl get pods -l run=my-nginx -o custom-columns=POD_IP:.status.podIPs
POD_IP
[map[ip:10.244.3.5]]
node $ curl -k https://10.244.3.5
...
<h1>Welcome to nginx!</h1>
最後の手順でcurlに-k
パラメーターを指定したことに注意してください。
これは、証明書の生成時にnginxを実行しているPodについて何も知らないためです。
CNameの不一致を無視するようcurlに指示する必要があります。
Serviceを作成することにより、証明書で使用されるCNameを、Service検索中にPodで使用される実際のDNS名にリンクしました。
これをPodからテストしましょう(簡単にするために同じシークレットを再利用しています。PodはServiceにアクセスするためにnginx.crtのみを必要とします):
apiVersion: apps/v1
kind: Deployment
metadata:
name: curl-deployment
spec:
selector:
matchLabels:
app: curlpod
replicas: 1
template:
metadata:
labels:
app: curlpod
spec:
volumes:
- name: secret-volume
secret:
secretName: nginxsecret
containers:
- name: curlpod
command:
- sh
- -c
- while true; do sleep 1; done
image: radial/busyboxplus:curl
volumeMounts:
- mountPath: /etc/nginx/ssl
name: secret-volume
kubectl apply -f ./curlpod.yaml
kubectl get pods -l app=curlpod
NAME READY STATUS RESTARTS AGE
curl-deployment-1515033274-1410r 1/1 Running 0 1m
kubectl exec curl-deployment-1515033274-1410r -- curl https://my-nginx --cacert /etc/nginx/ssl/tls.crt
...
<title>Welcome to nginx!</title>
...
アプリケーションの一部では、Serviceを外部IPアドレスに公開したい場合があります。
Kubernetesは、NodePortとLoadBalancerの2つの方法をサポートしています。
前のセクションで作成したServiceはすでにNodePort
を使用しているため、ノードにパブリックIPがあれば、nginx HTTPSレプリカはインターネット上のトラフィックを処理する準備ができています。
kubectl get svc my-nginx -o yaml | grep nodePort -C 5
uid: 07191fb3-f61a-11e5-8ae5-42010af00002
spec:
clusterIP: 10.0.162.149
ports:
- name: http
nodePort: 31704
port: 8080
protocol: TCP
targetPort: 80
- name: https
nodePort: 32453
port: 443
protocol: TCP
targetPort: 443
selector:
run: my-nginx
kubectl get nodes -o yaml | grep ExternalIP -C 1
- address: 104.197.41.11
type: ExternalIP
allocatable:
--
- address: 23.251.152.56
type: ExternalIP
allocatable:
...
$ curl https://<EXTERNAL-IP>:<NODE-PORT> -k
...
<h1>Welcome to nginx!</h1>
クラウドロードバランサーを使用するようにサービスを再作成しましょう。
my-nginx
サービスのType
をNodePort
からLoadBalancer
に変更するだけです:
kubectl edit svc my-nginx
kubectl get svc my-nginx
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
my-nginx LoadBalancer 10.0.162.149 xx.xxx.xxx.xxx 8080:30163/TCP 21s
curl https://<EXTERNAL-IP> -k
...
<title>Welcome to nginx!</title>
EXTERNAL-IP
列のIPアドレスは、パブリックインターネットで利用可能なものです。
CLUSTER-IP
は、クラスター/プライベートクラウドネットワーク内でのみ使用できます。
AWSでは、type LoadBalancer
はIPではなく(長い)ホスト名を使用するELBが作成されます。
実際、標準のkubectl get svc
の出力に収まるには長すぎるので、それを確認するにはkubectl describe service my-nginx
を実行する必要があります。
次のようなものが表示されます:
kubectl describe service my-nginx
...
LoadBalancer Ingress: a320587ffd19711e5a37606cf4a74574-1142138393.us-east-1.elb.amazonaws.com
...
Kubernetes v1.17 [beta]
EndpointSliceは、Kubernetesクラスター内にあるネットワークエンドポイントを追跡するための単純な手段を提供します。EndpointSliceは、よりスケーラブルでより拡張可能な、Endpointの代わりとなるものです。
Endpoint APIはKubernetes内のネットワークエンドポイントを追跡する単純で直観的な手段を提供してきました。 残念ながら、KubernetesクラスターやServiceが大規模になり、より多くのトラフィックを処理し、より多くのバックエンドPodに送信するようになるにしたがって、Endpoint APIの限界が明らかになってきました。 最も顕著な問題の1つに、ネットワークエンドポイントの数が大きくなったときのスケーリングの問題があります。
Serviceのすべてのネットワークエンドポイントが単一のEndpointリソースに格納されていたため、リソースのサイズが非常に大きくなる場合がありました。これがKubernetesのコンポーネント(特に、マスターコントロールプレーン)の性能に悪影響を与え、結果として、Endpointに変更があるたびに、大量のネットワークトラフィックと処理が発生するようになってしまいました。EndpointSliceは、この問題を緩和するとともに、トポロジカルルーティングなどの追加機能のための拡張可能なプラットフォームを提供します。
Kubernetes内ではEndpointSliceにはネットワークエンドポイントの集合へのリファレンスが含まれます。 コントロールプレーンは、セレクターが指定されているKubernetes ServiceのEndpointSliceを自動的に作成します。 これらのEndpointSliceには、Serviceセレクターに一致するすべてのPodへのリファレンスが含まれています。 EndpointSliceは、プロトコル、ポート番号、およびサービス名の一意の組み合わせによってネットワークエンドポイントをグループ化します。 EndpointSliceオブジェクトの名前は有効なDNSサブドメイン名である必要があります。
一例として、以下にexample
というKubernetes Serviceに対するサンプルのEndpointSliceリソースを示します。
apiVersion: discovery.k8s.io/v1beta1
kind: EndpointSlice
metadata:
name: example-abc
labels:
kubernetes.io/service-name: example
addressType: IPv4
ports:
- name: http
protocol: TCP
port: 80
endpoints:
- addresses:
- "10.1.2.3"
conditions:
ready: true
hostname: pod-1
topology:
kubernetes.io/hostname: node-1
topology.kubernetes.io/zone: us-west2-a
デフォルトでは、コントロールプレーンはEndpointSliceを作成・管理し、それぞれのエンドポイント数が100以下になるようにします。--max-endpoints-per-slice
kube-controller-managerフラグを設定することで、最大1000個まで設定可能です。
EndpointSliceは内部トラフィックのルーティング方法に関して、kube-proxyに対する唯一のソース(source of truth)として振る舞うことができます。EndpointSliceを有効にすれば、非常に多数のエンドポイントを持つServiceに対して性能向上が得られるはずです。
EndpointSliceは次の3種類のアドレスをサポートします。
EndpointSliceに属する各エンドポイントは、関連するトポロジーの情報を持つことができます。この情報は、エンドポイントの場所を示すために使われ、対応するNode、ゾーン、リージョンに関する情報が含まれます。 値が利用できる場合には、コントロールプレーンはEndpointSliceコントローラーに次のようなTopologyラベルを設定します。
kubernetes.io/hostname
- このエンドポイントが存在するNodeの名前。topology.kubernetes.io/zone
- このエンドポイントが存在するゾーン。topology.kubernetes.io/region
- このエンドポイントが存在するリージョン。これらのラベルの値はスライス内の各エンドポイントと関連するリソースから継承したものです。hostnameラベルは対応するPod上のNodeNameフィールドの値を表します。zoneとregionラベルは対応するNode上の同じ名前のラベルの値を表します。
ほとんどの場合、コントロールプレーン(具体的には、EndpointSlice コントローラー)は、EndpointSliceオブジェクトを作成および管理します。EndpointSliceには、サービスメッシュの実装など、他のさまざまなユースケースがあり、他のエンティティまたはコントローラーがEndpointSliceの追加セットを管理する可能性があります。
複数のエンティティが互いに干渉することなくEndpointSliceを管理できるようにするために、KubernetesはEndpointSliceを管理するエンティティを示すendpointslice.kubernetes.io/managed-by
というラベルを定義します。
EndpointSliceを管理するその他のエンティティも同様に、このラベルにユニークな値を設定する必要があります。
ほとんどのユースケースでは、EndpointSliceはエンドポイントスライスオブジェクトがエンドポイントを追跡するServiceによって所有されます。
これは、各EndpointSlice上のownerリファレンスとkubernetes.io/service-name
ラベルによって示されます。これにより、Serviceに属するすべてのEndpointSliceを簡単に検索できるようになっています。
場合によっては、アプリケーションはカスタムEndpointリソースを作成します。これらのアプリケーションがEndpointリソースとEndpointSliceリソースの両方に同時に書き込む必要がないようにするために、クラスターのコントロールプレーンは、ほとんどのEndpointリソースを対応するEndpointSliceにミラーリングします。
コントロールプレーンは、次の場合を除いて、Endpointリソースをミラーリングします。
endpointslice.kubernetes.io/skip-mirror
ラベルがtrue
に設定されています。control-plane.alpha.kubernetes.io/leader
アノテーションを持っています。個々のEndpointリソースは、複数のEndpointSliceに変換される場合があります。これは、Endpointリソースに複数のサブセットがある場合、または複数のIPファミリ(IPv4およびIPv6)を持つエンドポイントが含まれている場合に発生します。サブセットごとに最大1000個のアドレスがEndpointSliceにミラーリングされます。
それぞれのEndpointSliceにはポートの集合があり、リソース内のすべてのエンドポイントに適用されます。サービスが名前付きポートを使用した場合、Podが同じ名前のポートに対して、結果的に異なるターゲットポート番号が使用されて、異なるEndpointSliceが必要になる場合があります。これはサービスの部分集合がEndpointにグループ化される場合と同様です。
コントロールプレーンはEndpointSliceをできる限り充填しようとしますが、積極的にリバランスを行うことはありません。コントローラーのロジックは極めて単純で、以下のようになっています。
ここで重要なのは、3番目のステップでEndpointSliceを完全に分散させることよりも、EndpointSliceの更新を制限することを優先していることです。たとえば、もし新しい追加するべきエンドポイントが10個あり、2つのEndpointSliceにそれぞれ5個の空きがあった場合、このアプローチでは2つの既存のEndpointSliceを充填する代わりに、新しいEndpointSliceが作られます。言い換えれば、1つのEndpointSliceを作成する方が複数のEndpointSliceを更新するよりも好ましいということです。
各Node上で実行されているkube-proxyはEndpointSliceを監視しており、EndpointSliceに加えられた変更はクラスター内のすべてのNodeに送信されるため、比較的コストの高い処理になります。先ほどのアプローチは、たとえ複数のEndpointSliceが充填されない結果となるとしても、すべてのNodeへ送信しなければならない変更の数を抑制することを目的としています。
現実的には、こうしたあまり理想的ではない分散が発生することは稀です。EndpointSliceコントローラーによって処理されるほとんどの変更は、既存のEndpointSliceに収まるほど十分小さくなるためです。そうでなかったとしても、すぐに新しいEndpointSliceが必要になる可能性が高いです。また、Deploymentのローリングアップデートが行われれば、自然な再充填が行われます。Podとそれに対応するエンドポイントがすべて置換されるためです。
EndpointSliceの変更の性質上、エンドポイントは同時に複数のEndpointSliceで表される場合があります。
これは、さまざまなEndpointSliceオブジェクトへの変更が、さまざまな時間にKubernetesクライアントのウォッチ/キャッシュに到達する可能性があるために自然に発生します。
EndpointSliceを使用する実装では、エンドポイントを複数のスライスに表示できる必要があります。
エンドポイント重複排除を実行する方法のリファレンス実装は、kube-proxy
のEndpointSliceCache
実装にあります。
Kubernetes v1.19 [stable]
クラスター内のServiceに対する外部からのアクセス(主にHTTP)を管理するAPIオブジェクトです。
Ingressは負荷分散、SSL終端、名前ベースの仮想ホスティングの機能を提供します。
簡単のために、このガイドでは次の用語を定義します。
Ingressはクラスター外からクラスター内ServiceへのHTTPとHTTPSのルートを公開します。トラフィックのルーティングはIngressリソース上で定義されるルールによって制御されます。
全てのトラフィックを単一のServiceに送る単純なIngressの例を示します。
IngressはServiceに対して、外部疎通できるURL、負荷分散トラフィック、SSL/TLS終端の機能や、名前ベースの仮想ホスティングを提供するように設定できます。Ingressコントローラーは通常はロードバランサーを使用してIngressの機能を実現しますが、エッジルーターや、追加のフロントエンドを構成してトラフィックの処理を支援することもできます。
Ingressは任意のポートやプロトコルを公開しません。HTTPやHTTPS以外のServiceをインターネットに公開する場合、Service.Type=NodePortやService.Type=LoadBalancerのServiceタイプを一般的には使用します。
Ingressを提供するためにはIngressコントローラーが必要です。Ingressリソースを作成するのみでは何の効果もありません。
ingress-nginxのようなIngressコントローラーのデプロイが必要な場合があります。いくつかのIngressコントローラーの中から選択してください。
理想的には、全てのIngressコントローラーはリファレンスの仕様を満たすはずです。しかし実際には、各Ingressコントローラーは微妙に異なる動作をします。
Ingressリソースの最小構成の例は以下のとおりです。
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: minimal-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
ingressClassName: nginx-example
rules:
- http:
paths:
- path: /testpath
pathType: Prefix
backend:
service:
name: test
port:
number: 80
IngressにはapiVersion
、kind
、metadata
やspec
フィールドが必要です。Ingressオブジェクトの名前は、有効なDNSサブドメイン名である必要があります。設定ファイルに関する一般的な情報は、アプリケーションのデプロイ、コンテナの設定、リソースの管理を参照してください。Ingressでは、Ingressコントローラーに依存しているいくつかのオプションの設定をするためにアノテーションを一般的に使用します。例としては、rewrite-targetアノテーションなどがあります。Ingressコントローラーの種類が異なれば、サポートするアノテーションも異なります。サポートされているアノテーションについて学ぶためには、使用するIngressコントローラーのドキュメントを確認してください。
Ingress Specは、ロードバランサーやプロキシーサーバーを設定するために必要な全ての情報を持っています。最も重要なものとして、外部からくる全てのリクエストに対して一致したルールのリストを含みます。IngressリソースはHTTP(S)トラフィックに対してのルールのみサポートしています。
ingressClassName
が省略された場合、デフォルトのIngressClassを定義する必要があります。
デフォルトのIngressClass
を定義しなくても動作するIngressコントローラーがいくつかあります。例えば、Ingress-NGINXコントローラーはフラグ
--watch-ingress-without-class
で設定できます。ただし、下記のようにデフォルトのIngressClass
を指定することを推奨します。
各HTTPルールは以下の情報を含みます。
/testpath
)。各パスにはservice.name
とservice.port.name
またはservice.port.number
で定義されるバックエンドが関連づけられます。ロードバランサーがトラフィックを関連づけられたServiceに転送するために、外部からくるリクエストのホスト名とパスが条件と一致させる必要があります。Ingressコントローラーでは、defaultBackend
が設定されていることがあります。これはSpec内で指定されているパスに一致しないようなリクエストのためのバックエンドです。
ルールが設定されていないIngressは、全てのトラフィックを単一のデフォルトのバックエンドに転送します。.spec.defaultBackend
はその場合にリクエストを処理するバックエンドになります。defaultBackend
は、Ingressコントローラーのオプション設定であり、Ingressリソースでは指定されていません。.spec.rules
を設定しない場合、.spec.defaultBackend
の設定は必須です。defaultBackend
が設定されていない場合、どのルールにもマッチしないリクエストの処理は、Ingressコントローラーに任されます(このケースをどう処理するかは、お使いのIngressコントローラーのドキュメントを参照してください)。
HTTPリクエストがIngressオブジェクトのホスト名とパスの条件に1つも一致しない時、そのトラフィックはデフォルトのバックエンドに転送されます。
Resource
バックエンドはIngressオブジェクトと同じnamespaceにある他のKubernetesリソースを指すObjectRefです。
Resource
はServiceの設定とは排他であるため、両方を指定するとバリデーションに失敗します。
Resource
バックエンドの一般的な用途は、静的なアセットが入ったオブジェクトストレージバックエンドにデータを導入することです。
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ingress-resource-backend
spec:
defaultBackend:
resource:
apiGroup: k8s.example.com
kind: StorageBucket
name: static-assets
rules:
- http:
paths:
- path: /icons
pathType: ImplementationSpecific
backend:
resource:
apiGroup: k8s.example.com
kind: StorageBucket
name: icon-assets
上記のIngressを作成した後に、次のコマンドで参照することができます。
kubectl describe ingress ingress-resource-backend
Name: ingress-resource-backend
Namespace: default
Address:
Default backend: APIGroup: k8s.example.com, Kind: StorageBucket, Name: static-assets
Rules:
Host Path Backends
---- ---- --------
*
/icons APIGroup: k8s.example.com, Kind: StorageBucket, Name: icon-assets
Annotations: <none>
Events: <none>
Ingressのそれぞれのパスは対応するパスのタイプを持ちます。pathType
が明示的に指定されていないパスはバリデーションに通りません。サポートされているパスのタイプは3種類あります。
ImplementationSpecific
(実装に特有): このパスタイプでは、パスとの一致はIngressClassに依存します。Ingressの実装はこれを独立したpathType
と扱うことも、Prefix
やExact
と同一のパスタイプと扱うこともできます。
Exact
: 大文字小文字を区別して完全に一致するURLパスと一致します。
Prefix
: /
で分割されたURLと前方一致で一致します。大文字小文字は区別され、パスの要素対要素で比較されます。パス要素は/
で分割されたパスの中のラベルのリストを参照します。リクエストがパス p に一致するのは、Ingressのパス p がリクエストパス p と要素単位で前方一致する場合です。
/foo/bar
は/foo/bar/baz
と一致しますが、/foo/barbaz
とは一致しません)。
タイプ | パス | リクエストパス | 一致するか |
---|---|---|---|
Prefix | / |
(全てのパス) | はい |
Exact | /foo |
/foo |
はい |
Exact | /foo |
/bar |
いいえ |
Exact | /foo |
/foo/ |
いいえ |
Exact | /foo/ |
/foo |
いいえ |
Prefix | /foo |
/foo , /foo/ |
はい |
Prefix | /foo/ |
/foo , /foo/ |
はい |
Prefix | /aaa/bb |
/aaa/bbb |
いいえ |
Prefix | /aaa/bbb |
/aaa/bbb |
はい |
Prefix | /aaa/bbb/ |
/aaa/bbb |
はい、末尾のスラッシュは無視 |
Prefix | /aaa/bbb |
/aaa/bbb/ |
はい、末尾のスラッシュと一致 |
Prefix | /aaa/bbb |
/aaa/bbb/ccc |
はい、パスの一部と一致 |
Prefix | /aaa/bbb |
/aaa/bbbxyz |
いいえ、接頭辞と一致しない |
Prefix | / , /aaa |
/aaa/ccc |
はい、接頭辞/aaa と一致 |
Prefix | / , /aaa , /aaa/bbb |
/aaa/bbb |
はい、接頭辞/aaa/bbb と一致 |
Prefix | / , /aaa , /aaa/bbb |
/ccc |
はい、接頭辞/ と一致 |
Prefix | /aaa |
/ccc |
いいえ、デフォルトバックエンドを使用 |
Mixed | /foo (Prefix), /foo (Exact) |
/foo |
はい、Exactが優先 |
リクエストがIngressの複数のパスと一致することがあります。そのような場合は、最も長くパスが一致したものが優先されます。2つのパスが同等に一致した場合は、完全一致が前方一致よりも優先されます。
ホストは正確に一致する(例えばfoo.bar.com
)かワイルドカード(例えば*.foo.com
)とすることができます。
正確な一致ではHTTPヘッダーのhost
がhost
フィールドと一致することが必要です。
ワイルドカードによる一致では、HTTPヘッダーのhost
がワイルドカードルールに沿って後方一致することが必要です。
Host | Hostヘッダー | 一致するか |
---|---|---|
*.foo.com |
bar.foo.com |
共通の接尾辞により一致 |
*.foo.com |
baz.bar.foo.com |
一致しない。ワイルドカードは単一のDNSラベルのみを対象とする |
*.foo.com |
foo.com |
一致しない。ワイルドカードは単一のDNSラベルのみを対象とする |
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ingress-wildcard-host
spec:
rules:
- host: "foo.bar.com"
http:
paths:
- pathType: Prefix
path: "/bar"
backend:
service:
name: service1
port:
number: 80
- host: "*.foo.com"
http:
paths:
- pathType: Prefix
path: "/foo"
backend:
service:
name: service2
port:
number: 80
Ingressは異なったコントローラーで実装されうるため、しばしば異なった設定を必要とします。 各Ingressはクラス、つまりIngressClassリソースへの参照を指定する必要があります。IngressClassリソースには、このクラスを実装するコントローラーの名前などの追加設定が含まれています。
apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
name: external-lb
spec:
controller: example.com/ingress-controller
parameters:
apiGroup: k8s.example.com
kind: IngressParameters
name: external-lb
IngressClassの.spec.parameters
フィールドを使って、そのIngressClassに関連する設定を持っている別のリソースを参照することができます。
使用するパラメーターの種類は、IngressClassの.spec.controller
フィールドで指定したIngressコントローラーに依存します。
Ingressコントローラーによっては、クラスター全体で設定したパラメーターを使用できる場合もあれば、1つのNamespaceに対してのみ設定したパラメーターを使用できる場合もあります。
IngressClassパラメーターのデフォルトのスコープは、クラスター全体です。
.spec.parameters
フィールドを設定して.spec.parameters.scope
フィールドを設定しなかった場合、または.spec.parameters.scope
をCluster
に設定した場合、IngressClassはクラスタースコープのリソースを参照します。
パラーメーターのkind
(およびapiGroup
)はクラスタースコープのAPI(カスタムリソースの場合もあり)を指し、パラメーターのname
はそのAPIの特定のクラスタースコープのリソースを特定します。
例えば:
---
apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
name: external-lb-1
spec:
controller: example.com/ingress-controller
parameters:
# このIngressClassのパラメーターは「external-config-1」という名前の
# ClusterIngressParameter(APIグループk8s.example.net)で指定されています。この定義は、Kubernetesに
# クラスタースコープのパラメーターリソースを探すように指示しています。
scope: Cluster
apiGroup: k8s.example.net
kind: ClusterIngressParameter
name: external-config-1
Kubernetes v1.23 [stable]
.spec.parameters
フィールドを設定して.spec.parameters.scope
フィールドをNamespace
に設定した場合、IngressClassはNamespaceスコープのリソースを参照します。また.spec.parameters
内のnamespace
フィールドには、使用するパラメーターが含まれているNamespaceを設定する必要があります。
パラメーターのkind
(およびapiGroup
)はNamespaceスコープのAPI(例えば:ConfigMap)を指し、パラメーターのname
はnamespace
で指定したNamespace内の特定のリソースを特定します。
Namespaceスコープのパラメーターはクラスターオペレーターがワークロードに使用される設定(例えば:ロードバランサー設定、APIゲートウェイ定義)に対する制御を委譲するのに役立ちます。クラスタースコープパラメーターを使用した場合は以下のいずれかになります:
IngressClass API自体は常にクラスタースコープです。
以下はNamespaceスコープのパラメーターを参照しているIngressClassの例です:
---
apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
name: external-lb-2
spec:
controller: example.com/ingress-controller
parameters:
# このIngressClassのパラメーターは「external-config」という名前の
# IngressParameter(APIグループk8s.example.com)で指定されています。
# このリソースは「external-configuration」というNamespaceにあります。
scope: Namespace
apiGroup: k8s.example.com
kind: IngressParameter
namespace: external-configuration
name: external-config
Kubernetes 1.18でIngressClassリソースとingressClassName
フィールドが追加される前は、Ingressの種別はIngressのkubernetes.io/ingress.class
アノテーションにより指定されていました。
このアノテーションは正式に定義されたことはありませんが、Ingressコントローラーに広くサポートされています。
Ingressの新しいingressClassName
フィールドはこのアノテーションを置き換えるものですが、完全に等価ではありません。
アノテーションは一般にIngressを実装すべきIngressのコントローラーの名称を示していましたが、フィールドはIngressClassリソースへの参照であり、Ingressのコントローラーの名称を含む追加のIngressの設定情報を含んでいます。
特定のIngressClassをクラスターのデフォルトとしてマークすることができます。
IngressClassリソースのingressclass.kubernetes.io/is-default-class
アノテーションをtrue
に設定すると、ingressClassName
フィールドが指定されないIngressにはこのデフォルトIngressClassが割り当てられるようになります。
ingressClassName
が指定されていない新しいIngressオブジェクトを作成できないようにします。クラスターのデフォルトIngressClassを1つ以下にすることで、これを解消することができます。
Ingressコントローラーの中には、デフォルトのIngressClass
を定義しなくても動作するものがあります。 例えば、Ingress-NGINXコントローラーはフラグ
--watch-ingress-without-class
で設定することができます。ただし、デフォルトIngressClass
を指定することを推奨します:
apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
labels:
app.kubernetes.io/component: controller
name: nginx-example
annotations:
ingressclass.kubernetes.io/is-default-class: "true"
spec:
controller: k8s.io/ingress-nginx
Kubernetesには、単一のServiceを公開できるようにする既存の概念があります(Ingressの代替案を参照してください)。ルールなしでデフォルトのバックエンド を指定することにより、Ingressでこれを実現することもできます。
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: test-ingress
spec:
defaultBackend:
service:
name: test
port:
number: 80
kubectl apply -f
を実行してIngressを作成すると、その作成したIngressの状態を確認することができます。
kubectl get ingress test-ingress
NAME CLASS HOSTS ADDRESS PORTS AGE
test-ingress external-lb * 203.0.113.123 80 59s
203.0.113.123
はIngressコントローラーによって割り当てられたIPで、作成したIngressを利用するためのものです。
<pending>
となっているのを確認できます。
ファンアウト設定では単一のIPアドレスのトラフィックを、リクエストされたHTTP URIに基づいて1つ以上のServiceに転送します。Ingressによってロードバランサーの数を少なくすることができます。例えば、以下のように設定します。
Ingressを以下のように設定します。
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: simple-fanout-example
spec:
rules:
- host: foo.bar.com
http:
paths:
- path: /foo
pathType: Prefix
backend:
service:
name: service1
port:
number: 4200
- path: /bar
pathType: Prefix
backend:
service:
name: service2
port:
number: 8080
Ingressをkubectl apply -f
によって作成したとき:
kubectl describe ingress simple-fanout-example
Name: simple-fanout-example
Namespace: default
Address: 178.91.123.132
Default backend: default-http-backend:80 (10.8.2.3:8080)
Rules:
Host Path Backends
---- ---- --------
foo.bar.com
/foo service1:4200 (10.8.0.90:4200)
/bar service2:8080 (10.8.0.91:8080)
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal ADD 22s loadbalancer-controller default/test
IngressコントローラーはService(service1
、service2
)が存在する限り、Ingressの条件を満たす実装固有のロードバランサーを構築します。
構築が完了すると、ADDRESSフィールドでロードバランサーのアドレスを確認できます。
名前ベースのバーチャルホストは、HTTPトラフィックを同一のIPアドレスの複数のホスト名に転送することをサポートしています。
以下のIngress設定は、ロードバランサーに対して、Hostヘッダーに基づいてリクエストを転送するように指示するものです。
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: name-virtual-host-ingress
spec:
rules:
- host: foo.bar.com
http:
paths:
- pathType: Prefix
path: "/"
backend:
service:
name: service1
port:
number: 80
- host: bar.foo.com
http:
paths:
- pathType: Prefix
path: "/"
backend:
service:
name: service2
port:
number: 80
rules項目でのホストの設定がないIngressを作成すると、IngressコントローラーのIPアドレスに対するwebトラフィックは、要求されている名前ベースのバーチャルホストなしにマッチさせることができます。
例えば、以下のIngressはfirst.bar.com
に対するトラフィックをservice1
へ、second.foo.com
に対するトラフィックをservice2
へ、リクエストにおいてホスト名が指定されていない(リクエストヘッダーがないことを意味します)トラフィックはservice3
へ転送します。
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: name-virtual-host-ingress-no-third-host
spec:
rules:
- host: first.bar.com
http:
paths:
- pathType: Prefix
path: "/"
backend:
service:
name: service1
port:
number: 80
- host: second.bar.com
http:
paths:
- pathType: Prefix
path: "/"
backend:
service:
name: service2
port:
number: 80
- http:
paths:
- pathType: Prefix
path: "/"
backend:
service:
name: service3
port:
number: 80
TLSの秘密鍵と証明書を含んだSecretを指定することにより、Ingressをセキュアにできます。Ingressは単一のTLSポートである443番ポートのみサポートし、IngressでTLS終端を行うことを想定しています。IngressからServiceやPodへのトラフィックは平文です。IngressのTLS設定のセクションで異なるホストを指定すると、それらのホストはSNI TLSエクステンション(IngressコントローラーがSNIをサポートしている場合)を介して指定されたホスト名に対し、同じポート上で多重化されます。TLSのSecretはtls.crt
とtls.key
というキーを含む必要があり、TLSを使用するための証明書と秘密鍵を含む値となります。以下がその例です。
apiVersion: v1
kind: Secret
metadata:
name: testsecret-tls
namespace: default
data:
tls.crt: base64 encoded cert
tls.key: base64 encoded key
type: kubernetes.io/tls
IngressでこのSecretを参照すると、クライアントとロードバランサー間の通信にTLSを使用するようIngressコントローラーに指示することになります。作成したTLS Secretは、https-example.foo.com
の完全修飾ドメイン名(FQDN)とも呼ばれる共通名(CN)を含む証明書から作成したものであることを確認する必要があります。
tls
セクションのhosts
はrules
セクションのhost
と明示的に一致する必要があります。
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: tls-example-ingress
spec:
tls:
- hosts:
- https-example.foo.com
secretName: testsecret-tls
rules:
- host: https-example.foo.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: service1
port:
number: 80
Ingressコントローラーは、負荷分散アルゴリズムやバックエンドの重みスキームなど、すべてのIngressに適用されるいくつかの負荷分散ポリシーの設定とともにブートストラップされます。発展した負荷分散のコンセプト(例: セッションの永続化、動的重み付けなど)はIngressによってサポートされていません。代わりに、それらの機能はService用のロードバランサーを介して利用できます。
ヘルスチェックの機能はIngressによって直接には公開されていませんが、Kubernetesにおいて、同等の機能を提供するReadiness Probeのようなコンセプトが存在することは注目に値します。コントローラーがどのようにヘルスチェックを行うかについては、コントローラーのドキュメントを参照してください(例えばnginx、またはGCE)。
リソースを編集することで、既存のIngressに対して新しいホストを追加することができます。
kubectl describe ingress test
Name: test
Namespace: default
Address: 178.91.123.132
Default backend: default-http-backend:80 (10.8.2.3:8080)
Rules:
Host Path Backends
---- ---- --------
foo.bar.com
/foo service1:80 (10.8.0.90:80)
Annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal ADD 35s loadbalancer-controller default/test
kubectl edit ingress test
このコマンドを実行すると既存の設定をYAMLフォーマットで編集するエディターが表示されます。新しいホストを追加するには、リソースを修正してください。
spec:
rules:
- host: foo.bar.com
http:
paths:
- backend:
service:
name: service1
port:
number: 80
path: /foo
pathType: Prefix
- host: bar.baz.com
http:
paths:
- backend:
service:
name: service2
port:
number: 80
path: /foo
pathType: Prefix
..
変更を保存した後、kubectlはAPIサーバー内のリソースを更新し、Ingressコントローラーに対してロードバランサーの再設定を指示します。
変更内容を確認してください。
kubectl describe ingress test
Name: test
Namespace: default
Address: 178.91.123.132
Default backend: default-http-backend:80 (10.8.2.3:8080)
Rules:
Host Path Backends
---- ---- --------
foo.bar.com
/foo service1:80 (10.8.0.90:80)
bar.baz.com
/foo service2:80 (10.8.0.91:80)
Annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal ADD 45s loadbalancer-controller default/test
修正されたIngressのYAMLファイルに対してkubectl replace -f
を実行することで、同様の結果を得られます。
障害のあるドメインをまたいでトラフィックを分散する手法は、クラウドプロバイダーによって異なります。詳細に関して、Ingress コントローラーのドキュメントを参照してください。
Ingressリソースを直接含まずにサービスを公開する方法は複数あります。
Ingressリソースが動作するためには、クラスターでIngressコントローラーが実行されている必要があります。
kube-controller-manager
バイナリの一部として実行される他のタイプのコントローラーとは異なり、Ingressコントローラーはクラスターで自動的に起動されません。このページを使用して、クラスターに最適なIngressコントローラーの実装を選択してください。
プロジェクトとしてのKubernetesは現在、AWS、GCE、およびnginxのIngressコントローラーをサポート・保守しています。
Ingressコントローラーは、好きな数だけクラスターにデプロイすることができます。Ingressを作成する際には、クラスター内に複数のIngressコントローラーが存在する場合にどのIngressコントローラーを使用するかを示すために適切なingress.class
のアノテーションを指定する必要があります。
クラスを定義しない場合、クラウドプロバイダーはデフォルトのIngressコントローラーを使用する場合があります。
理想的には、すべてのIngressコントローラーはこの仕様を満たすべきですが、いくつかのIngressコントローラーはわずかに異なる動作をします。
Kubernetes v1.21 [alpha]
サービス内部トラフィックポリシーを使用すると、内部トラフィック制限により、トラフィックが発信されたノード内のエンドポイントにのみ内部トラフィックをルーティングできます。 ここでの「内部」トラフィックとは、現在のクラスターのPodから発信されたトラフィックを指します。これは、コストを削減し、パフォーマンスを向上させるのに役立ちます。
ServiceInternalTrafficPolicy
フィーチャーゲートを有効にすると、.spec.internalTrafficPolicy
をLocal
に設定して、Service内部のみのトラフィックポリシーを有効にすることができます。
これにより、kube-proxyは、クラスター内部トラフィックにノードローカルエンドポイントのみを使用するようになります。
次の例は、.spec.internalTrafficPolicy
をLocal
に設定した場合のServiceの様子を示しています:
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
selector:
app: MyApp
ports:
- protocol: TCP
port: 80
targetPort: 9376
internalTrafficPolicy: Local
kube-proxyは、spec.internalTrafficPolicy
の設定に基づいて、ルーティング先のエンドポイントをフィルタリングします。
spec.internalTrafficPolicy
がLocal
であれば、ノードのローカルエンドポイントにのみルーティングできるようにします。Cluster
または未設定であればすべてのエンドポイントにルーティングできるようにします。
ServiceInternalTrafficPolicy
フィーチャーゲートが有効な場合、spec.internalTrafficPolicy
のデフォルトはCluster
です。
externalTrafficPolicy
がLocal
に設定されている場合、サービス内部トラフィックポリシーは使用されません。同じServiceだけではなく、同じクラスター内の異なるServiceで両方の機能を使用することができます。Kubernetes v1.23 [beta]
Topology Aware Hintは、クライアントがendpointをどのように使用するかについての提案を含めることにより、トポロジーを考慮したルーティングを可能にします。このアプローチでは、EndpointSliceおよび/またはEndpointオブジェクトの消費者が、これらのネットワークエンドポイントへのトラフィックを、それが発生した場所の近くにルーティングできるように、メタデータを追加します。
たとえば、局所的にトラフィックをルーティングすることで、コストを削減したり、ネットワークパフォーマンスを向上させたりできます。
Kubernetesクラスターは、マルチゾーン環境で展開されることが多くなっています。 Topology Aware Hintは、トラフィックを発信元のゾーン内に留めておくのに役立つメカニズムを提供します。このコンセプトは、一般に「Topology Aware Routing」と呼ばれています。EndpointSliceコントローラーはServiceのendpointを計算する際に、各endpointのトポロジー(リージョンとゾーン)を考慮し、ゾーンに割り当てるためのヒントフィールドに値を入力します。 EndpointSliceコントローラーは、各endpointのトポロジー(リージョンとゾーン)を考慮し、ゾーンに割り当てるためのヒントフィールドに入力します。 kube-proxyのようなクラスターコンポーネントは、次にこれらのヒントを消費し、それらを使用してトラフィックがルーティングされる方法に影響を与えることが可能です(トポロジー的に近いendpointを優先します)。
service.kubernetes.io/topology-aware-hints
アノテーションをauto
に設定すると、サービスに対してTopology Aware Hintを有効にすることができます。これはEndpointSliceコントローラーが安全と判断した場合に、トポロジーヒントを設定するように指示します。
重要なのは、これはヒントが常に設定されることを保証するものではないことです。
この機能を有効にする機能は、EndpointSliceコントローラーとkube-proxyの2つのコンポーネントに分かれています。このセクションでは、各コンポーネントがこの機能をどのように実装しているか、高レベルの概要を説明します。
この機能が有効な場合、EndpointSliceコントローラーはEndpointSliceにヒントを設定する役割を担います。 コントローラーは、各ゾーンに比例した量のendpointを割り当てます。 この割合は、そのゾーンで実行されているノードの割り当て可能なCPUコアを基に決定されます。
たとえば、あるゾーンに2つのCPUコアがあり、別のゾーンに1つのCPUコアしかない場合、コントローラーは2つのCPUコアを持つゾーンに2倍のendpointを割り当てます。
次の例は、ヒントが入力されたときのEndpointSliceの様子を示しています。
apiVersion: discovery.k8s.io/v1
kind: EndpointSlice
metadata:
name: example-hints
labels:
kubernetes.io/service-name: example-svc
addressType: IPv4
ports:
- name: http
protocol: TCP
port: 80
endpoints:
- addresses:
- "10.1.2.3"
conditions:
ready: true
hostname: pod-1
zone: zone-a
hints:
forZones:
- name: "zone-a"
kube-proxyは、EndpointSliceコントローラーによって設定されたヒントに基づいて、ルーティング先のendpointをフィルター処理します。ほとんどの場合、これはkube-proxyが同じゾーン内のendpointにトラフィックをルーティングできることを意味します。コントローラーが別のゾーンからendpointを割り当てて、ゾーン間でendpointがより均等に分散されるようにする場合があります。これにより、一部のトラフィックが他のゾーンにルーティングされます。
各ノードのKubernetesコントロールプレーンとkube-proxyは、Topology Aware Hintを使用する前に、いくつかのセーフガードルールを適用します。これらがチェックアウトされない場合、kube-proxyは、ゾーンに関係なく、クラスター内のどこからでもendpointを選択します。
endpointの数が不十分です: クラスター内のゾーンよりもendpointが少ない場合、コントローラーはヒントを割り当てません。
バランスの取れた割り当てを実現できません: 場合によっては、ゾーン間でendpointのバランスの取れた割り当てを実現できないことがあります。たとえば、ゾーンaがゾーンbの2倍の大きさであるが、endpointが2つしかない場合、ゾーンaに割り当てられたendpointはゾーンbの2倍のトラフィックを受信する可能性があります。この「予想される過負荷」値が各ゾーンの許容しきい値を下回ることができない場合、コントローラーはヒントを割り当てません。重要なことに、これはリアルタイムのフィードバックに基づいていません。それでも、個々のendpointが過負荷になる可能性があります。
1つ以上のノードの情報が不十分です: ノードにtopology.kubernetes.io/zone
ラベルがないか、割り当て可能なCPUの値を報告していない場合、コントロールプレーンはtopology-aware endpoint hintsを設定しないため、kube-proxyはendpointをゾーンでフィルタリングしません。
1つ以上のendpointにゾーンヒントが存在しません: これが発生すると、kube-proxyはTopology Aware Hintから、またはTopology Aware Hintへの移行が進行中であると見なします。この状態のサービスに対してendpointをフィルタリングすることは危険であるため、kube-proxyはすべてのendpointを使用するようにフォールバックします。
ゾーンはヒントで表されません: kube-proxyが、実行中のゾーンをターゲットとするヒントを持つendpointを1つも見つけることができない場合、すべてのゾーンのendpointを使用することになります。これは既存のクラスターに新しいゾーンを追加するときに発生する可能性が最も高くなります。
ServiceでexternalTrafficPolicy
またはinternalTrafficPolicy
がLocal
に設定されている場合、Topology Aware Hintは使用されません。同じServiceではなく、異なるServiceの同じクラスターで両方の機能を使用することができます。
このアプローチは、ゾーンのサブセットから発信されるトラフィックの割合が高いサービスではうまく機能しません。代わりに、これは着信トラフィックが各ゾーンのノードの容量にほぼ比例することを前提としています。
EndpointSliceコントローラーは、各ゾーンの比率を計算するときに、準備ができていないノードを無視します。ノードの大部分の準備ができていない場合、これは意図しない結果をもたらす可能性があります。
EndpointSliceコントローラーは、各ゾーンの比率を計算するデプロイ時にtolerationを考慮しません。サービスをバックアップするPodがクラスター内のノードのサブセットに制限されている場合、これは考慮されません。
これはオートスケーリングと相性が悪いかもしれません。例えば、多くのトラフィックが1つのゾーンから発信されている場合、そのゾーンに割り当てられたendpointのみがそのトラフィックを処理することになります。その結果、Horizontal Pod Autoscalerがこのイベントを拾えなくなったり、新しく追加されたPodが別のゾーンで開始されたりする可能性があります。
IPアドレスまたはポートのレベル(OSI参照モデルのレイヤ3または4)でトラフィックフローを制御したい場合、クラスター内の特定のアプリケーションにKubernetesのネットワークポリシーを使用することを検討してください。ネットワークポリシーはアプリケーション中心の構造であり、Podがネットワークを介して多様な「エンティティ」(「Endpoint」や「Service」のようなKubernetesに含まれる特定の意味を持つ共通の用語との重複を避けるため、ここではエンティティという単語を使用します。)と通信する方法を指定できます。
Podが通信できるエンティティは以下の3つの識別子の組み合わせによって識別されます。
Podベースもしくは名前空間ベースのネットワークポリシーを定義する場合、セレクターを使用してセレクターに一致するPodとの間で許可されるトラフィックを指定します。
一方でIPベースのネットワークポリシーが作成されると、IPブロック(CIDRの範囲)に基づいてポリシーが定義されます。
ネットワークポリシーは、ネットワークプラグインにより実装されます。ネットワークポリシーを使用するには、NetworkPolicyをサポートするネットワークソリューションを使用しなければなりません。ネットワークポリシーを実装したコントローラーを使用せずにNetworkPolicyリソースを作成した場合は、何も効果はありません。
デフォルトでは、Podは分離されていない状態(non-isolated)となるため、すべてのソースからのトラフィックを受信します。
Podを選択するNetworkPolicyが存在すると、Podは分離されるようになります。名前空間内に特定のPodを選択するNetworkPolicyが1つでも存在すると、そのPodはいずれかのNetworkPolicyで許可されていないすべての接続を拒否するようになります。(同じ名前空間内のPodでも、どのNetworkPolicyにも選択されなかった他のPodは、引き続きすべてのトラフィックを許可します。)
ネットワークポリシーは追加式であるため、競合することはありません。複数のポリシーがPodを選択する場合、そのPodに許可されるトラフィックは、それらのポリシーのingress/egressルールの和集合で制限されます。したがって、評価の順序はポリシーの結果には影響がありません。
リソースの完全な定義については、リファレンスのNetworkPolicyのセクションを参照してください。
以下は、NetworkPolicyの一例です。
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: test-network-policy
namespace: default
spec:
podSelector:
matchLabels:
role: db
policyTypes:
- Ingress
- Egress
ingress:
- from:
- ipBlock:
cidr: 172.17.0.0/16
except:
- 172.17.1.0/24
- namespaceSelector:
matchLabels:
project: myproject
- podSelector:
matchLabels:
role: frontend
ports:
- protocol: TCP
port: 6379
egress:
- to:
- ipBlock:
cidr: 10.0.0.0/24
ports:
- protocol: TCP
port: 5978
必須フィールド: 他のKubernetesの設定と同様に、NetworkPolicyにもapiVersion
、kind
、metadata
フィールドが必須です。設定ファイルの扱い方に関する一般的な情報については、ConfigMapを使用してコンテナを構成するとオブジェクト管理を参照してください。
spec: NetworkPolicyのspecを見ると、指定した名前空間内で特定のネットワークポリシーを定義するのに必要なすべての情報が確認できます。
podSelector: 各NetworkPolicyには、ポリシーを適用するPodのグループを選択するpodSelector
が含まれます。ポリシーの例では、ラベル"role=db"を持つPodを選択しています。podSelector
を空にすると、名前空間内のすべてのPodが選択されます。
policyTypes: 各NetworkPolicyには、policyTypes
として、Ingress
、Egress
、またはその両方からなるリストが含まれます。policyTypes
フィールドでは、指定したポリシーがどの種類のトラフィックに適用されるかを定めます。トラフィックの種類としては、選択したPodへの内向きのトラフィック(Ingress)、選択したPodからの外向きのトラフィック(Egress)、またはその両方を指定します。policyTypes
を指定しなかった場合、デフォルトで常に
Ingress
が指定され、NetworkPolicyにegressルールが1つでもあればEgress
も設定されます。
ingress: 各NetworkPolicyには、許可するingress
ルールのリストを指定できます。各ルールは、from
およびports
セクションの両方に一致するトラフィックを許可します。ポリシーの例には1つのルールが含まれ、このルールは、3つのソースのいずれかから送信された1つのポート上のトラフィックに一致します。1つ目のソースはipBlock
で、2つ目のソースはnamespaceSelector
で、3つ目のソースはpodSelector
でそれぞれ定められます。
egress: 各NetworkPolicyには、許可するegress
ルールのリストを指定できます。各ルールは、to
およびports
セクションの両方に一致するトラフィックを許可します。ポリシーの例には1つのルールが含まれ、このルールは、1つのポート上で10.0.0.0/24
の範囲内の任意の送信先へ送られるトラフィックに一致します。
したがって、上のNetworkPolicyの例では、次のようにネットワークポリシーを適用します。
追加の例については、ネットワークポリシーを宣言するの説明を参照してください。
to
とfrom
のセレクターの振る舞いingress
のfrom
セクションまたはegress
のto
セクションに指定できるセレクターは4種類あります。
podSelector: NetworkPolicyと同じ名前空間内の特定のPodを選択して、ingressの送信元またはegressの送信先を許可します。
namespaceSelector: 特定の名前空間を選択して、その名前空間内のすべてのPodについて、ingressの送信元またはegressの送信先を許可します。
namespaceSelector および podSelector: 1つのto
またはfrom
エントリーでnamespaceSelector
とpodSelector
の両方を指定して、特定の名前空間内の特定のPodを選択します。正しいYAMLの構文を使うように気をつけてください。このポリシーの例を以下に示します。
...
ingress:
- from:
- namespaceSelector:
matchLabels:
user: alice
podSelector:
matchLabels:
role: client
...
このポリシーには、1つのfrom
要素があり、ラベルuser=alice
の付いた名前空間内にある、ラベルrole=client
の付いたPodからの接続を許可します。しかし、以下のポリシーには注意が必要です。
...
ingress:
- from:
- namespaceSelector:
matchLabels:
user: alice
- podSelector:
matchLabels:
role: client
...
このポリシーには、from
配列の中に2つの要素があります。そのため、ラベルrole=client
の付いた名前空間内にあるすべてのPodからの接続、または、任意の名前空間内にあるラベルuser=alice
の付いたすべてのPodからの接続を許可します。
正しいルールになっているか自信がないときは、kubectl describe
を使用すると、Kubernetesがどのようにポリシーを解釈したのかを確認できます。
ipBlock: 特定のIPのCIDRの範囲を選択して、ingressの送信元またはegressの送信先を許可します。PodのIPは一時的なもので予測できないため、ここにはクラスター外のIPを指定するべきです。
クラスターのingressとegressの仕組みはパケットの送信元IPや送信先IPの書き換えを必要とすることがよくあります。その場合、NetworkPolicyの処理がIPの書き換えの前後どちらで行われるのかは定義されていません。そのため、ネットワークプラグイン、クラウドプロバイダー、Service
の実装などの組み合わせによっては、動作が異なる可能性があります。
内向きのトラフィックの場合は、実際のオリジナルの送信元IPに基づいてパケットをフィルタリングできる可能性もあれば、NetworkPolicyが対象とする「送信元IP」がLoadBalancer
やPodのノードなどのIPになってしまっている可能性もあることになります。
外向きのトラフィックの場合は、クラスター外のIPに書き換えられたPodからService
のIPへの接続は、ipBlock
ベースのポリシーの対象になる場合とならない場合があることになります。
デフォルトでは、名前空間にポリシーが存在しない場合、その名前空間内のPodの内向きと外向きのトラフィックはすべて許可されます。以下の例を利用すると、その名前空間内でのデフォルトの振る舞いを変更できます。
すべてのPodを選択して、そのPodへのすべての内向きのトラフィックを許可しないNetworkPolicyを作成すると、その名前空間に対する「デフォルト」の分離ポリシーを作成できます。
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-ingress
spec:
podSelector: {}
policyTypes:
- Ingress
このポリシーを利用すれば、他のいかなるNetworkPolicyにも選択されなかったPodでも分離されることを保証できます。このポリシーは、デフォルトの外向きの分離の振る舞いを変更しません。
(たとえPodを「分離されたもの」として扱うポリシーが追加された場合でも)名前空間内のすべてのPodへのすべてのトラフィックを許可したい場合には、その名前空間内のすべてのトラフィックを明示的に許可するポリシーを作成できます。
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-all-ingress
spec:
podSelector: {}
ingress:
- {}
policyTypes:
- Ingress
すべてのPodを選択して、そのPodからのすべての外向きのトラフィックを許可しないNetworkPolicyを作成すると、その名前空間に対する「デフォルト」の外向きの分離ポリシーを作成できます。
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-egress
spec:
podSelector: {}
policyTypes:
- Egress
このポリシーを利用すれば、他のいかなるNetworkPolicyにも選択されなかったPodでも、外向きのトラフィックが許可されないことを保証できます。このポリシーは、デフォルトの内向きの分離の振る舞いを変更しません。
(たとえPodを「分離されたもの」として扱うポリシーが追加された場合でも)名前空間内のすべてのPodからのすべてのトラフィックを許可したい場合には、その名前空間内のすべての外向きのトラフィックを明示的に許可するポリシーを作成できます。
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-all-egress
spec:
podSelector: {}
egress:
- {}
policyTypes:
- Egress
名前空間内に以下のNetworkPolicyを作成すると、その名前空間で内向きと外向きのすべてのトラフィックを拒否する「デフォルト」のポリシーを作成できます。
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
このポリシーを利用すれば、他のいかなるNetworkPolicyにも選択されなかったPodでも、内向きと外向きのトラフィックが許可されないことを保証できます。
Kubernetes v1.19 [beta]
ベータ版の機能として、これはデフォルトで有効化されます。
クラスターレベルでSCTPを無効化するために、クラスター管理者はAPIサーバーで--feature-gates=SCTPSupport=false,…
と指定して、SCTPSupport
フィーチャーゲートを無効にする必要があります。
Kubernetes1.20現在、ネットワークポリシーAPIに以下の機能は存在しません。 しかし、オペレーティングシステムのコンポーネント(SELinux、OpenVSwitch、IPTablesなど)、レイヤ7の技術(Ingressコントローラー、サービスメッシュ実装)、もしくはアドミッションコントローラーを使用して回避策を実装できる場合があります。 Kubernetesのネットワークセキュリティを初めて使用する場合は、ネットワークポリシーAPIを使用して以下のユーザーストーリーを(まだ)実装できないことに注意してください。これらのユーザーストーリーの一部(全てではありません)は、ネットワークポリシーAPIの将来のリリースで活発に議論されています。
Kubernetes v1.16 [alpha]
IPv4/IPv6デュアルスタックを利用すると、IPv4とIPv6のアドレスの両方をPodおよびServiceに指定できるようになります。
KubernetesクラスターでIPv4/IPv6デュアルスタックのネットワークを有効にすれば、クラスターはIPv4とIPv6のアドレスの両方を同時に割り当てることをサポートするようになります。
KubernetesクラスターでIPv4/IPv6デュアルスタックを有効にすると、以下の機能が提供されます。
IPv4/IPv6デュアルスタックのKubernetesクラスターを利用するには、以下の前提条件を満たす必要があります。
IPv4/IPv6デュアルスタックを有効にするには、クラスターの関連コンポーネントでIPv6DualStack
フィーチャーゲートを有効にして、デュアルスタックのクラスターネットワークの割り当てを以下のように設定します。
--feature-gates="IPv6DualStack=true"
--service-cluster-ip-range=<IPv4 CIDR>,<IPv6 CIDR>
--feature-gates="IPv6DualStack=true"
--cluster-cidr=<IPv4 CIDR>,<IPv6 CIDR>
--service-cluster-ip-range=<IPv4 CIDR>,<IPv6 CIDR>
--node-cidr-mask-size-ipv4|--node-cidr-mask-size-ipv6
デフォルトのサイズは、IPv4では/24、IPv6では/64です--feature-gates="IPv6DualStack=true"
--cluster-cidr=<IPv4 CIDR>,<IPv6 CIDR>
--feature-gates="IPv6DualStack=true"
IPv4 CIDRの例: 10.244.0.0/16
(自分のクラスターのアドレス範囲を指定してください)
IPv6 CIDRの例: fdXY:IJKL:MNOP:15::/64
(これはフォーマットを示すための例であり、有効なアドレスではありません。詳しくはRFC 4193を参照してください)
クラスターでIPv4/IPv6デュアルスタックのネットワークを有効にした場合、IPv4またはIPv6のいずれかのアドレスを持つServiceを作成できます。Serviceのcluster IPのアドレスファミリーは、Service上に.spec.ipFamily
フィールドを設定することで選択できます。このフィールドを設定できるのは、新しいServiceの作成時のみです。.spec.ipFamily
フィールドの指定はオプションであり、ServiceとIngressでIPv4とIPv6を有効にする予定がある場合にのみ使用するべきです。このフィールドの設定は、外向きのトラフィックに対する要件には含まれません。
--service-cluster-ip-range
フラグで設定した、最初のservice cluster IPの範囲のアドレスファミリーです。
.spec.ipFamily
は、次のいずれかに設定できます。
IPv4
: APIサーバーはipv4
のservice-cluster-ip-range
の範囲からIPアドレスを割り当てますIPv6
: APIサーバーはipv6
のservice-cluster-ip-range
の範囲からIPアドレスを割り当てます次のServiceのspecにはipFamily
フィールドが含まれていません。Kubernetesは、最初に設定したservice-cluster-ip-range
の範囲からこのServiceにIPアドレス(別名「cluster IP」)を割り当てます。
apiVersion: v1
kind: Service
metadata:
name: my-service
labels:
app: MyApp
spec:
selector:
app: MyApp
ports:
- protocol: TCP
port: 80
次のServiceのspecにはipFamily
フィールドが含まれています。Kubernetesは、最初に設定したservice-cluster-ip-range
の範囲からこのServiceにIPv6のアドレス(別名「cluster IP」)を割り当てます。
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
ipFamily: IPv6
selector:
app: MyApp
ports:
- protocol: TCP
port: 80
targetPort: 9376
比較として次のServiceのspecを見ると、このServiceには最初に設定したservice-cluster-ip-range
の範囲からIPv4のアドレス(別名「cluster IP」)が割り当てられます。
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
ipFamily: IPv4
selector:
app: MyApp
ports:
- protocol: TCP
port: 80
targetPort: 9376
IPv6が有効になった外部ロードバランサーをサポートしているクラウドプロバイダーでは、type
フィールドにLoadBalancer
を指定し、ipFamily
フィールドにIPv6
を指定することにより、クラウドロードバランサーをService向けにプロビジョニングできます。
パブリックおよび非パブリックでのルーティングが可能なIPv6アドレスのブロックを利用するためには、クラスターがベースにしているCNIプロバイダーがIPv6の転送を実装している必要があります。もし非パブリックでのルーティングが可能なIPv6アドレスを使用するPodがあり、そのPodをクラスター外の送信先(例:パブリックインターネット)に到達させたい場合、外向きのトラフィックと応答の受信のためにIPマスカレードを設定する必要があります。ip-masq-agentはデュアルスタックに対応しているため、デュアルスタックのクラスター上でのIPマスカレードにはip-masq-agentが利用できます。