Gunosy Tech Blog

Gunosy Tech Blogは株式会社Gunosyのエンジニアが知見を共有する技術ブログです。

ゼロから始めるEKS事始め(前編)

本記事は、Gunosy Advent Calendar 2020 7日目の記事です。

前回は、しゅんけー君の「その実験、再現できますか?pyenvとpoetryによる “そんなに頑張らない” 再現可能な実験環境構築」でした

data.gunosy.io

はじめに

Gunosyの大関と申します。いくつかのEKSクラスターとクラスター上で稼働するサービスの開発運用を、基盤レイヤーを中心に担当しています。

昨年のadvent calendarでは、こんな記事を書いていました。 tech.gunosy.io

さて、今年一年で社内の様々なサービスがEKSでの新規構築ないしEKSへの移行を行っており、世間一般でもコンテナオーケストレーションのデファクトスタンダードとして確固たる地位を築いた印象のあるK8Sですが、まだまだ初心者が気軽に手を出すには敷居が高い印象です。

その理由としては、

  • 個々のドキュメント(例: Amazon EKS クラスター - Amazon EKS )はネットで探せばあるし読めばいいんだけど、
  • その後に、クラスタにアプリをデプロイしてサービスを稼働させるまでには、具体的に何をどの順番でやったらいいんだ?
  • また本番環境での安定稼働に向けて、検討すべきポイントはどこだ?

などといった、初期の検討段階から実際の構築〜運用のポイントといった、工程全般に言及した一気通貫のナレッジが見当たらない事が原因なのではないかと思ってます。*1

そこで、これからK8Sを使って何らかのサービスを「今まさに」構築しようとしているが、周囲に知見者がおらずも事例もなくて困っている。といった方に対して、私がこの1年で培ったナレッジをお裾分けして、流れを把握してもらうガイドブック的な読み物となっております。

なお、技術的な設定例や詳細な解説について触れると本を1冊書けるボリュームとなってしまうため、詳細は省き概要のみ理解して頂く説明に終始しています。

予めご了承下さい。

前提条件

以下、漸く具体的なガイドとなります。すでに検討・対応済みという方は読み飛ばして頂いて構いませんが、見直しの一助になれば幸いです。

また個々の項目の細かい注釈は本稿の最後にまとめましたので、オイオイこれどういうことだと気になった箇所があったら、是非お読み下さい。

目指すクラスター像

本ガイドでは、

  • EKSを使って複数コンテナによるクラスターを構築+EC2のspotインスタンスでコストを抑えつつ、
  • ClusterAutoscalerやHPA等を使って負荷の増減への弾力性を担保することが出来て、
  • 監視ツールでサービスレベルを維持管理することが出来て、
  • アプリのデプロイツールを使ってCI/CDを行うことが出来て、
  • インフラの構成管理ツールを使ってInfrastructure as Codeでクラスタの構成管理が出来る

といったような、モダンなDevOpsの開発スタイルに沿った、ある程度の規模・品質を想定した本番サービスでも充分に耐えうる、運用しやすいクラスタを目指します。

使用するサービス・ツール類

クラスタの構築・運用に不可欠な各種サービス、ツール類についてまとめました。

基本的に、全てパブリッククラウドやパブリックサービス(SaaS)を活用します。 オーソドックスなチョイスとなっていますが、あくまで私個人の経験・知見に基づいていますので、社内で類似のサービスを使っていて知見があるなどといった場合は、そちらを採用すると良いでしょう。

  • AWS
    • EKS*2
    • AutoscalingGroup*3
    • アプリ用のコンテナイメージ管理として、ECR
    • アプリ用のバックエンドサービス(RDS、ElastiCache、Dynamo等)
  • ソース管理
  • CI/CD
  • インフラの構成管理ツール
  • アプリのデプロイツール
  • 監視ツール

ローカルの開発環境は、Macを想定しています。クライアントツールとして、aws-cli、kubectl、terraform、docker(for mac)、helm、skaffold、lensなどを、インストールしておきましょう。

K8Sを使う上での心構え

