Contour ではじめる Kubernetes Ingress

本記事は「[TUNA-JP Advent Calendar 2021](https://qiita.com/advent-calendar/2021/tuna-jp)」の2日目のエントリとして、Tanzu OSS の1つである Contour を解説します。

# Contour とは？
Contourは、[Envoy Proxy](https://www.envoyproxy.io/) をデータプレーンとする Kubernetes の Ingress Controller です。
Kubernetes の生みの親でもある、Craig McLuckie 氏とJoe Beda 氏が立ち上げた Heptio, Inc.（以下Heptio） によって OSS として開発が開始されましたが、VMware による Heptio の買収に伴い移管され、現在は Tanzu ファミリーの OSS として CNCF 配下で開発・提供が行われています。

![logo](https://raw.githubusercontent.com/projectcontour/contour/main/contour.png)

- [website](https://projectcontour.io/)
- [GitHub](https://github.com/projectcontour/contour)

# Ingress、Ingress Controller って何だっけ?
## Ingress
Ingress は Kubernetes における HTTP/HTTPS のリクエストルーティングを行うためのリソースです。Kubernetes クラスタ内の Service リソースを HTTP/HTTPS で外部公開する際に、ルーティングやTLSの終端、バーチャルホストなどの設定を定義します。Kubernetes の標準リソースであるため、データプレーンに何が利用されるかどうかは意識する必要なく、抽象化レイヤーとして働きます。

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

## Ingress Controller
Ingress Controller は、作成された Ingress リソースの内容を解釈し、データプレーンの設定を行うためのコンポーネントです。データプレーンとして利用可能なソリューションとしては、NGINX や HAProxy、Envoy Proxy など様々な Layer 7 ロードバランサやプロキシが存在し、各ソリューションごとに設定を行うための Ingress Controller が実装されています。

パブリッククラウド環境では Ingress Controller はプロバイダ側で予め提供されるため、自分で用意する必要はありませんが、オンプレミス環境などで Ingress を利用した HTTP ルーティングを行うためには、Ingress Controller の用意が必須となります。

代表的な Ingress Controller の実装としては、下記の Kubernetes 公式ドキュメントを参照してください。  
https://kubernetes.io/docs/concepts/services-networking/ingress-controllers/

# Contour の構成概要図
Contour（Ingress Controller）、Envoy、Ingress の関係をまとめるとこのようになります。
![image](https://user-images.githubusercontent.com/300815/137741494-c041c54d-8053-421b-a508-1826a7be4399.png)

# Contour の特徴
## もう1つの Ingress 設定、HTTPProxy
Contour では、Ingress に加えて CRD (Custom Resource Definition) を利用した独自リソースの HTTPProxy によって HTTP/HTTPS ルーティング設定を行うことが可能です。この HTTPProxy が Contour の最大の特徴であり、Envoy Proxy の機能を活用した、より柔軟で細やかなルーティングを実現します。

今回は、HTTPProxy が可能にする機能を2つ紹介します。

## 複数の Service を利用した HTTP ロードバランシング、カナリアリリース
Ingress ではホスト名とパスの組み合わせごとに Service が１つ設定されることになりますが、HTTPProxy では1つのホストで複数の Service を設定することが可能です。Service ごとにリクエストの割り振りの比率を設定することも可能なため、アプリケーションの新バージョンをリリースする際、旧版と新版で Service を作成し、新版の Service に対するリクエストを5%や10%といった少ない割合で振り分けながら、段階的に置き換えを行うカナリアリリースを可能とします。この比率は Service 選択の割合となり、選択された Service に紐づく Pod の選択は Service 内で均等に行われます。（補足となりますが、Contour は 設定されたService から Endpoint を参照して Pod の IP を取得し、それを Envoy に適用するため、Envoy からのリクエストの転送は Service の ClusterIP を経由せず Pod に対して直接転送されます）

```
apiVersion: projectcontour.io/v1
kind: HTTPProxy
metadata:
  name: root
  namespace: contour-roots
spec:
  virtualhost:
    fqdn: local.projectcontour.io
  routes:
    - services:
      - name: service1-new
        port: 80
        weight: 10
      - name: service1
        port: 80
        weight: 90
```

## 管理者・運用者と開発者の設定分離
もし複数の開発者が異なる namespace で同一ホスト名を利用して設定を行った場合、コンフリクトが発生してしまいます。HTTPProxy では管理者側で予め作成したホストに対してパスごとに設定を切り出して各 namespace の開発者へ提供し、開発者が作成する HTTPProxy リソースを読み込んでデータプレーン設定を行うことで、事故のない運用を可能にします。

```
apiVersion: projectcontour.io/v1
kind: HTTPProxy
metadata:
  name: root
  namespace: contour-roots
spec:
  virtualhost:
    fqdn: local.projectcontour.io
  includes:
    - name: blogsite
      namespace: marketing
      conditions:
        - prefix: /blog
  routes:
    - services:
      - name: rootapp
        port: 80
      conditions:
        - prefix: /
    - services:
      - name: secureapp
        port: 80
      conditions:
        - prefix: /secure

====

apiVersion: projectcontour.io/v1
kind: HTTPProxy
metadata:
  name: blogsite
  namespace: marketing
spec:
  routes:
    - services:
      - name: wwwblog
        port: 80
```

# Contour を使ってみよう
ぜひ Contour と HTTPProxy をお試しください！サンプルマニフェストが提供されていますので、それを使うとデプロイも簡単です。

```
$ kubectl apply -f https://projectcontour.io/quickstart/contour.yaml
```

正常にデプロイされると、projectcontour の namespace が作成され、そこで contour と envoy の pod が起動します。

```
$ kubectl get pods -n projectcontour -o wide
```

そして、HTTPProxy リソースを作成します。ここでは例として、 `s1` という Service を HTTP で `foo.bar.com` というホスト名で公開します。

```
cat <<EOF | kubectl apply -f -
apiVersion: projectcontour.io/v1
kind: HTTPProxy
metadata:
  name: foo.bar.com
spec:
  virtualhost:
    fqdn: foo.bar.com
  routes:
    - services:
        - name: s1
          port: 80
EOF
```

HTTPProxy リソースを作成すると、Contour はその内容を評価し、Envoy の設定を生成します。
Contour の HTTPProxy のバリデーション状況については、HTTPProxy リソースの STATUS から確認可能です。正常であれば `valid`、何らかの問題が発生している場合には `invalid` となり、設定には反映されません。`invalid` の場合には、STATUS DESCRIPTION に出力されるエラーメッセージをもとに、原因を解決します。

```
$ kubectl get httpproxy 
NAME           FQDN              TLS SECRET   STATUS    STATUS DESCRIPTION
foo.bar.com    foo.bar.com                    valid     valid HTTPProxy
foo2.bar.com   foo2.bar.com                   invalid   Spec.Routes unresolved service reference: service "default/s2" not found
```

設定した HTTPProxy のステータスが正常であることを確認したら、実際に動作を確認してみましょう。エンドポイントとなる IP は、Envoy の Service です。サンプルのマニフェストでは、Envoy の Service は LoadBalancer Service として作成されています。お使いの環境が LoadBalancer Service に対応している場合は External-IP を、そうでなければ NodePort を利用して確認が可能です。HTTP リクエストの Host ヘッダへホスト名をセットしてください。

```
$ curl -H "Host: foo.bar.com" ${ENVOY-ENDPOINT} --head
HTTP/1.1 200 OK
server: envoy
date: XXX, XX Oct 2021 XX:XX:XX GMT
content-type: text/html
content-length: 612
last-modified: XXX, XX Oct 2021 XX:XX:XX GMT
etag: "60e46fc5-264"
accept-ranges: bytes
x-envoy-upstream-service-time: 2

$ curl -H "Host: foo2.bar.com" ${ENVOY-ENDPOINT} --head
HTTP/1.1 404 Not Found
date: XXX, XX Oct 2021 XX:XX:XX GMT
server: envoy
transfer-encoding: chunked
```

正常な HTTPProxy 設定が行われていれば 200 のステータスコード、正常でない HTTPProxy もしくは設定されていないホスト名であれば 404 のステータスコードで Envoy からの応答が確認されます。正常であれば Pod 側でもアクセスログが出力されるかと思います。これで、HTTPProxy の設定による、外部から Pod 上のアプリケーションに対する HTTP ルーティングの動作確認ができました！


# おわりに
今回は Contour の特徴と基本的な HTTPProxy の扱いをご紹介しましたが、今後は実際の運用時に考慮するような細かいパラメータの深堀りなどを行っていこうと思います。TUNA-JP では Contour だけでなく、Tanzu 関連 の OSS プロダクトのナレッジも積極的に発信していきますので、よろしくお願いします！