EKSクラスターのB/Gアップグレード作業の改善で取り組んでいること

この記事ははてなエンジニアのカレンダー | Advent Calendar 2022 - Qiitaの14日目のエントリです。

背景

私のチームで運用しているEKSクラスターですが、アップグレードはBlueGreenアップグレードする方針をとっています。 B/Gアップグレードを採用している主な理由は切り替え時に問題が発生した場合に素早く切り戻しを行いたいためです。

詳細は以下のブログを参照ください。

developer.hatenastaff.com

B/Gアップグレードのおおまかな流れは以下の通りです。

  1. 新バージョンのEKSクラスターを構築する
  2. ArgoCDに新クラスター用のデプロイ設定を手動で作成してアプリケーションをデプロイする
  3. Route53やAWS Global Acceleratorの設定を変更して旧クラスタから新クラスタにリクエストを切り替える

上記の2と3の工程には以下のような課題を感じています。

  • サービスが増えることで新クラスターに対するデプロイ設定の作業負荷が高くなってきている
  • より手軽にすばやく切り替え、切り戻しを行えるようにしたい

上記について改善を行ってきたので紹介したいと思います。

実現したいこと

上記の課題より私達が実現したいことは以下の通りです。

  • クラスタのバージョンアップ時など、デプロイ先のクラスタが増えた場合でも、アプリケーションを効率よくデプロイできるようにする
  • 旧クラスターから新クラスターにリクエストを手軽に素早く切り替えられるようにする

解決へのアプローチ

ArgoCDのApplicationSetを利用する

ArgoCDはデプロイ設定をApplicationというカスタムリソースで管理しています。Applicationに主に定義している内容は以下の通りです。

B/Gアップグレード時にはデプロイ先のクラスターが異なる、2つのApplicationを作成する必要があります。デプロイするアプリケーションが多いほど管理が煩雑になっていきます。

ApplicationSetはApplicationの管理をまとめることができます。 これを利用することで複数のクラスター分のApplicationリソースを自動的に作成することができます。

さらにGeneratorというパラメータを利用することで、ArgoCDに登録されているクラスターや指定したクラスターのリスト分だけApplicationを作成することが可能です。

argo-cd.readthedocs.io

ApplicationSetを利用することで新旧クラスター用のデプロイ設定を、効率よく管理できるようになると考えています。

AWS Loadbalancer ControllerのTargetGroupBindingを利用する

AWS Loadbalancer ControllerはKubernetesのアドオンです。 KubernetesのIngressリソースを作成するとALBがプロビジョニングされます。

つまり以下の図のようにクラスター毎にALBが作成されます。

この方法だと基本的にはDNSの切り替えでクラスター移行することになります。これでも問題はないのですが、DNSをキャッシュしているクライアントがあった場合に完全に切り替わるのを待つ必要があります。

AWS Loadbalancer ControllerにはTargetGroupBindingというカスタムリソースがあります。これを利用すると既存のTargetGroupを利用してPodを公開することができます。

つまりIngressを作成しないためALBはIaCなどKubernetesのライフサイクルの外で管理できます。

TargetGroupBindingを利用すると以下の図のように、新旧クラスターで同じALBを利用して、異なるTargetGroupにぶら下がる構成となります。

これによりALBのWeightedRoutingをが利用でき、新クラスターにリクエストを少しずつ移行することが可能となります。

クラスタ別のリソース定義の書き換えをどうするか

ここまで紹介した内容の改善を行って来た結果、一部問題が生じました。 TargetGroupBindingを利用すると、クラスター別で異なるTargetGroupのARNをManifestに記述する必要があります。 このARNをどのようにして書き換えるかという問題が生じました。

私達はKustomizeを利用して環境毎(Dev/Stg/Prd)のManifestの管理を行っています。そのため新旧クラスター毎にディレクトリを分けてkustomization.yamlを作成しました。ApplicationSet においては、クラスター別にロードするパスを分けることで対応しました。具体的なファイル構成を以下に記述します。

ディレクトリツリー

├── production
│   ├── kustomization.yaml
│   ├── ...
├── production-blue-cluster
│   ├── kustomization.yaml
│   └── patches
│       └── targetgroupbinding.yaml
└── production-green-cluster
    ├── kustomization.yaml
    └── patches
        └── targetgroupbinding.yaml

上記の./production配下のファイル群はこれまでデプロイに利用していた既存のファイルです。 production-[blue|green]-clusterが新規追加したディレクトリとkustomization.yamlになります。

production-[blue|green]-cluster/kustomization.yaml

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
- ../production

patches:
  - patches/targetgroupbinding.yml

既存のkustomizeの構成には手を入れずTargetGroupBindingに関する記述のみpatchをあてるような構成にしています。

patches

---
apiVersion: elbv2.k8s.aws/v1alpha1
kind: TargetGroupBinding
metadata:
  name: my-tgb
spec:
  targetGroupARN: 'arn://hogefugapiyo'
  networking:
    ingress:
      - from:
        - securityGroup:
            groupID: 'sg-hogefugapiyo'

patchには新旧クラスターで異なるTargetGroupのARNとALBのセキュリティグループIDを記述します。

ArgoCD ApplicationSet

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: my-app
  namespace: my-service
spec:
  generators:
  - clusters: {} # Automatically use all clusters defined within Argo CD
  template:
    spec:
      source:
        path: k8s-manifest/project/production-{{cluster}} 
      ...

ArgoCDのApplicationSetではGeneratorを利用して{{cluster}}部分を変数化することで、自動的にB/Gクラスター用のデプロイ設定(Applicationリソース)を作成することができます。

さいごに

EKSクラスターのバージョンアップ作業を効率化するための取り組みについて紹介しました。 TargetGroupBindingを利用したクラスターのB/Gアップグレードはよく耳にしますが実際に私達の環境で実践してみると課題があり様々な解決方法があることを知りました。

今回、私達はこのブログに書いた方針で対応をとりましたが、今後運用を進めるとよりよい方法やツールを知ることになると思うので、このような改善は継続してどこかで公開できればと思います。

明日のはてなエンジニアAdvent Calendarは id:kouki_dan さんです。