ここまで読ませておいて恐縮ですが、ガイドを進める前にいくつか条件があります。

  1. コンテナで動作するアプリケーションがある*13
  2. EKS(K8S)を使って行こうというモチベーションがある*14
  3. EKSを運用していくにあたり、リソースを割く覚悟がある

1と2については問題ないと思いますが、重要なのは3です。

言わずもがなですが、アプリを介したサービスでビジネスの目標を達成することがゴールであり、アプリが動くクラスタは単なる道具でしかなく、ビジネス目標の達成にはサービスの安定稼働が必要で、そのためには日々の運用が大事となり、運用コストがかかります。

ここでいう運用コストはAWSやSaaSに支払うインフラコストではなく、人的コストを指します。

日々の運用業務の具体例としては、

  1. 監視ツールを使ってアラートを仕掛けて、異常が発生したら対応する
  2. 監視ツールで各種メトリクスを取得し、メトリクスを元にダッシュボードを作り、日々の変化を把握する
  3. 定期的なEKSのバージョンアップ作業が必要となる
  4. kune-systemや各種ツール類のバージョンアップ情報を収集し、必要に応じて更新する
  5. Kubernetesや各種ツールの知見を深める etc…

といったような、地味で地道な*15運用業務が発生します。 1と2はEKSに限った話ではないですが、3以降はEKS固有の話となります。 この運用分の人的リソースを確保し、必要なコストを予算として計上する。という覚悟が無いと安定稼働は見込めず、せっかく作ったクラスタも、いずれ焼け野原と成り果てます。

ポイントとしては、クラスターの規模に比例して運用コストが発生するのではなく、小さなクラスターでもある程度の運用コストが必要となる事です。 正直なところ、リーンスタートがしづらいと思っています。

K8S(EKS)クラスターの安定運用は、想像以上に運用負担が重いことを覚悟して下さい。

同じコンテナオーケストレーションのECSをEKSと比較した場合、EKSには様々なメリット*16*17*18*19*20がありますが、定期的なバージョンアップを避けるために、ECSという選択肢もありますし、それはそれで良い判断だと思います。*21

環境設計編

直接的にはEKSと別の話となりますが、安定稼働のためには重要かつ、一旦作ってしまうと後戻りがしんどいのであえて触れます。

環境設計

ここでいう環境とは、本番環境、テスト環境、開発環境などの面のことです。

  • 本番環境とその他の環境は、バージョンアップやメンテナンス時の検証環境としても、最低限分けましょう*22*23
  • ローカルPC上の開発環境(docker-compose)は、あきらめましょう*24
  • 環境ごとにクラスターを分けましょう*25*26*27*28
  • (可能であれば)環境ごとにAWSのアカウントも分けましょう*29*30*31

リポジトリ

続いてソース管理とリポジトリについてです

  • インフラとアプリは、それぞれリポジトリを分けましょう

    • インフラ用
      • EKSクラスターを含めたAWS全般
      • kube-systemやdatadogなど、クラスタ管理用Pod
    • アプリ用
      • アプリのソースコード
      • アプリ用コンテナのビルド設定(Dockerfile)
      • アプリをEKSクラスタにデプロイするためのデプロイツール設定(helm)
  • 前述の環境(およびAWSアカウント)を元に、ブランチを分けましょう*32

    • 仮に本番・ステージング・開発の3環境を作成する場合
      • 開発 : development ブランチ
      • ステージング:stagingブランチ
      • 本番:productionブランチ
    • それぞれのブランチへのmerge commitをhookして、CI/CDツールが動作するように設定しましょう*33

クラスター構築・前編(EKSクラスター)

すでにAWSのアカウントがあり、最低限のユーザアカウント(IAMユーザーなど)、ネットワーク(vpc, subnet, routetable, internet/nat-gateway, 最低限のSecurityGroupなど)、デプロイ用などS3 Bucketなどの準備ができている方は、飛ばしてください。

もしまだの場合に検討・考慮すべきポイントについて触れておきます

  • AWS環境は全てTerraform化しましょう*34

  • CircleCIと連携してTerraformをapplyする仕組みを作りましょう*35

