【Kubernetes】Service Ingress is 何

## はじめに
本記事ではKubernetesリソースのService、Ingressについて解説します。
また、AWS LoadBalancer Controllerについても解説します。


## 全体像
はじめに各リソースを説明する上で、全体像を先に提示します。
順を追って、この全体像を基に各リソースをマニフェストファイルの内容とともに解説していきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/379725/61866952-6c29-4227-a694-97dc3178d3e9.png)





## Service とは
Kubernetesクラスター内で複数のPodとして実行されているアプリケーションを公開するためのリソースです。
かつ、「複数のPodとして」という説明の通り、冗長化されたPodに対してトラフィックを分散させる役割も持っています。

マニフェストファイルの例
```yaml
apiVersion: v1
kind: Service
metadata:
  name: my-service-80
  namespace: app
  annotations:
      alb.ingress.kubernetes.io/healthcheck-path/: '/api/health'
      alb-ingress.kubernetes.io/target-type: ip
spec:
  selector:
    app: my-app # my-appというPodに対してトラフィックを転送する
  ports:
    - protocol: TCP
      port: 80      # Serviceが公開するポート
      targetPort: 9376  # Podがリッスンしているポート
```
上記マニフェストの場合、`my-service-80`という名前のServiceが定義され、`spec.selector.app`で指定されたラベルに一致する全てのPodに対してトラフィックを分散します。

:::note warn
アプリケーションを公開するとは`port: 80`と定義することで、`port: 80`でクラスター内部に対して公開する、という意味になります。
後続のPodが冗長化している場合、Serviceでロードバランサーの役割を担い、負荷分散させてトラフィックを流す必要があるため、必須のリソースとなります。

注意点としては外部に対して公開しているわけではいので、http://PodのIPアドレス:80 でアクセスできるわけではありません。
:::

`Deployment`のマニフェストファイルで定義されたyaml内に、`replicas: 2`のように定義されている場合、`my-app`ラベルのPodは2つに冗長化されていることになりますが、その2つのPodに対してトラフィックを分散することになります。

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app-deployment-80
spec:
  replicas: 2  # Podのレプリカ数
  selector:
    matchLabels:
      app: my-app
      tier: backend
```
複数のPodに対してトラフィックを分散させることは可能ですが、`targetPort`が異なる場合、Serviceはそれぞれの`targetPort`に向けたServiceが必要になります。

> `my-app`ラベルのPodは2つに冗長化されていることになりますが、その2つのPodに対してトラフィックを分散することになります。

上記で説明した2つに冗長化されているPodとは同じ`targetPort`に向けた同じ機能を持つPodという意味です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/379725/dbb7ccd8-8a07-4979-8867-c1621eac659b.png)


### 異なるtargetPortに向けたService
異なる`targetPort`に向けてトラフィックを転送する場合にはそれぞれServiceが必要になります。

以下のマニフェストファイルのようにServiceを2つ定義してそれぞれの`Port`で`Listen`しているPodにトラフィックを転送します。
```yaml
apiVersion: v1
kind: Service
metadata:
  name: my-service-80
  namespace: app
  annotations:
      alb.ingress.kubernetes.io/healthcheck-path/: '/api/health' # ヘルスチェックパス
      alb-ingress.kubernetes.io/target-type: ip # ターゲットタイプ
spec:
  selector:
    app: my-app # my-appというPodに対してトラフィックを転送する
    tier: backend # バックエンドを示すラベル
  ports:
    - protocol: TCP
      port: 80      # Serviceが公開するポート
      targetPort: 9376  # Podがリッスンしているポート
---
apiVersion: v1
kind: Service
metadata:
  name: my-service-81
  namespace: app
  annotations:
      alb.ingress.kubernetes.io/healthcheck-path/: '/api/health'# ヘルスチェックパス
      alb-ingress.kubernetes.io/target-type: ip # ターゲットタイプ
spec:
  selector:
    app: my-app # my-appというPodに対してトラフィックを転送する
    tier: backend # バックエンドを示すラベル
  ports:
    - protocol: TCP
      port: 81      # Serviceが公開するポート
      targetPort: 9377  # Podがリッスンしているポート
```

しれっと`namespace`, `annotations`以下の内容が存在していますが、ここについても解説します。
```yaml
  namespace: app
  annotations:
      alb.ingress.kubernetes.io/healthcheck-path/: '/api/health'
      alb-ingress.kubernetes.io/target-type: ip
