agent bankの開発で使われているKubernetes, EKS

agent bank開発部の森です。

新機能の開発や不具合修正中、他の人にちょっとみてもらいたい時に プレビュー環境 とよばれる環境を構築してURLを共有してみてもらうことが多々あります。

最近 agent bank のプレビュー環境を Amazon EKS に置き換えましたのでどのように構築したのか、構築してみての感想を記します。

back check でもプレビュー環境を構築しています。あえて異なるアプローチをとったのでback checkでのプレビュー環境の構築についてはこちらの記事もご参照ください。 techblog.roxx.co.jp

概要

概要を列記すると以下のとおりです。

構成

  • KubernetesAPIコンテナ、フロントエンドコンテナを起動、管理している
  • Kubernetesの環境構築には Skaffold , Helm を利用している
    • ローカル環境のAPIコンテナ、データベースコンテナもKubernetesで構築している
      • ローカル環境ではAPIのみコンテナ起動、フロントエンドは yarn dev で起動
    • プレビュー環境はEKS (Amazon Elastic Kubernetes Service) 上で利用している

プレビュー環境へのデプロイ

  • GitHub ActionsでPull Request作成時や任意のタイミングで開発者がアクセスする環境にデプロイしてコンテナを作成する
    • Pull Request をクローズしたら自動で環境は削除される

インフラ構成図

インフラ構成図です。大きいサイズの画像はこちら

f:id:jiska_roxx:20200319192954p:plain
インフラ構成図

クラスター内部詳細はこちらです。大きいサイズの画像はこちら

f:id:jiska_roxx:20200319192943p:plain:w800
クラスタ

クラスター内部でnginx controllerが起動しており、其々ネームスペースごとに起動するコンテナへ紐づいています。 また、メールの表示確認などに使用している MailHog やシステムの統計のために Grafana が起動しています。

クラスター内部のログはCloudwatch Logsへ出力するようにしています。

MySQLやElasticacheなどはクラスターには含めていません。

Before Kubernetes

今回構築したKubernetesで動作している環境の前はどうだったのか、その課題はなんだったのか、それぞれ記載しておきます。

旧開発環境はEC2インスタンスで起動しており、nginxで複数サブドメインを定義して動かすようにしていました。 環境を追加したい場合は EC2インスタンスsshしてサブディレクトリ作成して.envファイルを手動で編集する という面倒なもので、社内esaに手順はまとめてあるものの準備に時間がかかっていました。

デプロイはSlackの専用チャンネルでhubot経由でデプロイしていましたが、 朝になるとhubotプロセスが死んでる ので毎朝再起動かけていました。

解決したかった課題

この状況から解決したかったことは以下のとおりです。

  • 環境を簡単に増やせること
    • PRごととか、欲をいえばコミット単位で環境作りたい
  • かんたんにデプロイできること
    • Slackの専用チャンネルでhubot経由でデプロイできるのは便利なのでこの体験は失いたくない
  • メンテナンスコスト下げたい
    • 俗人化のもと
  • デプロイは高速化したい
    • デプロイに数分かかっていた

Why Kubernetes

この状況でなぜKubernetesを選択したかですが、正直なところback checkと同じくECSで環境構築した方が楽でしたが、今後EKSやKubernetesまわりの運用を開発チームが正しく行えるのか評価、またback checkとあえて別のアプローチを検証した方が社内知見も増えるだろうという目的でした。

次にkubernetesクラスターの構築手順を記します。

Slaffold, Helmを利用した環境構築

kubernetesクラスターの構築には Skaffold , Helm を組み合わせて利用しています。

skaffold.dev

helm.sh

それぞれ公式ホームページから引用すると、SkaffoldはKubernetesのアプリケーション構築、プッシュ、デプロイのワークフローを処理し、CI / CDパイプラインを作成するためのビルディングブロックを提供するツール、HelmはKubernetes用のパッケージマネージャーです。

Skaffold & Helm導入して良かったこと、大変だったこと

Skaffold用とHelm用にYamlを記述し、skaffold run を実行するとDockerイメージのビルド、ECRへのプッシュ、EKSへpodsを作成…といった一連の作業をコマンド1つで行えるようになりました。

また、ローカル環境も skaffold dev で起動させ、APIコンテナを動作させつつソースコードの編集時に自動反映させるような仕組みを実現できています。

Helmについてはパッケージ管理に用いるChartsが拡張性が高く、agent bank用のAPIコンテナ、フロントエンドコンテナの作成もHelm Chartsで定義しています。 またMySQLコンテナなどよく使いそうなものが公開されており便利でした。

大変だったこととしてHelm Chartsに go templates を多用することになり、{{ include "xxxx" . | indent 4 }} などindent合ってたっけ?みたいな数えることが多々あり以下のようなぱっと見読むのが大変なYamlが出来上がりました…。