ここまでの準備ができたら、(Terraformで)EKSクラスタを立ましょう。公式ドキュメントを参考*36に、いくつかのsgとroleを作成し、作成済のsubnetと組み合わせて、eksクラスタ(のresource)を作成します。*37*38

クラスター構築・中編(ASG)

ASG or Fargate

EKSクラスターが構築できたら、続いてpodが動作するASGを用意しEKSクラスターに組み込みますが、

その前に、Podを通常通りASGで制御したEC2インスタンス(node)上で実行するか、最初からfargate化してサーバーレス化するかの検討・判断が入ります*39*40

前述の運用コストの結構な割合をnodeとpodのリソース調整が占めているケースが多く、fargateを選択した場合は、

  • node(spotインスタンス)で動作する場合に比べて、AWSに支払うインフラコストは増加する
  • nodeの管理をしなくて良いので、運用コストは低下する*41
  • fargate化するPodにはいくつか制限がある*42*43*44*45
  • HPA等でオートスケールする場合など、node上のallocationにpodを起動するよりも、fargateインスタンスの起動分、追加の時間がかかる

といった条件を呑めるのであれば、fargate化するメリットは充分あります。

fargateを使う場合は、構築したEKSクラスターに対してfargate profileを作成し*46*47、profile内で指定したnamespaceとlabel*48をpodのmanifestに設定するだけです*49*50*51

ASG

続いて通常のnodeでPodを動かす場合についてです。 通常のASG、もしくはManagementNodeGroupのどちらでも良いです(どちらもLaunchTemplateが肝心なため)

いくつASGが必要なのか?という話ですが、基本的には

  • サービス/アプリの種類ごとにASGを分けましょう
  • クラスタ管理(kune-systemなど)用のASGも分ましょう

ワークロード毎にASGを使い分けて、処理リソースを分割することが重要です。

なぜ分けるのか。単一のASGで運用した方が、一見管理の簡素化や低コスト化につながりやすそうですが、ワークロードごとの負荷や設定変更時の影響を局所化して互いに疎結合にすることが、クラスタ全体のサービスレベルを維持し、安定稼働につながるからです。*52*53

複数用意したASGを使い分けるためには、nodeにlabelを付与した上で、pod側でnodeselectorないしNodeAffinityの設定を追加する必要があります。*54*55

spotインスタンス

spotインスタンスを使う場合の注意点を、以下にまとめました。

  • SpotAllocationStrategyは、本番サービスはcapacity-optimized、開発環境はlowest-priceなどと使い分けましょう*56
  • TerminationPolictyは、DefaultかAllocationStrategyがオススメです*57
  • ClusterAutoscalerを使っている場合(どの環境でも必須)は、SuspendedProcessesでAZRebalanceを設定する必要があります*58*59*60

spotインスタンスは、端的に言うと安い代わりに不定期にインスタンスの中断が発生するインスタンスです*61

非コンテナ環境でもクラスタリングによりインスタンス停止起因のサービス断を防ぐことが出来ますが、コンテナ化+K8Sのself-healingにより、さらに効果を発揮します。

そのためにはpodとalbを組み合わせて、gracefulに起動停止する仕組みが必要となります*62*63

最初から100%spotインスタンスで運用することが難しいのであれば、ondemandインスタンスとspotインスタンスの組み合わせでも良いですし、Reservedインスタンスを購入して低コスト化するのも一つの手です*64*65*66*67

ASGをTerraformで構築したら、ASGのinstance-profileのARNをaws-authに追記して、EKSクラスタにapplyします。これをCI/CDツール上で行います*68*69

この状態で$ kubectl get nodeで設定したインスタンス見え、$ kubectl -n kube-system get poでaws-node, kube-proxy, corednsがRunningとなっていれば、ASGは正常に認識されています。

以下は3インスタンスで構成されたASGでのサンプルです

