Kubernetes における Ingress: アプリケーションを世界に公開する

1. 概要

cluster 内で Service が動作していても、外部のユーザーが api.nonladev.com のような美しいドメインでアクセスしたい場合、混雑した IP:port ではなく、これは Ingress が必要な時です。このガイドでは、Ingress とは何か、Service 型 LoadBalancer との違い、Ingress Controller のインストール方法、そして一般的なシナリオ(パスベースのルーティング、ホストベースのルーティング、TLS)に対応する Ingress リソースの書き方を説明します。最後には、Spring Boot アプリケーションをドメインと HTTPS で公開できるようになります。

2. なぜ Ingress が必要なのか

2.1. Service の問題点

Service 型の NodePort または LoadBalancer は機能しますが、制限があります:

  • LoadBalancer:各 Service に外部 IP が必要 → 多くのサービスがある場合、コストがかかります(各クラウド LB は個別に課金)。
  • NodePort:奇妙なポート(30000-32767)を公開、TLS ターミネーションがない、ドメイン/パスに基づくルーティングがない。

例えば、公開する必要がある 5 つのマイクロサービスがある場合、LoadBalancer を使用すると 5 つのクラウドロードバランサーと 5 つの外部 IP が必要になります。Ingress は、ホスト名またはパスに基づいて、単一のエントリーポイントを使用して内部の複数のサービスにトラフィックをルーティングすることで、これを解決します。

2.2. Ingress とは何か

Ingress は、外部から cluster 内の Service へのトラフィックに対して HTTP/HTTPS ルーティングルール を定義する API オブジェクトです。Ingress 自体は何もしません — ルールを読み取り実行するために、Ingress Controller(NGINX、Traefik、HAProxy などのリバースプロキシを実行する Pod)が必要です。

Client → Ingress Controller (LoadBalancer/NodePort) → Ingress rules → Service → Pod

注記: cluster に Ingress Controller がインストールされていない場合、Ingress リソースを作成しても何も効果がありません — それは単なる設定で、controller が読み込むのを「待機」しています。

3. Ingress Controller のインストール

最も一般的なのは ingress-nginx です。Helm を使用してインストールします:

<!-- 適用外 — Helm/kubectl を使用、Maven dependency ではない -->
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update
helm install ingress-nginx ingress-nginx/ingress-nginx \
  --namespace ingress-nginx --create-namespace

このコマンドは ingress-nginx namespace を作成し、外部からのトラフィックを受け取るために Ingress Controller と Service 型 LoadBalancer をデプロイします。確認してください:

kubectl get pods -n ingress-nginx
kubectl get svc -n ingress-nginx
NAME                                       TYPE           EXTERNAL-IP     PORT(S)
ingress-nginx-controller                   LoadBalancer   34.123.45.67    80:31234/TCP,443:31235/TCP

この外部 IP は、cluster 内のすべての Ingress の唯一のエントリーポイントです。

4. Deployment とサンプル Service

Ingress を書く前に、ルーティング先の Service が必要です。order-service アプリケーションを想定します:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-service
spec:
  replicas: 2
  selector:
    matchLabels:
      app: order-service
  template:
    metadata:
      labels:
        app: order-service
    spec:
      containers:
        - name: order-service
          image: nonladev/order-service:1.0
          ports:
            - containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: order-service
spec:
  selector:
    app: order-service
  ports:
    - port: 80
      targetPort: 8080

Service order-service はポート 80 を公開し、コンテナーポート 8080 に転送します。このサービスは LoadBalancer 型である必要はなく、ClusterIP(デフォルト)で十分です。Ingress Controller は内部ネットワーク経由で Service を呼び出すためです。

5. 基本的な Ingress リソース — ホストベースのルーティング

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: order-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  ingressClassName: nginx
  rules:
    - host: api.nonladev.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: order-service
                port:
                  number: 80

各部分の説明:

  • ingressClassName: nginx — このルールを処理するコントローラを指定します(cluster に複数の Ingress Controller がある場合は重要)。
  • rules.host — ヘッダー Host: api.nonladev.com を持つトラフィックのみがこのルールにマッチします。
  • path + pathType: Prefix — / で始まるすべてのリクエスト(つまり全体)が order-service:80 に転送されます。
  • annotation rewrite-target — ingress-nginx 固有で、転送前に URL を書き直すために使用されます(controller によってアノテーションが異なります)。

適用およびテスト:

kubectl apply -f order-ingress.yaml
curl -H "Host: api.nonladev.com" http://34.123.45.67/
{"service":"order-service","status":"UP"}

本番環境では、DNS レコード api.nonladev.com を Ingress Controller の外部 IP にポイントし、上記のような偽の Host ヘッダーは必要ありません。

6. パスベースのルーティング — 複数のサービスが同じドメインを共有