```

### namespace: app 
クラスターに所属するNamespaceを指定しています。この場合`app`というNamespace内にこのServiceは存在します。

### annotations
Serviceに追加の設定をする場合に記述します。ここではAWS LoadBalancer Controllerに関するアノテーションを指定しています。
AWS LoadBalancer Controllerについては後述します。

### alb.ingress.kubernetes.io/healthcheck-path/: '/api/health' 
ヘルスチェックのパスを指定します。Serviceにおいて`healthcheck-path`を定義していますが、実際にヘルスチェックが行われるのはPodです。
ALBは`'/api/health'`に対してHTTPリクエストを定期的に送り続け、Serviceがそのリクエストを受信し、後続のPodに転送します。Podがリクエストを受信し、応答が正常であればHTTPステータスコード200を返し、`healthy`状態であることを確認します。

### alb-ingress.kubernetes.io/target-type: ip
ALBがトラフィックを転送する先のターゲットタイプを`ip`を指定してトラフィックを転送します。
ALBがPodのIPアドレスに対してトラフィックを流します。


## Ingress とは
Ingressはクラスター外部からクラスター内のServiceに対してトラフィック(HTTP/HTTPS)を転送する役割を担います。
簡単に言うと、外部からのアクセスを適切なServiceに振り分ける仕組みです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/379725/2e9996b1-863a-4c02-8894-72eab63e2643.png)
Ingressを使用する場合、前提としてIngressコントローラーが必要になります。
Ingressコントローラーには様々なコントローラーが存在します。
詳細の内容はKubernetes公式に記載があります。

https://kubernetes.io/ja/docs/concepts/services-networking/ingress-controllers/

本記事では`NGINX Ingress Controller`, `AWS Load Balancer Controller`が登場するため、この2つのコントローラーに絞って内容を記載していきます。

Ingressの主な役割は以下です。

## 外部からのアクセスを管理
    - クラスター外からのHTTP/HTTPSリクエストを受け付け、適切なServiceに転送します

## パスベースのルーティング
    - リクエストのパスに応じて異なるServiceにトラフィックを振り分けます

例えば以下のようなIngressを定義した場合、`rules`セクションで定義している`path`に応じて、どのServiceにリクエストを転送するかを定義します。
`my-app.example.com`のようなドメインである場合、ユーザが`my-app.example.com/path1`でアクセスした場合、リクエストはALB -> Ingressの流れでリクエストが到達します。
Ingressは`rules`の内容に従って、`path1`を解釈し、`my-service-80`のServiceに対してリクエストを転送します。
`path2`である場合、`my-servce-81`のServiceに対してリクエストを転送します。
```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-ingress
spec:
  rules:
      http:
        paths:
          - path: /path1
            pathType: Prefix # `/`で分割されたURLと前方一致で一致、大文字小文字は区別される
            backend:
              service:
                name: my-service-80
                port:
                  number: 9376
          - path: /path2
            pathType: Prefix
            backend:
              service:
                name: my-service-81
                port:
                  number: 9377
```

## TLSの終端
    - こちらは以下を見るとわかりやすいです

ユーザからのリクエストでIngressまでが暗号化された通信となります。
Ingressで暗号化された内容を復号化し、平文をServiceに転送します。
```plaintext
+-------------------+       +-------------------+       +-------------------+       +-------------------+
|    ユーザー        |       |       ALB         |       |      Ingress      |       |      Service       |
|                   |       |                   |       |                   |       |                   |
|  HTTPSリクエスト   | ----> |  HTTPSリクエスト   | ----> |  HTTPSリクエスト   | ----> |  HTTPリクエスト     |
|  (暗号化)          |       |  (暗号化)         |       |  (暗号化)          |       |  (平文)            |
|                   |       |                   |       |                   |       |                   |
+-------------------+       +-------------------+       +-------------------+       +-------------------+
```
ただし、上記の流れは`NGINX Ingress Controller`を使用した場合の例となります。

```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-ingress
  annotations:
    # NGINX Ingress ControllerがTLS終端を行い、復号されたHTTPリクエストをServiceに転送する。
    nginx.ingress.kubernetes.io/ssl-redirect: "true" 
spec:
  tls:
      secretName: my-tls-secret
  rules:
    - host: my-app.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: my-service-80
                port:
                  number: 9376
