とうとうこの時がやってきました Pod 起動、からのHTTPリクエスト流すまでのヤツ!
先に書いておきますけど、この構成はあくまで色んな手法がある中での一例です。1つしか知らずに突っ走るのではなく、いくつかつまみ食いしてから、やっぱコレやなって決めるくらいがほどよい、お年頃なシステムなのです。
Deployment
Pod はもちろん単体で起動して遊ぶこともあるでしょうが、ここではWEBサービス用途なので、同じコンテナを複数起動して耐障害性と負荷分散を目的としていきます。Pod を起動するためのリソースは複数あって、特に古めの記事を見ちゃうと Replication Controller というのが目に入ることがあります。が、ここでは有無を言わさず Deployment を採用していきます。理由は新しいからの火の玉ストレートです。もし、違いとか知りたければこの辺を見ておくとよいでしょう。
いったん、余計な項目を削除した内容で載せておきます。Podの起動先Nodeを制御したり、とかいきなり記述しちゃうと、Podがどこにも起動しねぇ!とかになっちゃうので。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
resource "kubernetes_deployment" "main" { count = local.on_eks ? 1 : 0 metadata { name = local.eks_cluster_name labels = { app = local.eks_cluster_name } } spec { replicas = local.eks_config["pod_replicas"] selector { match_labels = { app = local.eks_cluster_name } } template { metadata { labels = { app = local.eks_cluster_name } } spec { container { name = local.eks_cluster_name image = local.eks_config["pod_image"] liveness_probe { http_get { path = local.eks_config["pod_path"] port = local.eks_config["pod_port"] } initial_delay_seconds = 3 period_seconds = 3 } resources { limits { memory = local.eks_config["pod_memory"] } requests { cpu = local.eks_config["pod_cpu"] memory = local.eks_config["pod_memory"] } } } } } } lifecycle { ignore_changes = [spec[0].replicas] } depends_on = [aws_eks_cluster.main] } |
だいたい名前で雰囲気はわかると思いますが、リソース間を名前で関連付けたり、起動台数・ECRのイメージ・ヘルスチェック・Podリソース量、などを設定しています。ignore_changes しているのは、あとで自動増減を入れたときに、Terraform実行時に元に戻そうとしないようにするためですね。
AutoscalingGroupからNodeを1台以上起動した状態で、この Deployment を作成すると、ついに Pod が起動します。コンテナの中身は前にECRに作ったイメージとして、WEBサーバー:80 が動いているものとしましょう。
Service – NodePort
起動した1台以上のPodに対して、どう外部からのリクエストを流すかですが、Nodeにバランサのような Service NodePort をおっ立てます。作成すると、Node上の任意のポートでバランサがLISTENされ、関連付けたPodへ分散転送されるようになります。例えば NodePort:30080 だとすると・・・ALB Listener:443 -> ALB Target Group -> EC2s NodePort:30080 -> Pods:80
という感じで転送されていきます。ALB周りのコードは過去記事を参照していただくとして、TargetGroup から先の経路はこれ1つで済む、ということになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
resource "kubernetes_service" "main" { count = local.on_eks ? 1 : 0 metadata { name = local.eks_cluster_name } spec { type = "NodePort" session_affinity = "None" selector = { app = local.eks_cluster_name } port { protocol = local.eks_config["pod_protocol"] port = local.eks_config["pod_port"] target_port = local.eks_config["pod_port"] node_port = local.eks_node_port } } depends_on = [aws_eks_cluster.main] } |
NodePort の分散モード
複数のPodに対して、NodePortからどんな感じで転送されてるのかなと見ていると、少なくとも RoundRobin ではないことがわかります。じゃあどういう仕組みなのかなって突っ込んでみると、こんな記事があります。ようはデフォは iptables でランダム転送、次点で ipvs がイケてるよってことです。ipvs はLVSで活躍した仕組みで、分散アルゴリズムを選べたり、軽量だったりで、しかも既に充分枯れていると言ってよいと思うので、興味があれば採用を検討してもよいと思います。
他の選択肢
過去記事でも触れましたが、Service:type=LoadBalancer を使うとELBが自動起動したり、AWS ALB Ingress Controller だと ALB の起動から経路関連まで面倒みてくれるので、一見便利なのです。んが、これらは宗教上の理由により即ボツにしました。モノとしては悪くはないので、選択肢を知るという意味でも一度は動かしてみたほうがよいかもしれません。なぜ、私がこれを嫌ったかというと、ALB周りや Route53, ACM あたりは既にTerraformコードになっており、充分な管理統制が成されていたということがあります。できれば、それにくっつける形で運用したいという効率面が1つ。もう1つは、自動作成されたリソースは命名などを始め、任意で操作できないとうことです。
と、いうかぶっちゃけ、クラウド上のリソース管理を、Terraformからと、Kubernetes の2箇所からなんて、普通はやりたくないでしょう?という意味合いが強いです。もしこの、NodePort による構成がとれなかったら、EKSの扱いが気持ち悪すぎて、ブログ記事を書こうと思わなかったかもしれません。
リクエストを送信してみる
Podの内容は、イメージを作る記事で利用した内容で作成しているとすると、特定の箇所へリクエストすればIPアドレスや環境変数が表示されるようになっています。それを見て、分散具合や環境変数が正しく設定されているか、などを確認します。送信先は2箇所あって、
ALBへの場合は 0.0.0.0/0 から受け付けていますが、443番のみかつ特定のFQDNしか受け付けないようにしているので curl で。
直接 EC2 NodePort の場合は、80番ですがプライベートネットワークや、ALBのセキュリティグループからしか受け付けないので、そういう環境のクライアントから curl や telnet でリクエストを送信します。
1 2 3 4 5 6 7 |
# ALB:443 へ curl https://test.example.com/test/index # EC2:80 なら telnet の方が好き telnet ${EC2_private_addr} 30080 GET /test/index HTTP/1.0 Host: test.example.com |
たいした作業ではないですが、最初は 1Node 1Pod にして疎通確認をすること。それから複数Podにしたり、複数Node にして分散具合を確認していくとよいでしょう。
ヘルスチェック
この構成は、ヘルスチェックが2段階になります。いつもの ALB -> EC2 に加えて、Deployment -> Pod があるからです。Pod 起動後に Deployment が OK を出し、ALB が 1Pod 以上OKな Deployment を含む Node に対して OK を出します。これよって特徴に変化が起きるので、考察しておく必要があります。まず、新Node起動時から活動開始までの時間です。
これにかかる時間自体は、ヘルスチェック回数や間隔の調整で、ある程度はなんとでもなりますが、自身の管理するクラスタがどのくらいの急増トラフィックに耐久できるかを認識するためにも、しっかり把握しておくべき重要な数値となります。
他にもリソースダウンについて。
これまでEC2がダウンした場合、ALBヘルスチェックが認識するまではエラーになっていました。それがPodダウンになると、Deployment が認識するまではそこへの転送はエラーになるし、もしNode内のPodがゼロになったならば、ALBが認識するまでそのNodeへの転送は確実にエラー、ということになります。
Nodeの死はAutoscalingGroupによって、Podの死はDeploymentによって、自動的に補充や再起動が行われるので、障害発生時に起きる影響範囲というのは、全体のリソース量によるものの、誤差の範囲かもしれません。しかし、原因を調べる際に、どの部分に障害があって、どの経路や部分に影響が起きたのか、を把握するためには、そういった仕組みについても理解しておくほうが良いでしょう。
ヘルスチェックそのものについては、このあたりを読んでおくと良さげです。
ここまでくれば、あとは本番運用に向けて必要な味付けをしていくことになりますが、いったん kubectl で遊ぶ時間をとってもよいタイミングです。Node, Pod 情報を確認したり、Pod をわざと殺してみたり、イメージを更新してみたり。なんの情報がどこにあるのか、どんな操作をしたらどんな挙動をするのか、っていう体感はわりと重要なので、インフラのコード化をしつつも、こういうところで楽しんでいきましょう。
さて、ここからの細かい味付け、なにから手を付けていくか・・・
めんどくさくなる前にとにかく仕留めちゃうぞぃっ☆