3
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

作ってわかる! Ingress

Last updated at Posted at 2024-08-31

CKAに合格してもIngressがよくわからない...

Kubernetesを学ぶと、最初の方でサービス系の基本的なリソースとしてPod, Deployment, Serviceが出てきます。ここまで知って、「Deploymentを使って複数のPodを作って冗長化して、Serviceで複数のPodにトラフィックをよしなに送ってくれるんだね。動いていないPodがあったらそこには通信しない仕組みもあるんだね。まあなんと素敵!!」と多くの人が(おそらく)感動します。

さらにConfigMapとかSecretとかPV,PVCとかがある程度わかってくると、「もうだいたいKubernetesわかってきたんじゃね?自分で作れるんじゃね!?」という気分になってきます。

そんな高揚感を打ち砕くように突如現れるのが、Ingress

「うっ...、Ingressかぁ... Ingressねぇ...」

CKAやCKADで試験勉強をした場合、KodeKloudやkiller.shでIngressの問題が出てきます。よくわかっていないながらも何回かやっていくうちに一応解けるようになり、晴れてCKA合格!

だけど、Ingressに関してはずっと自信が持てない。。。
私にとってのIngressはそんな存在でした。

そこで本記事は、Ingressを使う必要性を考察しつつ、オンプレKubernetes上で実際にIngressリソースを作り理解を深めることを目指して執筆します。

なぜIngressを使うのか?

Ingressの説明については、以下のkubernetes.ioをご参照ください。

上の記事には「Ingressの機能として負荷分散、SSL終端、名前ベースの仮想ホスティングの3つがある」と書かれていますが、これらはIngressを使わなくても、例えばnginxで実現できます。nginxの設定ファイル(nginx.conf等)で、負荷分散はlocationディレクティブ内、SSL終端は ssl_ で始まる設定値、名前ベースの仮想ホスティングはserver_nameにホスト名を設定すればOKです。
なので別にIngressを使わなくても、nginxのDeploymentとServiceを立ち上げれば同じことができるわけです。

ではなぜIngressを使うのか?

それは、Ingressを使うと「負荷分散」「SSL終端」「名前ベースの仮想ホスティング」等の設定をKubernetesマニフェストでシンプルに設定でき、汎用性も上がるからです。

もしリバースプロキシ的な機能をIngressではなくて、nginx単体で実装しようとするとどうなるでしょうか。
この場合、ConfigMapにnginx.confを自分で用意して、nginxのDeploymentにそれをマウントする手間が必要になります。また、nginx以外のリバースプロキシツール(TraefikやApache等)を使いたくなった場合に、設定ファイルを一から作り直さなければなりません。場合によっては、新しく使うツールの設定ファイルの仕様の調査から必要になってしまうでしょう。

これをIngressで実現すると、使うリバプロツールによらずに全く同じ書き方で、負荷分散やSSL終端等の設定を表現できます。

以下は簡単なIngressのマニフェストの例です。

simple-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: simple-ingress
  namespace: default
spec:
  ingressClassName: nginx # ここでリバプロツールを指定している
  rules:
  - host: example.com     # 「名前ベースの仮想ホスティング」の設定
    http:
      paths:
      - path: /           # 「負荷分散」の設定
        pathType: Exact
        backend:
          service:
            name: example-service
            port:
              number: 80
  tls:                    # 「SSL終端」の設定
  - hosts:
    - example.com
    secretName: cert

マニフェストにもコメントしていますが、どのリバプロツールを使うかは spec.ingressClassName で指定できます。例えばnginxからTraefikに変えたくなった場合は、ingressClassName: traefik と書き換えるだけでおおかた済みます。Ingressを使って非常にシンプルになるのがおわかりいただけますでしょうか!

尚、後述しますが、nginxやTraefikを使うためには各々の「Ingressコントローラー」のインストールが必要です。使えるIngressコントローラーは以下をご参照ください。

Ingressを作ってみる

それでは実際にIngressを作ってみましょう。

事前状況の確認

kubectlコマンドで、IngressのリソースはKubernetesのデフォルトで使える状態であることがわかります。

$ kubectl api-resources | grep -e ^NAME -e networking
(出力結果は少し編集)
NAME             SHORTNAMES  KIND
ingressclasses               IngressClass
ingresses        ing         Ingress
networkpolicies  netpol      NetworkPolicy

しかしながら、IngressコントローラーをインストールしていないのでIngressを正常にデプロイすることはできません。Ingressコントローラーをインストールするとそれに対応するIngressClassリソースが作成されますが、初期状態だと以下のようにリソースが存在しないです。