# 抜粋
metadata:
  name: {{ template "agent-bank-base.api.fullname" . }}
  labels:
    app: agent-bank-api
{{ include "agent-bank-base.labels" . | indent 4 }}
spec:
  selector:
    matchLabels:
      app: agent-bank-api
      release: {{ .Release.Name }}
  template:
    metadata:
      labels:
        app: agent-bank-api
{{ include "agent-bank-base.labels" . | indent 8 }}
    spec:
      terminationGracePeriodSeconds: {{ .Values.api.terminationGracePeriodSeconds }}
      containers:
        - name: api
          image: {{ .Values.api.imageName }}
          imagePullPolicy: {{ .Values.api.imagePullPolicy }}
          args:
            - php-fpm
          command:
            - sh
            - -c
          env:
{{- range $key, $value := .Values.api.env }}
            - name: "{{ $key }}"
              value: "{{ $value }}"
{{- end }}

プレビュー環境へのデプロイ

プレビュー環境へのデプロイはGitHub Actionsで行っています。上述の skaffold run が起動するようにしています。

  1. Pull Request作成
  2. コメントに /preview 入力
  3. でCIフローが動き出す
  4. skaffold runが起動してコンテナのビルド、デプロイ開始
  5. デプロイ完了、Pull Requestに起動したコンテナのURLがコメントされる

解決したかった課題は解決したのか

課題と結果を振り返ります。

課題 結果 振り返り
環境を簡単に増やせること よい
かんたんにデプロイできること とてもよい
メンテナンスコスト下げたい Hubotの運用をやめられたけどEKSの運用は大変だったのでトータルではあまりコスト下がってない
デプロイは高速化したい × デプロイから環境立ち上げまでの時間は数分かかっている

今後改善していきたいこと

  • コンテナの自動起動数に制限がある
    • インスタンスのスペック次第だが同時に割り当てられるIP数に制限がある
  • コンテナの起動に数分かかる
    • イメージのビルドに時間がかかっているので高速化が必要
  • 各コンテナで共通のデータベースを参照している
    • agent bankのseederが貧弱なのでいい感じのデータが作成できていないため
      • seederをいい感じにしていく

EKS使ってみての感想

たとえば EKSのコンソールではコンテナ内部の情報を確認することができず、クラスター内部にどんなpodsが起動しているのかとかが確認できません。 確認するためにはたとえばOctantなど別ツールを使う必要があり、せっかくAWS使っているのにAWSだけで全て完結するでもないのがつらいです。

github.com

また、クラスターはデプロイごとに破棄、再作成したいけどロードバランサーは再利用したい…みたいな運用が難しいです。

なぜつらいのか調べていたところ、Amazon EKS Advent Calendar 2019 最終日のtoriさんの記事みてふに落ちました。

toris.io

長いですが引用します。

Kubernetes というプラットフォームの哲学に沿ってシステムを構築すれば、素晴らしく一貫性のある自動化とその体験が手に入ります. しかし、Kubernetes というプラットフォーム自体の運用容易性を高める目的で Kubernetes クラスタ自体を短命なものにしようとすると、そのクラスタから作成された長寿命なクラスタ外リソース(ALB や RDS)のライフサイクルとの整合性が取れず、奇妙な運用をやることになってしまう.

そのような悩ましい現実を直視した結果、ALB や RDS は AWSAPI で、コンテナで動かすものは KubernetesAPI で、というような、一貫性に欠ける構築・運用方法を選ぶことが往々にしてあります. あるいは Kubernetes クラスタを短命かつ継続的に再作成することは諦め、長寿命なクラスタとしての運用を選択することで、AWS リソース作成と管理まで含めて Kubernetes の一貫性ある体験の中で完結させることにプライオリティを置く方もいます. しかしその選択の結果、数多くのアプリケーションが同居しながら動く大規模クラスタに育ってしまい、ちょっとしたクラスタ障害がそこで動く多くのアプリケーションに影響を与えるようなことになってしまうのは想像に難くありません.

このような状況と前提条件の中で、どういう決断をするのか. 何が自分たちと自分たちのシステムにとってベストなのかをどうやって判断するのか. これが EKS(Kubernetes) が ECS と比較して『難しい』と言われる正体、その理由の1つだと僕は理解しています.

AWSの恩恵を受けつつもKubernetesの運用を楽したい、という欲張りすると難しいなあという感想でした。

まとめ

  • Kubernetesは便利だった
  • Skaffold, Helmの組み合わせはかなり便利だった
  • 引き続き開発環境よくするのやっていきたい
  • EKSの運用には勘所つかむのが大変

ともあれ今後もagent bankの開発がんばっていくための足場固めができつつあるので新機能の開発や既存機能のブラッシュアップもより加速することができると思います。