はてなにおけるEKSの運用と自動化 (2024年版)

サービスプラットフォームチームで SRE を担当している id:masayosu です。

先月からですが Hatena Developer Blog にて SRE 連載を始めました。先月の記事は はてなブログの DB を RDS for MySQL 8.0 にアップグレードした話 - Hatena Developer Blog です。

毎月はてなの SRE が交代でブログ記事を書きますのでお楽しみに。 この記事は2024年2月の SRE 連載の記事です。

はてなの EKS 利用について

私が所属するサービスプラットフォームチームでは EKS の運用を続けており、先日 Kubernetes 1.23 から 1.28 へのアップグレードを完了しました。 私のチームは少人数で形成されているのですが、担当しているサービスは大小様々あり EKS クラスター上では数十個のサービスが稼働しています。

少人数で Kubernetes の運用ということで大変なイメージを持つかもしれませんが、Kubernetes のエコシステムに乗っかることで少人数で複数のサービス開発を効率よく回せているといった実情もあります。

クラスター運用でのポイントというとクラスターアップグレードが挙げられます。これまで私は今のチームで4度のアップグレードを経験してきました。

アップグレード方法ですが、新バージョンのクラスターを別に構築してサービスを移行する Blue/Green アップグレードという方法を選択しています。その理由としてアップグレードによる問題が生じた場合にすぐに切り戻しができるようするためです。

冒頭にも書いた通り少人数のチームのため年 3 回ある Kubernetes のアップグレード全てに追いつくのはリソース的に難しいと考えています。そのためどうしてもバージョンを飛ばしてアップグレードすることが多く影響範囲が大きくなってしまうため Blue/Green アップグレードを選択することでより安全にアップグレードを実施しています。

Blue/Green アップグレードの方法もいろいろと工夫している点があるので現状を紹介します。

新クラスターへのデプロイ

Blue/Green アップグレードのポイントとしてまず新クラスターへのアプリケーションのデプロイについて説明します。

EKS クラスターへのデプロイは ArgoCD を利用しています。 ArgoCD ではサービスのデプロイに関する設定を Application というリソースで保持しています。 ArgoCD も Kubernetes 上で動作するサービスなのでこれらの Application リソースについても manifest で管理することができます。manifest で管理できるとはいえ、Blue/Green のクラスター 2 つの設定を管理するのは面倒です。

これを解決するために私たちは Applicationset を利用しています。 Applicationset は Application リソースをまとめて管理できるリソースです。 Applicationset の Cluster Generator を利用することでデプロイ対象の Blue/Green クラスターへのデプロイ設定をまとめて管理することができます。

以下が Applicationset のサンプルです。

code: applicationset 
 apiVersion: argoproj.io/v1alpha1
 kind: ApplicationSet
 metadata:
   name: sample-application 
   namespace: argocd
 spec:
   generators:
   - clusters:
       selector:
         matchLabels:
           eks: "true"
           version: "1.23"
       values:
         branch: master
   - clusters:
       selector:
         matchLabels:
           eks: "true"
           version: "1.28"
       values:
         branch: master
   ignoreApplicationDifferences:
   - jsonPointers:
     - /spec/syncPolicy
   template:
     metadata:
       name: sample-application-{{name}}
     spec:
       destination:
         namespace: sample-application
         server: '{{server}}'
       project: spf-eks
       source:
         path: manifest/project/sample-application/production-{{name}}
         repoURL: https://github.com/hatena/spf-eks
         targetRevision: '{{ values.branch }}' 

上記のポイントとしては以下の3点です。

  • clusters の指定については全 Applicationset の設定を書き換えるのは面倒なので kustomize を利用することで1 ファイルで管理できるようにしました
  • クラスターアップグレードの際には専用のブランチを切って動作確認をする場合が多いのでクラスター毎にデプロイするブランチを切り替えれるようにしました
  • syncPolicy は基本 AutoSync を採用しているのですが新クラスターにいきなりデプロイされるのはバッチの2重問題などもあり避けたいと考えています。そのため初期状態では AutoSync は利用せず切り替えのタイミングで手動で AutoSync に変更する運用をしています。手動で変更したものが設定で上書きされないようにignoreApplicationDifferencesを利用しています

EKSでは数十個のサービスが稼働しているので、Applicationset を利用することで設定管理の作業コストを削減することができました。

切り替え作業

次は切り替え作業についてです。