$ kubectl get ingressclasses
No resources found

そこでまずは、何かしらのIngressコントローラーのインストールが必要となります。

Ingress NGINX Controller のインストール

今回は、nginxのIngressコントローラーをHelmチャートでインストールします。
HelmチャートのインストールはHelmfileが便利です。以下のYAMLファイルをhelmfileコマンドでインストールします。

helmfile-ingress-nginx.yaml
repositories:

- name: ingress-nginx
  url: https://kubernetes.github.io/ingress-nginx

releases:

- name: ingress-nginx
  namespace: ingress-nginx
  createNamespace: true
  chart: ingress-nginx/ingress-nginx
  version: 4.11.2

実行コマンド

helmfile apply -f helmfile-ingress-nginx.yaml

上記を実施するには、helm および helmfile のインストールが必要です。

間もなくして、ingress-nginx-controller のリソースが作成されます。

$ kubectl get deploy -n ingress-nginx
NAME                       READY   UP-TO-DATE   AVAILABLE   AGE
ingress-nginx-controller   1/1     1            1           22s
$ kubectl get svc -n ingress-nginx
NAME                                 TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)                      AGE
ingress-nginx-controller             LoadBalancer   10.110.19.229   <pending>     80:31958/TCP,443:31030/TCP   22s
ingress-nginx-controller-admission   ClusterIP      10.102.14.65    <none>        443/TCP                      22s

IngressClassにも、ingress-nginx-controllerに対応するリソースが追加されたことがわかります。

$ kubectl get ingressclasses
NAME    CONTROLLER             PARAMETERS   AGE
nginx   k8s.io/ingress-nginx   <none>       4m4s

先にお伝えしておくと、この後作成するIngressに宛てたリクエストはIngressコントローラーを経由してバックエンドのリソースに届きます。少しくどいですが、上の例だとIngressコントローラーは「ingress-nginx-controller のServiceリソース」です。

したがって、疎通はingress-nginx-controllerのNodePortを使って以下のように確認できます。
※ 【注意!】現時点ではまだIngressを作っていないのでつながりません

httpの場合

curl http://[hostname]:31958

httpsの場合

curl https://[hostname]:31030

バックエンド用のリソースの作成

最初にバックエンド用のリソースを作成します。マニフェストで作るのが面倒なので、以下のコマンドでnginxとhttpdのDeployent,およびServiceリソースを作ります。

kubectl create deployment backend-nginx --image=nginx --replicas=1
kubectl expose deployment backend-nginx --port=80 --target-port=80 --name=backend-nginx
kubectl create deployment backend-httpd --image=httpd --replicas=1
kubectl expose deployment backend-httpd --port=80 --target-port=80 --name=backend-httpd

リソースが作成できたことを確認します。

$ kubectl get po -n default -l app
NAME                             READY   STATUS    RESTARTS   AGE
backend-httpd-5f8458bbcd-nchlb   1/1     Running   0          13s
backend-nginx-8cb49db7d-d2jf2    1/1     Running   0          25s

$ kubectl get svc -n default -l app
NAME            TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)   AGE
backend-httpd   ClusterIP   10.110.192.166   <none>        80/TCP    14s
backend-nginx   ClusterIP   10.97.122.179    <none>        80/TCP    23s

Ingressの作成

準備は整ったので、お待ちかねのIngressリソースを作成します。

IngressもYAMLを書かずとも、以下のようなコマンドで一応作れます。

kubectl create ingress \
ing-multipath \
-n default \
--class=nginx \
--annotation nginx.ingress.kubernetes.io/rewrite-target=/ \
--rule="/nginx=backend-nginx:80" \
--rule="/httpd=backend-httpd:80"

上記コマンドの最後に --dry-run=client -o yaml を加えてYAMLで出力すると、次のようなマニフェストになるのを確認できます。

ing-multipath.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: / # 後で説明
  creationTimestamp: null
  name: ing-multipath
  namespace: default
spec:
  ingressClassName: nginx
  rules:
  - http:
      paths:
      - backend:
          service:
            name: backend-nginx
            port:
              number: 80
        path: /nginx
        pathType: Exact
      - backend:
          service:
            name: backend-httpd
            port:
              number: 80
        path: /httpd
        pathType: Exact

nginx.ingress.kubernetes.io/rewrite-target: / というannotationは、バックエンドにリクエストを転送する際にパスを書き換えるのに必要です。例えば /nginxのパスで来たリクエストは、backend-nginxのServiceには / のパスに書き換えられて転送されます。これを書いておかないと、backend-nginxにも/nginxのパスでリクエストされて "404 Not Found" が返ってきます。(おそらく多くの人が一度はここでハマるはず...)