一般的なシナリオ:1 つのドメイン、異なるパスに複数のサービス。

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: api-gateway-ingress
spec:
  ingressClassName: nginx
  rules:
    - host: api.nonladev.com
      http:
        paths:
          - path: /orders
            pathType: Prefix
            backend:
              service:
                name: order-service
                port:
                  number: 80
          - path: /customers
            pathType: Prefix
            backend:
              service:
                name: customer-service
                port:
                  number: 80

api.nonladev.com/orders/123 へのリクエストは order-service にルーティングされ、/customers/55 は customer-service にルーティングされます。pathType: Prefix は接頭辞でマッチすることを意味します — Exact(完全一致が必要)や ImplementationSpecific(controller が独自に解釈、通常は正規表現用)とは異なります。

注記: Ingress v1 ではパスの順序はマッチングに影響しません — controller は最長マッチを自動的に選択します。ただし、パスを明確に書き、重複しないようにして、デバッグが難しいバグを避けてください。

7. TLS — Ingress の HTTPS

HTTPS を持つには、cert と key を含む Secret が必要で、その後 Ingress で参照します。

kubectl create secret tls nonladev-tls \
  --cert=tls.crt --key=tls.key
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: order-ingress-tls
spec:
  ingressClassName: nginx
  tls:
    - hosts:
        - api.nonladev.com
      secretName: nonladev-tls
  rules:
    - host: api.nonladev.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: order-service
                port:
                  number: 80

spec.tls はどのホストがどの証明書を使用するかを宣言します。Controller は自動的に TLS ターミネーションを行います — つまり、Ingress で HTTPS を復号化してから、内部の Service にプレーン HTTP を転送します。Pod は TLS について知る必要がありません。

実際には、通常、手動で Secret を作成する代わりに、cert-manager を使用して Let’s Encrypt 証明書を自動的にリクエストします:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: order-ingress-tls
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
  ingressClassName: nginx
  tls:
    - hosts:
        - api.nonladev.com
      secretName: nonladev-tls
  rules:
    - host: api.nonladev.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: order-service
                port:
                  number: 80

Annotation cluster-issuer は cert-manager に Secret nonladev-tls を自動的に作成および更新するよう要求します — 手動での追加操作は不要です。

8. 公開後のエンドポイント検証テストの記述

Ingress が動作した後、controller クラスがパスを正しく処理することを検証する簡単な統合テストを作成できます(Spring Boot アプリケーション層でテストし、YAML を直接テストしません):

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class OrderControllerIT {

    @Autowired
    private TestRestTemplate restTemplate;

    @Test
    void shouldReturnOrderWhenCalledWithOrdersPrefix() {
        ResponseEntity<String> response =
                restTemplate.getForEntity("/orders/123", String.class);

        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
        assertThat(response.getBody()).contains("orderId");
    }
}

このテストは、Spring Boot アプリケーションが Ingress が書き直す正しいパス /orders/123 に応答することを保証します — バックエンドコードの問題と Ingress 設定の問題を分離します。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

9. 一般的なエラー

  • ingressClassName を忘れる:cluster に複数の Ingress Controller がある場合(例えば nginx と traefik の両方)、Ingress はどの controller が処理するかが不明確になります → 機能しないか、両方によって誤って処理されます。
  • Service 型が間違っている:バックエンド Service を LoadBalancer のままにする必要はなく、無駄になります — ClusterIP で十分です。
  • pathType が期待に合わない:Prefix が必要な場合に Exact を使用すると、サブリクエスト(/orders/123)が 404 になります。/orders のみが正確に一致するためです。
  • DNS がまだポイントされていない:実際の DNS 設定を行う前に curl -H "Host: ..." でテストし、DNS 伝播を待つ時間を無駄にしないようにしてください。
  • Rewrite annotation が間違ったコントローラー向け:ingress-nginx のアノテーションは Traefik や AWS ALB Ingress Controller では機能しません — 各コントローラーは独自のアノテーション セットを持っており、対応する文書を注意深く読んでください。

**Ingress を使用すべきでない時:**アプリケーションが HTTP/HTTPS 以外のプロトコルを使用している場合(例えば、raw TCP、複雑な gRPC ストリーミング、または UDP)、標準 Ingress は十分にサポートしません — TCP/UDP の場合は Gateway API または Service 型 LoadBalancer の使用を検討してください。

10. 結論

Ingress は集中化された HTTP/HTTPS ルーティング層であり、複数のサービスを単一のエントリーポイント経由で公開でき、LoadBalancer コストを削減し、ドメイン/パス/TLS 管理が容易になります。覚えておくべき重要なポイント:Ingress リソースはただのルールであり、実行には Ingress Controller が必要です;ingressClassName と pathType を正しく選択すること;TLS を自動化するために cert-manager を使用すること。HTTP 以外のアプリケーションの場合、Ingress を無理に使うのではなく Gateway API を検討してください。

kubernetesingressnetworkingdevopsnginx