Webアプリケーションの切り替えについてですが TargetGroupBinding という機能と ALB の WeightedRouting を利用してます。

もともと Kubernetes の Ingress リソースから ALB を作成していましたがその方法だと DNS の切り替えが発生します。それでも問題ないのですがクライアントにキャッシュが残っている場合などに切り替えを待つ必要がありました。

また一部の重要なサービスについては ALB の上に Global Accelerator を配置してエンドポイントウェイトの機能を利用して切り替えを実施していました。

ALB に WeightedRouting の機能が追加されたことで Global Accelerator を利用する理由も無くなり TargetGroupBinding を利用する方針に切り替えました。これらの ALB と TargetGroup はクラスターとは別の IaC ( CDK )で管理しています。

TargetGroup の weight は以下のようなコードで管理しています。

 const tgSettings: { id: string; name: string; weight: number }[] = [
   { id: 'BlueTargetGroup', name: `${prefix}-blue`, weight: 100 },
   { id: 'GreenTargetGroup', name: `${prefix}-green`, weight: 0 },
 ]

実際に切り替えをする際は上記の weight を書き換え cdk deploy を実施するような流れとなります。weight の値を少しずつ増やすことでリクエスト先を除々に切り替えていくことも可能です。

これで Global Accelerator も不要となり構成がシンプルになり、ALB を利用するサービスを安全に切り替えができるようになりました。

1.28へのアップグレード

この度、Kubernetes の 1.23 から 1.28 にアップグレードする際にこれまで紹介してきた Applicationset や CDK での切り替えを始めて実施しましたが仕組み化されたことでより安全に効率的にアップグレード作業を実施することができました。

その他、1.28 へのアップグレードの際に対応した変更について紹介します。

EKS は Kubernetes 1.24 以降で Dockershim のサポートを終了しています。それに伴いコンテナランタイムも dockerd から containerd に変更となっています。

containerd になったことで Node の /var/log/containers 配下に出力されるログのフォーマットが変更されました。私たちの EKS は daemonset で ​Fluent Bit を利用してログを集約しているためこのフォーマット変更に対応する必要がありました。それぞれのログフォーマットは以下のようになっています。

・dockerd

{"log":"{\"level\":\"info\",\"ts\":\"2024-01-05T13:11:25.922Z\",\"caller\":\"XXXX/main.go:53\",\"msg\":\"starting gRPC server (port = 50051)\"}\n","stream":"stdout","time":"2024-01-05T13:11:25.923191709Z"}

・containerd

024-02-02T00:32:57.908092235Z stderr F {"level":"info","ts":1706833977.9079013,"caller":"XXXX/main.go:53","msg":"starting gRPC server (port = 50051)"}

このフォーマット変更には ​Fluent Bit の設定ファイルのみで対応できました。まずは以下のように新しいログフォーマットに対応した Parser を作成します。こちらの Parser の内容は公式のドキュメントにも記載されています。

docs.fluentbit.io

 [PARSER]
   Name        cri
   Format      regex
   Regex       ^(?<time>[^ ]+) (?<stream>stdout|stderr) (?<logtag>[^ ]*) (?<log>.*)$
   Time_Key    time
   Time_Format %Y-%m-%dT%H:%M:%S.%L%z

あとは上記の Parser を利用するように INPUT セクションの Parser を書き換えます。

 [INPUT]
   Name             tail
   Tag              kube.*
   Path             /var/log/containers/*.log
   Parser           cri 
   DB               /var/log/flb_kube.db
   Key              log
   Mem_Buf_Limit    5MB
   Skip_Long_Lines  On
   Refresh_Interval 10

以上で containerd のログフォーマットに対応できました。

まとめ

EKS クラスターのバージョンアップの取り組みについて紹介しました。

ArgoCD でデプロイの設定を中央集権的に管理できることは Kubernetes のメリットの一つで、Applicationset を利用してコード管理することでより効率よく自動的に設定を管理できるようになりました。

TargetGroupBinding を利用して AWS と Kubernetes のリソースを分離して管理することで新クラスターへの切り替えを無停止で効率よく切り替えを行えるようになりました。

アップグレード作業は年に 1-2 度という低頻度な作業ですが、面倒だし作業負担がかかる(精神的にも)作業です。この作業を定型化し、より安全な方法にすることでクラスターの状態を健全に保っていきたいと考えています。

はてなでは今後も EKS 利用の改善を行っていきます。今後もよいプラクティスがあれば公開していこうと思います。