各Ingressコントローラーに固有の設定は、上の例のように annotation に設定を書く必要があります。本格的に業務でIngressを使うことになった場合、 annotation を活用することが必須になるので、是非覚えておいてください!

さて、以下のようにIngressリソースが作成されました。

$ kubectl get ing -n default
NAME            CLASS   HOSTS   ADDRESS   PORTS   AGE
ing-multipath   nginx   *                 80      7s

実際にリクエストして、バックエンドにリソースが届くかを確認してみましょう。

今回作っているk8sクラスターのノードは一つで、IPアドレスが 10.0.0.4です。

$ kubectl get node -owide | awk '{print $1 " " $5 " " $6}' | column -t
NAME         VERSION  INTERNAL-IP
ip-10-0-0-4  v1.29.8  10.0.0.4

そこで、10.0.0.4とhttpのNodePortを目がけてcurlを叩いてみます。

$ curl http://10.0.0.4:31958/nginx 2>/dev/null | head -n 4
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
$ curl http://10.0.0.4:31958/httpd 2>/dev/null | head -n 4
<html><body><h1>It works!</h1></body></html>

nginxとhttpdのバックエンドに、見事にリクエストが届きました!

補足:パスの置き換え規則について

上の例では、バックエンドのパスの置き換え規則を次のように書きました。

metadata:
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec
  rules:
  - http:
      paths:
      - path: /nginx
        pathType: Exact
        ...

これだとバックエンドのpathが /nginx 一つであれば問題ないですが、例えば /nginx/about-us.html/nginx/contact.html のように、/nginx/ の下に複数のサイトがあるとうまく動きません。
この場合は次のように、rewrite-targetpathに正規表現を使うことで解決します。

metadata:
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /$2 # pathの正規表現でマッチした2番目のグループのテキストに書き換える
spec
  rules:
  - http:
      paths:
      - path: /nginx(/|$)(.*)            # 正規表現にする
        pathType: ImplementationSpecific # 後述
        ...

pathType: ImplementationSpecificドキュメントを見ると「このpathTypeはIngressClassに依存する」と書かれているので、ingress-nginx-controllerがよしなに処理してくれるものと思われます。
pathType: Prefixにしても動きはしたのですが、Ingress作成時に以下のワーニングが出たので、ImplementationSpecificに変更しました。

Warning: path /nginx(/|$)(.*) cannot be used with pathType Prefix

Ingressのカスタマイズ

上で作った ing-multipath.yaml のマニフェストをカスタマイズする事例を紹介します。

ホスト名を指定

spec.rules[].host でクライアントがリクエストするホスト名を指定できます。

...
spec:
  ingressClassName: nginx
  rules:
  - http:
      paths:
        ...
    host: example.com # 追加

この場合、クライアントはIPアドレスではなく、次のようにexample.comでリクエストする必要があります。

curl http://example.com:31958/nginx

example.com はクライアント側で名前解決してノードのIPアドレス(今回だと10.0.0.4)に変換する必要があります。動作検証が目的であれば、/etc/hosts10.0.0.4 example.com を一行追記するのが手軽でしょう。

名前解決しなくても、以下のようにヘッダーにホスト名を追加することでもリクエスト可能です。

curl http://10.0.0.4:31958/nginx -H "Host: example.com"

ちなみにexample.comを指定せずにIPアドレスでリクエストすると、ingress-nginx-controllerでホスト名が不正とみなされて404エラーになります。

$ curl -I http://10.0.0.4:31958/nginx 2>/dev/null | head -n 1
HTTP/1.1 404 Not Found

SSL証明書を使ってhttpsでリクエスト

spec.tls にSSL(TLS)に関する情報を追記すると、httpsでセキュアに通信可能になります。

spec:
  ...
  tls:                           # ここ以降は追加した行
  - hosts:
    - example.com
    secretName: cert-selfsigned

secretName で指定するSecretリソースにはドキュメントを参照すると、以下のようにSSL通信用のサーバ証明書と秘密鍵を格納する必要があります。

$ kubectl get secret -n default cert-selfsigned -oyaml | cut -c 1-48 | head -n 6
apiVersion: v1
kind: Secret
data:
  ca.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk # CA証明書(必須ではないかも)
  tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tC # サーバ証明書
  tls.key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktL # 秘密鍵

