Ingress: Kubernetes で外からのリクエストをアプリごとに振り分ける
- 公開日
- カテゴリ:Kubernetes
- タグ:Kubernetes,学習メモ

クラスタの外にいるブラウザから、クラスタの中の Pod にリクエストを届けたい。Kubernetes では、Pod の手前に Service を置き、クラスタの外からの入口は Service の type(NodePort / LoadBalancer)か Ingress で決まる。
この記事では、手元の Mac をクラスタの外に見立てて、次の 3 つを確認する。
- Service の名前(
demo-service)で Pod に届くこと - NodePort と kind のポート転送で、Mac の
localhost:30080から Pod に届くこと - Ingress と Ingress Controller(Contour)で、Mac の
http://localhost/からホスト名ごとに別の Service に届くこと
contents
- 環境
- Mac から Pod までの経路
- Deployment と Service を作る
- Mac から Node のポートに届かせる(NodePort)
- Ingress を作る(Ingress Controller なし)
- Ingress Controller(Contour)を入れる
- Mac から Ingress 経由で届く
- ホスト名で振り分ける
- 消す
- 用語メモ
- 参考 URL
環境
kind で作ったクラスタ(Control Plane 1台 + Worker 2台)を使う。(作り方は「Kubernetes の Pod とは?コンテナを動かす最小単位を整理する」を参照)
- 環境: macOS(Apple Silicon)、Docker Desktop 29.8.0、kind v0.33.0、Kubernetes v1.37.0、kubectl v1.36.1
kind の Node は Docker のコンテナで、そのままでは Mac から Node のポートに届かない。この記事では、kind の設定に extraPortMappings(Mac のポート → Node のポート)を足したクラスタを使う。
# kind-config.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
- role: worker
extraPortMappings:
- containerPort: 80 # Mac の 80 番 → この Node の 80 番(Ingress 用)
hostPort: 80
listenAddress: "127.0.0.1"
- containerPort: 30080 # Mac の 30080 番 → この Node の 30080 番(NodePort 用)
hostPort: 30080
listenAddress: "127.0.0.1"
- role: worker
$ kind create cluster --name k8s-study --config kind-config.yaml
$ docker ps --format '{{.Names}}\t{{.Ports}}' | grep k8s-study
k8s-study-worker 127.0.0.1:80->80/tcp, 127.0.0.1:30080->30080/tcp
k8s-study-control-plane 127.0.0.1:65042->6443/tcp
k8s-study-worker2
k8s-study-worker に Mac の 80 番と 30080 番がつながっている。これは Docker(kind)の設定で、Kubernetes 側の設定ではない。
Mac から Pod までの経路
| 経路の要素 | 何か | 実体 |
|---|---|---|
| Pod | nginx が動いている。IP は 10.244.x.x(クラスタの中だけで通じる) | Node の上のコンテナ |
| Service | Pod の手前に置く固定の名前と IP。demo-service → nginx の Pod 3 つ | etcd に保存されたデータ。届けているのは各 Node の kube-proxy |
| NodePort | Service の type の 1 つ。全部の Node の同じポート(30080)でも受ける | Service の設定 |
| Ingress | 「このホスト名の HTTP リクエストは、この Service へ」というルール | etcd に保存されたデータ |
| Ingress Controller | Ingress を読んで、そのとおりに HTTP を振り分けるプログラム。Kubernetes に入っていないので自分で入れる | 今回は Contour(projectcontour namespace の Pod) |
外から来た HTTP リクエストは、そのままでは Pod に届かない。Kubernetes に、外から受けて Pod に渡す仕組みが入っていないため。
そこで Ingress Controller を自分で入れる。これはライブラリではなく、クラスタの中で動くプログラム(Pod)。Kubernetes とは別のプロジェクトがいくつも作っていて(nginx ベース、HAProxy ベース、クラウド各社のものなど)、どれかを選んで入れる。この記事では Contour を使う。
Contour を入れると、Pod が 2 種類動く。
- contour: Ingress を読む
- envoy: Node の 80 番で受け、Ingress のルール(ホスト名 → Service)に従って、その Service の Pod に転送する
アプリは nginx(Service demo-service)と Apache httpd(Service httpd-service)の 2 つ。振り分け先を 2 つにするためで、httpd も nginx と同じ Web サーバー。
Ingress で実現される構成を図にすると次のとおり。Pod がどの Node にあるかは、この仕組みには関係ないので描いていない。
- 振り分けは 2 段階ある。入口のロードバランサーは「どの Node(マシン)に渡すか」を決める。envoy は「どの Service の Pod に渡すか」を
Hostヘッダーで決める。ロードバランサーは HTTP の中身を見ないので、アプリを選ぶ仕事はできない - 角が二重の箱(contour、envoy)は動いているプログラム。自分で入れた Pod で、各 Worker Node に 1 組ずつある。どの Node にリクエストが来ても、その Node の envoy が同じ判断をする。Node が 1 台止まっても、残りの Node で受けられる
- envoy は、Service の宛先一覧にある Pod の IP に送る。その Pod が自分の Node にあるか別の Node にあるかは見ない
- 自分で書くのは Service と Ingress の YAML だけ
- この記事の kind では、入口はクラウドのロードバランサーではなく Docker のポート転送で、Mac の 80 番は
k8s-study-workerの 80 番だけにつながっている
Deployment と Service を作る
nginx の Deployment(Pod 3 個)と、その手前の Service を作る。クラスタの中から打つための Pod client(busybox、sleep するだけ)も作る。
# demo-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: demo-app
spec:
replicas: 3
selector:
matchLabels:
app: demo-app
template:
metadata:
labels:
app: demo-app
spec:
containers:
- name: nginx
image: nginx:1.16.1
ports:
- containerPort: 80
# demo-service.yaml
apiVersion: v1
kind: Service
metadata:
name: demo-service
spec:
selector:
app: demo-app
ports:
- port: 80
targetPort: 80
# client.yaml
apiVersion: v1
kind: Pod
metadata:
name: client
spec:
containers:
- name: client
image: busybox:1.36
command: ["sleep", "86400"]
$ kubectl apply -f demo-deployment.yaml -f demo-service.yaml -f client.yaml
$ kubectl get svc demo-service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
demo-service ClusterIP 10.96.196.69 <none> 80/TCP 4s
$ kubectl exec client -- wget -qO- http://demo-service | grep title
# => <title>Welcome to nginx!</title>
クラスタの中からは、Service の名前 demo-service で nginx に届く。(Service は「Service: Kubernetes で Pod への宛先を固定する」を参照)
Mac から Node のポートに届かせる(NodePort)
Service の type を NodePort にすると、全部の Node の 30080 番でも受けるようになる。
# demo-service-nodeport.yaml
apiVersion: v1
kind: Service
metadata:
name: demo-service
spec:
type: NodePort
selector:
app: demo-app
ports:
- port: 80
targetPort: 80
nodePort: 30080 # 書かないと 30000〜32767 から自動で選ばれる
$ kubectl apply -f demo-service-nodeport.yaml
# => service/demo-service configured
$ kubectl get svc demo-service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
demo-service NodePort 10.96.213.171 <none> 80:30080/TCP 10s
$ curl -s http://localhost:30080 | grep title
# => <title>Welcome to nginx!</title>
PORT(S) の 80:30080/TCP は「Service のポート 80、Node のポート 30080」。Mac の localhost:30080 は kind のポート転送で k8s-study-worker の 30080 番につながり、そこから Service の Pod に届く。ブラウザで http://localhost:30080/ を開いても nginx のページが出る。
NodePort は「ポート番号 1 つ = Service 1 つ」。アプリが増えるとポート番号も増える。1 つの入口で複数のアプリに振り分けるのが、次の Ingress。
Ingress を作る(Ingress Controller なし)
まず、Ingress Controller を入れずに Ingress だけ作る。ルールは「全部の HTTP リクエストを demo-service へ」。
# demo-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: demo-ingress
spec:
rules:
- http: # host を書かないので、どのホスト名でも当てはまる
paths:
- path: /
pathType: Prefix # / で始まるパス = 全部
backend:
service:
name: demo-service # 送り先の Service
port:
number: 80
$ kubectl apply -f demo-ingress.yaml
# => ingress.networking.k8s.io/demo-ingress created
$ kubectl get ingress
NAME CLASS HOSTS ADDRESS PORTS AGE
demo-ingress <none> * 80 6s
$ kubectl describe ingress demo-ingress
Name: demo-ingress
Labels: <none>
Namespace: default
Address:
Ingress Class: <none>
Default backend: <default>
Rules:
Host Path Backends
---- ---- --------
*
/ demo-service:80 (10.244.1.2:80,10.244.3.3:80,10.244.1.3:80)
Annotations: <none>
Events: <none>
$ curl -m 3 http://localhost/
# => curl: (52) Empty reply from server
Rules: の Backends に、Service の名前とその先の Pod の IP まで出る。データとしてはつながっているが、Mac から http://localhost/ を打っても返事がない。Node の 80 番を聞いているプログラムがいないため。
Ingress は、Deployment や Service と違って、読んで動くプログラムが Kubernetes に最初から入っていない。公式ドキュメントも "Only creating an Ingress resource has no effect." としている。
Ingress Controller(Contour)を入れる
Ingress Controller は、Ingress を読んで、そのとおりに HTTP を振り分けるプログラム。中身はリバースプロキシ(外から来たリクエストを受け取って、後ろにいるサーバーのどれかに渡すプログラム)で、Ingress のルールをその設定に変換して動かす。何種類もある中から、この記事では Contour を使う。
| Contour の Pod | 何をするか |
|---|---|
contour(Deployment、2 つ) | Ingress と Service を kube-apiserver から読んで、Envoy の設定に変換して配る |
envoy(DaemonSet、Worker ごとに 1 つ) | リバースプロキシ本体(Envoy)。Node の 80 番で HTTP を受け、設定どおりの Pod に転送する |
$ kubectl apply -f https://projectcontour.io/quickstart/contour.yaml
namespace/projectcontour created
serviceaccount/contour created
serviceaccount/envoy created
configmap/contour created
customresourcedefinition.apiextensions.k8s.io/contourconfigurations.projectcontour.io created
customresourcedefinition.apiextensions.k8s.io/contourdeployments.projectcontour.io created
customresourcedefinition.apiextensions.k8s.io/extensionservices.projectcontour.io created
customresourcedefinition.apiextensions.k8s.io/httpproxies.projectcontour.io created
customresourcedefinition.apiextensions.k8s.io/tlscertificatedelegations.projectcontour.io created
serviceaccount/contour-certgen created
rolebinding.rbac.authorization.k8s.io/contour created
role.rbac.authorization.k8s.io/contour-certgen created
job.batch/contour-certgen-v1-33-7 created
clusterrolebinding.rbac.authorization.k8s.io/contour created
rolebinding.rbac.authorization.k8s.io/contour-rolebinding created
clusterrole.rbac.authorization.k8s.io/contour created
role.rbac.authorization.k8s.io/contour created
service/contour created
service/envoy created
deployment.apps/contour created
daemonset.apps/envoy created
$ kubectl get pods -n projectcontour -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
contour-859c5dcfdc-d52lr 0/1 ContainerCreating 0 7s <none> k8s-study-worker2 <none> <none>
contour-859c5dcfdc-xjqcg 0/1 ContainerCreating 0 7s <none> k8s-study-worker <none> <none>
contour-certgen-v1-33-7-cpg4v 0/1 Completed 0 7s 10.244.1.4 k8s-study-worker2 <none> <none>
envoy-6jl64 0/2 Init:0/1 0 7s <none> k8s-study-worker2 <none> <none>
envoy-rbsm2 0/2 Init:0/1 0 7s <none> k8s-study-worker <none> <none>
1 分ほどで contour が 1/1、envoy が 2/2 の Running になる(contour-certgen は証明書を作る Job で Completed のまま)。
Mac から Ingress 経由で届く
$ curl -s http://localhost/ | grep title
# => <title>Welcome to nginx!</title>
$ curl -si http://localhost/ | head -4
HTTP/1.1 200 OK
server: envoy
date: Sun, 11 Oct 2026 00:27:48 GMT
content-type: text/html
さっき返事がなかった http://localhost/ で nginx が返るようになった。返事の server ヘッダーが envoy になっていて、間に Envoy がいることがわかる。
Envoy のログには、Mac からのリクエストと、転送先の Pod の IP が残る。
$ kubectl logs -n projectcontour -l app=envoy -c envoy --tail=1
# => [2026-10-11T00:27:48.031Z] "GET / HTTP/1.1" 200 - 0 612 3 2 "172.21.0.1" "curl/8.7.1" "685ca5ab-ab0b-4c11-8405-1cf27d26ff0e" "localhost" "10.244.1.3:80"
最後の "10.244.1.3:80" が転送先で、nginx の Pod の IP。Envoy は Service の宛先一覧(EndpointSlice)から Pod の IP を受け取り、Pod に直接送っている。
ホスト名で振り分ける
Ingress の使いどころは、1 つの入口に来たリクエストを HTTP の Host ヘッダー(ホスト名)で別々の Service に振り分けること。2 つ目の Web サーバーとして Apache httpd を足す。
# demo-httpd.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: demo-httpd
spec:
replicas: 1
selector:
matchLabels:
app: demo-httpd
template:
metadata:
labels:
app: demo-httpd
spec:
containers:
- name: httpd
image: httpd:2.4
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: httpd-service
spec:
selector:
app: demo-httpd
ports:
- port: 80
targetPort: 80
Ingress を、ホスト名つきのルール 2 つに書き換える。
# demo-ingress-hosts.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: demo-ingress
spec:
rules:
- host: nginx.example.com # Host ヘッダーがこの名前なら demo-service へ
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: demo-service
port:
number: 80
- host: httpd.example.com # この名前なら httpd-service へ
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: httpd-service
port:
number: 80
$ kubectl apply -f demo-httpd.yaml -f demo-ingress-hosts.yaml
deployment.apps/demo-httpd created
service/httpd-service created
ingress.networking.k8s.io/demo-ingress configured
$ kubectl get ingress
NAME CLASS HOSTS ADDRESS PORTS AGE
demo-ingress <none> nginx.example.com,httpd.example.com 80 12m
nginx.example.com も httpd.example.com も実在しない名前なので、curl の -H で Host ヘッダーだけを付けて、宛先は localhost のまま打つ。
$ curl -s -H 'Host: nginx.example.com' http://localhost/ | grep title
# => <title>Welcome to nginx!</title>
$ curl -s -H 'Host: httpd.example.com' http://localhost/ | grep title
# => <title>It works! Apache httpd</title>
$ curl -si http://localhost/ | head -1
# => HTTP/1.1 404 Not Found
Host ヘッダー | 届く先 |
|---|---|
nginx.example.com | demo-service → nginx の Pod |
httpd.example.com | httpd-service → httpd の Pod |
指定なし(localhost) | どのルールにも当てはまらず、Envoy が 404 を返す |
同じ localhost:80 に来たリクエストが、ホスト名で 2 つの Service に分かれた。本番では DNS で両方の名前を同じ入口の IP に向けておき、1 つの入口で複数のアプリを公開する。
消す
$ kubectl delete -f demo-ingress-hosts.yaml -f demo-httpd.yaml -f demo-service-nodeport.yaml -f demo-deployment.yaml -f client.yaml
ingress.networking.k8s.io "demo-ingress" deleted from default namespace
deployment.apps "demo-httpd" deleted from default namespace
service "httpd-service" deleted from default namespace
service "demo-service" deleted from default namespace
deployment.apps "demo-app" deleted from default namespace
pod "client" deleted from default namespace
Contour も消すときは、入れたときと同じ YAML で kubectl delete -f https://projectcontour.io/quickstart/contour.yaml。
用語メモ
| 用語 | 読み方 | 意味 |
|---|---|---|
| ClusterIP | クラスター アイピー | Service に付く固定の IP(10.96.x.x)。クラスタの中からだけ届く。type を書かないときの Service の種類 |
| NodePort | ノード ポート | Service の種類。ClusterIP に加えて、全部の Node の同じポート(30000〜32767)でも受ける |
| LoadBalancer | ロード バランサー | Service の種類。NodePort に加えて、クラウドのロードバランサーを作ってもらい、その IP で受ける。kind には作るプログラムがいないので EXTERNAL-IP が <pending> のまま |
| extraPortMappings | エクストラ ポート マッピングス | kind の設定。Mac のポートを Node(Docker コンテナ)のポートにつなぐ。Kubernetes の設定ではない |
| Ingress | イングレス | HTTP 専用の振り分けルール。「ホスト名 / パス → Service とポート」。読むプログラムがいないと何も起きない |
| Ingress Controller | イングレス コントローラー | Ingress を読んで、リバースプロキシを設定して振り分けるプログラム。Kubernetes に入っていないので自分で入れる |
| リバースプロキシ | ─ | 外からのリクエストを受け取って、後ろにいるサーバーのどれかに代わりに渡すプログラム。nginx、Envoy、HAProxy など |
| Contour | コンツアー | Ingress Controller の 1 つ。contour(Ingress を読む Deployment)と envoy(リバースプロキシの DaemonSet)の 2 種類の Pod で動く |
| Envoy | エンボイ | リバースプロキシ。Contour の中で HTTP を受けて転送している本体 |
| pathType | パス タイプ | Ingress のパスの条件の種類。Prefix(/ で区切った前方一致)、Exact(完全一致)。必須 |
まとめ
- クラスタの中からは、Service の名前(
demo-service)で Pod に届く - クラスタの外から Node に届く経路は Kubernetes の外にある。kind では
extraPortMappingsで Mac のポートを Node のポートにつなぐ。クラウドではロードバランサーがその役 - Service の
type: NodePortは、全部の Node の同じポートで受ける。ポート番号 1 つにつき Service 1 つ - Ingress は「ホスト名 / パス → Service」の HTTP 専用のルール。作っただけでは何も起きず、Ingress Controller を入れると動く
- Ingress Controller は Kubernetes に入っていない。Contour なら
kubectl apply1 つで入り、Ingress を読む Pod と HTTP を受けて転送する Pod(Envoy)が動く - 1 つの入口(
localhost:80)に来たリクエストを、Hostヘッダーで別々の Service に振り分けられる
参考 URL
- [official] Service | Kubernetes
- [official] Ingress | Kubernetes
- [official] Ingressコントローラー | Kubernetes
- [official] ServiceとPodに対するDNS | Kubernetes
- Configuration | kind
- Getting Started | Contour
[Prev] Rolling Update / Rollback: Kubernetes でアプリを止めずに Pod を入れ替える