$ kubectl get nodes
NAME                                              STATUS   ROLES    AGE   VERSION
ip-10-74-71-57.ap-northeast-1.compute.internal    Ready    <none>   23h   v1.17.9-eks-4c6976
ip-10-74-86-155.ap-northeast-1.compute.internal   Ready    <none>   14d   v1.17.9-eks-4c6976
ip-10-74-95-195.ap-northeast-1.compute.internal   Ready    <none>   9h    v1.17.9-eks-4c6976
$ kubectl -n kube-system get po
NAME                                         READY   STATUS    RESTARTS   AGE
aws-node-d9694                               1/1     Running   0          23h
aws-node-ddpk4                               1/1     Running   0          9h
aws-node-qkdwx                               1/1     Running   0          14d
coredns-5674bf8d8-9hz2s                      1/1     Running   0          14d
coredns-5674bf8d8-hlttf                      1/1     Running   0          4d4h
kube-proxy-blpnt                             1/1     Running   0          9h
kube-proxy-lfskw                             1/1     Running   0          23h
kube-proxy-ql994                             1/1     Running   0          14d

後編に続きます(後編は明日です)

*1:あくまで個人の観測範囲として

*2:GKEやAKSでも可

*3:ManagedNodeGroupでも可。つい先日spotインスタンスに対応した

*4:Bitbucket等、CI/CDと連動できればなんでも可

*5:GithubActionやCodeBuild+CodePipeline等でも可

*6:CloudFormationでも可と言いたいが、awsだけではなくk8sやdatadogなどのproviderが豊富なので、現状Terraform一択

*7:kustomizeでも可だが、世間の流れ的にはCNCFのincubating-projectsであるhelmが主流となりつつあるので、helmがオススメ

*8:私が担当しているクラスタではhelmもkustomizeの両方を使っていますが、kustomizeに比べてhelmは一見複雑ですが、if文が使えたり、if文と組み合わせてyaml内の特定の配列の入力の有無を制御出来たりと、よりプログラマブルに管理することができます

*9:一部cloudwatch

*10:自前の監視基盤を作るのは、コスパが悪いのでオススメしません

*11:log管理も必要なので、datadog-logsないしpapertrailで

*12:Mackerelでも同じことはできると思いますが、お好みで

*13:これから開発するでも可

*14:技術的に興味がるから!でも良いと思います。コンテナオーケストレーションとしてはデファクトスタンダードな状況はあと数年は続くでしょうし、これから知見を得ることは個人としても組織としても非常に有益です

*15:ただし、これはこれで楽しい

*16:K8S自体と周辺ツールはOSSなので困ったらソース読めばよい

*17:OSSコミュニティ参加者が多いのでサービス更新が早い

*18:ドキュメントが豊富

*19:負荷の増減に対する柔軟性が高い(ECSも追従しつつあるけど)

*20:デプロイツールが豊富

*21:小規模なAPIならlambdaでも良い

*22:どの位まで分けるかは、サービスレベルとステークホルダーの有無、許容するコストなど考慮すべき点は多岐に渡りますので一概には言えませんが、一般的に本番環境・ステージング(テスト)環境・開発環境の3つに分けるケースが多いです

*23:https://www-creators.com/archives/780

*24:MBPのメモリ16GでもローカルでK8Sクラスタを立ててテストする事は、性能的に厳しいです。AWS上の開発環境を用意しましょう

*25:本番環境(のクラスター)に、(namespaceを作ってASG分けて)開発等の他の環境を同居させる。というのは非常にオススメしません

*26:デプロイ時の制御が複雑になる(このブランチはこの環境にデプロイなど)

*27:kube-systemなどクラスタ全体に影響があるコンポーネントの更新時には、影響を局所化できない

*28:EKSのバージョンアップ時には、事前の検証ができない

*29:コスト管理がしやすい

*30:環境に応じてAWSアカウントに対するアクセス制御がしやすい(同一アカウントに対してのアクセス制御は可能だが、より簡単)

*31:異なるアカウントであれば、環境ごとに同じ名前でサービスが作れるので、監視がしやすい

*32:細かい名前はお好みで(production or master)

*33:https://dev.classmethod.jp/articles/deploy-terraform-with-circleci/