SSLに関する証明書を自分で作成するのはだいぶ面倒臭いですが、例えば cert-manager を使うと自己(オレオレ)証明書であれば割と簡単に作れます。(作り方の詳細は本題から外れるため割愛いたします)

SSLに関するSecretリソースをIngressのマニフェストに追記して反映すると、以下のコマンドでhttpsでのリクエストが可能になります。
※ ちなみに -kオプションは、SSL/TLS証明書の検証を無効化するためのオプションです。

curl -k https://10.0.0.4:31030/nginx

余談ですが、IngressコントローラーによってはSSLに関する設定を spec.tls ではなくて、annotationで行うものもあります。例えばAWSのALB(Application Load Balancer)の場合は、alb.ingress.kubernetes.io/certificate-arn の annotation で指定します。

IngressコントローラーをTraefikに変えてみる

続いて、Traefikを二つ目のIngressコントローラーとして作ってみます。
Ingress NGINX Controllerと同様に、以下のHelmfileを使って作成します。

helmfile-ingress-traefik.yaml
repositories:

- name: traefik
  url: https://traefik.github.io/charts

releases:

- name: traefik
  namespace: ingress-traefik
  createNamespace: true
  chart: traefik/traefik
  version: v30.1.0

以下のコマンドを実行します。

helmfile apply -f helmfile-ingress-traefik.yaml

TraefikのIngressコントローラーが作成されたことを確認します。

$ kubectl get deploy -n ingress-traefik
NAME      READY   UP-TO-DATE   AVAILABLE   AGE
traefik   1/1     1            1           11s

$ kubectl get svc -n ingress-traefik
NAME      TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)                      AGE
traefik   LoadBalancer   10.105.179.76   <pending>     80:30883/TCP,443:32586/TCP   16s

80:30883/TCP,443:32586/TCP と書かれているので、httpだと30883のポート、httpsの場合は32586のポートにアクセスすれば問題なさそうです。

また、IngressClassにも traefik が追加されているのを確認できます。

$ kubectl get ingressclasses
NAME      CONTROLLER                      PARAMETERS   AGE
nginx     k8s.io/ingress-nginx            <none>       26h
traefik   traefik.io/ingress-controller   <none>       33s

準備ができたので、Traefik用のIngressリソースのマニフェストを作ります。

ing-multipath-traefik.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ing-multipath-traefik
  namespace: default
  annotations:
    # nginxのIngressの "nginx.ingress.kubernetes.io/rewrite-target"
    # と同等のアノテーション(詳細は後述)
    traefik.ingress.kubernetes.io/router.middlewares: default-stripprefix@kubernetescrd
spec:
  ingressClassName: traefik # nginxから変更
  rules:
  - http:
      paths:
      - backend:
          service:
            name: backend-nginx
            port:
              number: 80
        path: /nginx
        pathType: Exact
      - backend:
          service:
            name: backend-httpd
            port:
              number: 80
        path: /httpd
        pathType: Exact

nginxのIngressからの変更点は「annotations のKeyValue」と「ingressClassName の値」、この2つだけです。

尚Traefikでは、パスを書き換えるのにMiddlewareリソースを別途作る必要があります。以下のように、Middlewareのマニフェストにおいてspec.stripPrefixで削除したいプレフィックス列挙するものを作ります。

ing-multipath-traefik-middleware.yaml
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
  name: stripprefix
  namespace: default
spec:
  stripPrefix:
    prefixes:
      - "/nginx"
      - "/httpd"

Ingressリソースのannotationで、作成したMiddlewareを以下のように参照する必要があります。

traefik.ingress.kubernetes.io/router.middlewares: default-stripprefix@kubernetescrd

値はどうやら [namespace]-[resource-name]@kubernetescrdのように書く必要がありそうです。

上記のIngressおよびMiddlewareのリソースを作成すると、以下のようにTraefikのIngressコントローラーでもバックエンドにリクエストが届くことを確認できます!

$ curl http://10.0.0.4:30883/nginx 2>/dev/null | head -n 4
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
$ curl http://10.0.0.4:30883/httpd 2>/dev/null | head -n 4
<html><body><h1>It works!</h1></body></html>

おわりに

実際にIngressを手を動かして作ってみることで、少し理解が進みました。
本記事の最初の方で、「もしIngressを使わなかったら、nginx.confのConfigMapを用意して、Deploymentにマウントする必要がある。これは手間だしイケていない」と書きましたが、実は私が個人で動かしているKubernetesクラスターが今その(イケていない?)作りになっています。なので近々Ingressに書き換えようかな。
そして次は、Istioを理解することを目指します!

3
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?