Azure Container Appsを試す前に知っておきたいこと - Ingress編
何の話か
Ignite 2021秋でAzure Container Appsが発表されました。Kubernetesを基盤に、Envoy、KEDA、DaprといったCNCFプロジェクトの成果物をマネージドサービスとして提供するサービスです。ここ数年、マイクロソフトがCNCFで取り組んできたことをまとめてドン、という感じで、なかなか熱いものがあります。
ですが、プレビュー初期ということもあり、公式ドキュメントが充実しているとは言いにくい状況です。そこで、ドキュメントが整備されるまで、試す前に知っておくといいかな、ということをいくつか紹介します。
なお、プレビュー中ということもあり、これから説明する内容には変更される可能性があります。公式ドキュメントを正としてください。また、選定や設計するにあたって足りない情報があれば、どしどし要求しましょう。公式ドキュメント(英語)の各ページ下部にGitHub Issuesへのリンクがあります。
今回はIngress編です。
Azure Container Apps Ingressとは
EnvoyをベースとしたProxyです。
Ingressはユーザの作成したContainer Appsアプリケーションに対し、Container Apps環境内外からアクセスできるよう、エンドポイントを提供します。TLS終端はもちろん、複数リビジョンへのトラフィックスプリットもできます。Container Appsの使いこなしのカギのひとつです。
以下の3つは、わたしが初見の際に持った疑問です。みなさんも、知っておく価値はあると思います。
- gRPC使える?
- 名前解決やサービスディスカバリってどうなってる?
- 同じ環境内のIngress経由トラフィックをプライベートネットワークに閉じ込められる?
gRPC使える?
使えます。
では試してみましょう。grpc/grpc-go のhelloworldサンプルをベースに、負荷分散が効くか確認するため、応答メッセージにホスト名を加えたサーバアプリを作ります。また、gRPCリフレクションも設定します。
package main
import (
"context"
"flag"
"fmt"
"log"
"net"
"os"
"google.golang.org/grpc"
pb "google.golang.org/grpc/examples/helloworld/helloworld"
"google.golang.org/grpc/reflection"
)
var (
port = flag.Int("port", 50051, "The server port")
)
type server struct {
pb.UnimplementedGreeterServer
}
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
log.Printf("Received: %v", in.GetName())
hostname, _ := os.Hostname()
return &pb.HelloReply{Message: "Hello " + in.GetName() + ", this is " + hostname}, nil
}
func main() {
flag.Parse()
lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port))
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
s := grpc.NewServer()
pb.RegisterGreeterServer(s, &server{})
reflection.Register(s)
log.Printf("server listening at %v", lis.Addr())
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
このgRPCサーバアプリケーション(grpc-greeter)をContainer Appsにデプロイします。負荷分散を確認したいので、レプリカ数は2です。また、Ingressは"external"で公開します。transportは"http2"1、targetPortは"50051"です。
では、gRPCurlでサービスをリストしてみましょう。接続先はIngressエンドポイントです。
$ grpcurl grpc-greeter.makabecchi-123456.canadacentral.azurecontainerapps.io:443 list
grpc.reflection.v1alpha.ServerReflection
helloworld.Greeter
リストできていますね。では、helloworld.Greeter/SayHelloを呼び出してみましょう。
$ grpcurl -d '{"name": "Toru"}' grpc-greeter.makabecchi-123456.canadacentral.azurecontainerapps.io:443 helloworld.Greeter/SayHello
{
"message": "Hello Toru, this is grpc-greeter--bd346ys-679c6454bc-kctf9"
}
$ grpcurl -d '{"name": "Toru"}' grpc-greeter.makabecchi-123456.canadacentral.azurecontainerapps.io:443 helloworld.Greeter/SayHello
{
"message": "Hello Toru, this is grpc-greeter--bd346ys-679c6454bc-6452x"
}
呼び出せました。また、複数回呼び出すと、応答のホスト名が変わり、負荷分散できていることがわかります。
名前解決やサービスディスカバリってどうなってる?
Ingressには、FQDNが付与されます。
[Container app name].[Environment unique identifier].[Region name].azurecontainerapps.io
がルールです。たとえば、以下のように。
myapp.happyhill-70162bb9.canadacentral.azurecontainerapps.io
このFQDNを引くと、IngressのIPアドレスが得られます。
ポイントは、externalとinternalで扱いが変わることです。
試しに3つ、Ingress付きアプリケーションを作ってみましょう。すべてNGINXです。external向けを1つ、internalを2つ。
nginx-external.makabecchi-123456.canadacentral.azurecontainerapps.io
nginx-internal.makabecchi-123456.canadacentral.azurecontainerapps.io
nginx-internal-2.makabecchi-123456.canadacentral.azurecontainerapps.io
では、Container Apps環境外部、インターネットから名前解決してみます。
$ dig +noall +answer nginx-external.makabecchi-123456.canadacentral.azurecontainerapps.io
nginx-external.makabecchi-123456.canadacentral.azurecontainerapps.io. 3556 IN A 20.151.223.25
$ dig +noall +answer nginx-internal.makabecchi-123456.canadacentral.azurecontainerapps.io
nginx-internal.makabecchi-123456.canadacentral.azurecontainerapps.io. 3600 IN A 20.151.223.25
$ dig +noall +answer nginx-internal-2.makabecchi-123456.canadacentral.azurecontainerapps.io
nginx-internal-2.makabecchi-123456.canadacentral.azurecontainerapps.io. 3600 IN A 20.151.223.25
すべて同じパブリックIPアドレス(20.151.223.25)が返ってきました。
external向けには、意図通りインターネット経由でアクセスできます。
$ curl -sv https://nginx-external.makabecchi-123456.canadacentral.azurecontainerapps.io
* Trying 20.151.223.25:443...
* TCP_NODELAY set
* Connected to nginx-external.makabecchi-123456.canadacentral.azurecontainerapps.io (20.151.223.25) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
...
< HTTP/2 200
< server: nginx/1.21.3
...
internal向けには、できません。internal向けに2つ作りましたが、結果は同様です。
$ curl -sv https://nginx-internal.internal.makabecchi-123456.canadacentral.azurecontainerapps.io
* Trying 20.151.223.25:443...
* TCP_NODELAY set
* Connected to nginx-internal.internal.makabecchi-123456.canadacentral.azurecontainerapps.io (20.151.223.25) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
...
< HTTP/2 404
...
以上から、同じ環境にあるIngressのエンドポイントは、アプリケーション間で共有されることがわかります。そして、internal Ingressは、環境外部からのトラフィックを転送しません。
では、Container Apps環境内部からの名前解決は、どうでしょうか。確認用のコンテナを作って確かめましょう。digやcurlが含まれるコンテナイメージ giantswarm/tiny-tools を使います。ところでデバッグコンテナ的な何かがほしいですね。のびしろです。
digで名前解決を確認します。
$ az containerapp create -n tiny-tools -g rg-containerapp-poc --environment containerapps-env --image giantswarm/tiny-tools --command 'dig, +noall, +answer, nginx-internal.internal.makabecchi-123456.canadacentral.azurecontainerapps.io' --revisions-mode single --cpu 0.25 --memory 0.5Gi
標準出力は、Container Appsに接続したLog Analyticsで確認できます。
k8se-envoy-internal.k8se-system.svc.cluster.local. 5 IN A 10.0.183.11
環境内部からの名前解決要求に対しては、プライベートアドレスが返ってくることがわかります。もうひとつのIngress internalエンドポイントについても、確認しましょう。
$ az containerapp update -n tiny-tools -g rg-containerapp-poc --image giantswarm/tiny-tools --command 'dig, +noall, +answer, nginx-internal-2.internal.makabecchi-123456.canadacentral.azurecontainerapps.io'
k8se-envoy-internal.k8se-system.svc.cluster.local. 5 IN A 10.0.183.11
同じIPアドレスが返ってきました。internal Ingressではプライベートな共有エンドポイントが作られ、環境内部からはそのIPで解決されることがわかります。ホスト名からも、想像ができますね。
ここでKubernetes経験者は、同じNamespaceのServiceであればサービス名的な何かだけで解決できるのでは、と思うでしょう。わたしもそうでした。ただし、現在のContainer AppsはNamespaceとServiceが非可視、またIngressがFQDNでフィルタ処理をしているため、現状ではできません。
なお、Ingressを使わず、Daprを使ってもアプリケーション、サービス呼び出しができます。こちらの指定はシンプルです。環境識別子やリージョン名を指定せずにサービスディスカバリできます。例えば以下のように。
http://localhost:3500/v1.0/invoke/myapp
同じ環境内のIngress経由トラフィックをプライベートネットワークに閉じ込められる?
これまでの流れでわかりますね。できます。
では、internal Ingress経由でcurlしてみましょう。
$ az containerapp update -n tiny-tools -g rg-containerapp-poc --image giantswarm/tiny-tools --command 'curl, -sv, https://nginx-internal.internal.makabecchi-123456.canadacentral.azurecontainerapps.io'
Log Analyticsで確認します。
* Trying 10.0.183.11:443...
* Connected to nginx-internal.internal.makabecchi-123456.canadacentral.azurecontainerapps.io (10.0.183.11) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
...
< HTTP/2 200
< server: nginx/1.21.3
...