*34:私もかつては最初はmanagement condoleからのポチポチでいいじゃん派でしたが、長期的にかつ効率的にインフラを管理しようとした場合、Infrastructure as Codeを徹底していないと構成変更時などに非常に辛くなる、一度ポチポチで作成しておいて後からimportすることもできるが、10倍大変かつ環境をぶっ壊してしまうことも有り得る、ポチポチ手作業で作成する事は、即ち設定ミスの原因となり、コード化されていないとミスした事にすら気づかないなどといったは反省点を踏まえて、構成管理徹底おじさんとなりました

*35:最初期に1人で構築する分にはローカルからのterraform applyでも困りませんが、2人以上の場合はtfstateの共有が必要になるので、頑張ってCI/CD環境を整えましょう。一度作りさえすれば、あとは楽チンです

*36:https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/eks_cluster

*37:eksctlを使ってもクラスタ+nodeの構築は可能ですが、CI/CDに乗せて冪等性があり更新可能なクラスタを構築する知見がないので、本項では割愛します

*38:あくまでCloudFormationのWrapperだからなぁという印象ですが、いまは出来るんですかね?情報求む

*39:あとからの構成変更でも対応可能

*40:ASGとFargateの組み合わせも可能

*41:nodeの管理に関連する、いくつかのkube-systemのpodが不要となる

*42:https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/fargate.html#fargate-considerations

*43:vCPU数が4まで

*44:hostPort使えない

*45:daemonsetとしての利用は不可

*46:https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/eks_fargate_profile

*47:https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/fargate-profile.html

*48:例:deploy=fargateなど

*49:AmazonEKSFargatePodExecutionRolePolicyがない場合は作成しましょう

*50:https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/pod-execution-role.html

*51:https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/eks_fargate_profile#example-iam-role-for-eks-fargate-profile

*52:例えば(これは実体験ですが)、異なるサービスレベルのアプリA、Bとkube-systemが1つのASGで同居していたと仮定し、アプリAのpodが高負荷かつ常に起動/停止を繰り返す不安定な状態に陥った際は、クラスタ内の全podのモニタリングを行うkube-systemのpod(metrics-server, lube-state-metrics, datadog)もつられて高負荷となり、同一ASGのnodeのリソースを取り合うため、ますます不安定化が進みます。最終的にはアプリBの稼働に必要なリソースまで枯渇してしまい、クラスタ全体が不安定となる。といったケースが考えられます

*53:予めASGを分けていた場合、アプリAの不安定さとkube-systemの必要リソースは別途解消が必要ですが、ASGを分けてさえおけば、影響は局所化できます

*54:参考:https://labs.gree.jp/blog/2020/01/20271/

*55:https://www.eksworkshop.com/beginner/140_assigning_pods/node_selector/

*56:https://aws.amazon.com/jp/blogs/compute/introducing-the-capacity-optimized-allocation-strategy-for-amazon-ec2-spot-instances/

*57:https://docs.aws.amazon.com/ja_jp/autoscaling/ec2/userguide/as-instance-termination.html#custom-termination-policy

*58:https://docs.aws.amazon.com/ja_jp/autoscaling/ec2/userguide/as-suspend-resume-processes.html#process-types

*59:https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/cloudprovider/aws/README.md#common-notes-and-gotchas

*60:CAがASGに対してインスタンスを追加する場合、どのAZに追加するかは考慮しません。AZ間のインスタンスの台数が不均衡になりAZRebalanceが無効化されていないと、CAが起動したインスタンスがASGにより停止されて、インスタンスの増減がガチャガチャと続くことになります

*61:https://recipe.kc-cloud.jp/archives/321

*62:神記事:https://qiita.com/superbrothers/items/3ac78daba3560ea406b2

*63:ec2インスタンスの停止を検知して、事前にpodの停止を実行するaws-note-termination-handlerというkube-systemの管理podがあります

*64:https://dev.classmethod.jp/articles/ec2-auto-scaling-groups-with-multiple-instance-types/

*65:あとからの構成変更でも対応可能

*66:私が担当しているクラスタでは全て100%spotインスタンスで安定稼働させています

*67:コストダウンは大事ですが、安定稼働はもっと大事です

*68:https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/add-user-role.html

*69:Code3兄弟での例ですが、参考:aws-auth ConfigMapをEKSクラスタにCodeシリーズで自動デプロイする仕組みを構築してみた | DevelopersIO