```

`NGINX Ingress Controller`を使用した場合はTLSの終端がIngressとなりますが、`AWS LoadBalancer Controller`を使用した場合はTLSの終端はALBとなります。

`AWS LoadBalancer Controller`を使用したマニフェストファイルの例が以下です。
```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-ingress
  annotations:
    kubernetes.io/ingress.class: alb  # AWS Load Balancer Controllerを使用
    alb.ingress.kubernetes.io/scheme: internet-facing  # インターネット向けALB
    alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}, {"HTTPS":443}]'  # HTTPとHTTPSのリスナー
    alb.ingress.kubernetes.io/certificate-arn: arn:aws:acm:region:account-id:certificate/certificate-id  # TLS証明書のARN
    alb.ingress.kubernetes.io/actions.ssl-redirect: '{"Type": "redirect", "RedirectConfig": { "Protocol": "HTTPS", "Port": "443", "StatusCode": "HTTP_301"}}'  # HTTPをHTTPSにリダイレクト
spec:
  rules:
      http:
        paths:
          - path: /path1  # パス
            pathType: Prefix  # パスのマッチングタイプ
            backend:
              service:
                name: my-service-80  # 転送先のService
                port:
                  number: 9376  # Serviceのポート番号
```

ALBがTLSの終端となる場合に必要な記述は以下の`annotations`セクションとなります。


```yaml
  annotations:
    kubernetes.io/ingress.class: alb  # AWS Load Balancer Controllerを使用
    alb.ingress.kubernetes.io/scheme: internet-facing  # インターネット向けALB
    alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}, {"HTTPS":443}]'  # ALBがリッスンするポートを指定、HTTPとHTTPSのリスナー
    alb.ingress.kubernetes.io/certificate-arn: arn:aws:acm:region:account-id:certificate/certificate-id  # TLS証明書のARN
    alb.ingress.kubernetes.io/actions.ssl-redirect: '{"Type": "redirect", "RedirectConfig": { "Protocol": "HTTPS", "Port": "443", "StatusCode": "HTTP_301"}}'  # HTTPをHTTPSにリダイレクト
```

### alb.ingress.kubernetes.io/listen-ports
`'[{"HTTP": 80}, {"HTTPS":443}]'`の記載でALBがポート80, 443で`Listen`している状態です。


### alb.ingress.kubernetes.io/actions.ssl-redirect
リダイレクトの内容を記載しているため、仮に`http://my-app.example.com`でアクセスしてきた場合でも、ALBが80番ポートで`Linsten`しているため、ALBがHTTPS(ポート443)にリダイレクトします。

### alb.ingress.kubernetes.io/certificate-arn
ALBがパブリックに公開されている場合には、HTTPS通信を実現するためにACMを使用してTLS証明書を発行し、ALBに関連付けをすることで暗号化通信を実現するための記述です。


## AWS LoadBalancer Controller
ここまで温めてきたAWS LoadBalancer Controllerについて満を持してご説明します。
ここまで温めてきたのは最初にServiceとIngressの内容を理解した上で、AWS LoadBalancer Controllerの説明をした方が理解し易いためです。
AWS LoadBalancer Controllerをクラスターにインストールすることで、ServiceやIngressのリソースを基に、ALBやNLBを自動作成してくれるすぐれものコンポーネントです。

Ingressリソースでは前述してきた通り、Ingressのマニフェストファイルで`annotations`セクションにALBの内容を記載することでAWS LoadBalancer Controllerが自動的にALBを構築します。

では、NLBは誰が作るの？という話ですが、NLBはServiceリソースで定義することで作成されます。
マニフェストファイルの例が以下です。

```yaml
apiVersion: v1
kind: Service
metadata:
  name: my-service-80
  annotations:
    service.beta.kubernetes.io/aws-load-balancer-type: "nlb"  # NLBを作成
    service.beta.kubernetes.io/aws-load-balancer-internal: "true" 
spec:
  type: LoadBalancer
  selector:
    app: my-app
  ports:
    - protocol: TCP
      port: 80
      targetPort: 9376
```
### service.beta.kubernetes.io/aws-load-balancer-type
`annotations`セクションで`service.beta.kubernetes.io/aws-load-balancer-type: "nlb"`を指定することでServiceリソースを基にNLBが作成されます。


## 最後に
EKSクラスター上でAWS LoadBalancer ControllerをインストールしService, IngressをデプロイしALBの構築なども別記事でできたらと思っています。

以下AWS公式が出している内容を基にハンズオンしていくと良いかなと思っていますので、機会があれば執筆できたらとおもってい

https://aws.amazon.com/jp/blogs/news/exposing-kubernetes-applications-part-2-aws-load-balancer-controller/


## 参考
https://pages.awscloud.com/rs/112-TZM-766/images/AWS-Black-Belt_2024_Amazon-EKS-Introduction_1010_v1.pdf


https://kubernetes.io/ja/docs/concepts/services-networking/ingress/