以前の記事でも簡単に紹介した通り、一休では、アプリケーションのAWS Elastic beanstalkからAmazon EKSへの移行を進めています。
この記事では、その背景や、実際の設計、実際にAmazon EKSを活用してみて気付いた点、困った点、今後の展望を紹介したいと思います。
AWS Elastic beanstalkの辛い点
新しい環境の構築や運用が大変
一休ではAWSのリソースをTerraformを使って管理しています。新しくウェブアプリケーションを立ち上げて、Elastic beanstalkで動かす場合、以下の作業をする必要があります。
Terraformで、Elastic beanstalkの定義を作ってリリースする。
- 新しいアプリケーションのデプロイを通知するように自前で作ったAWS lambdaを修正。
- アプリケーションのCI/CDの構築。
- (必要に応じて)Route53の調整。
これを検証環境と本番環境の両方で実施する必要があります。面倒です。 さらに、TerraformとElastic beanstalkはあまり相性がよくないようで、意図しない変更差分が発生してしまったりします。 また、新しいインスタンスタイプが出てきたときに、環境によっては、完全に再作成しないと使えない場合があります。 実際、一休では、c5系やt3系のインスタンスが既存環境では使えずに、かなりの工数をかけて環境を再構築しました。 EC2とALBやAutoscalingをなまで使うよりElastic beanstalkを使うほうがはるかに楽なのは間違いないのですが、もっと楽に環境構築や運用ができる方法があれば、そっちに移行したい。
計算リソースを最適に使えていない
Elastic Beanstalkの場合、↓のようなアプリ配置をしなければならず、その結果、計算リソースの余剰を抱え込まざるを得ないです。
- どんなに小さいアプリケーションでも可用性を確保するため、2台のec2インスタンスを割り当てている。
- 2台ないとデプロイするときにダウンタイムが発生してしまいます。
- ひとつのECインスタンスでひとつのアプリケーションだけを動かす。
- 厳密にいえば複数のアプリを動かすことも可能ですが、設計的な無理が生じます。
実際に本番環境で動作しているすべてのEC2のCPU利用率の平均を算出してみたのですが、大半が使われていないことがわかりました。メモリも同様です。 オートスケールに依存しない設計にしているので、リソースはある程度余裕をもって割り当てています。なので、計算リソースの余剰がある程度あるのは設計通り、なのですが、さすがにかなりもったいない。
Amazon EKSへの移行による解決
Amazon EKSへ移行することで上述の2点は以下のように解決されると考えました。
- 環境構築のTerraformから脱却しアプリケーションの構成や運用に関する定義はなるべくひとつのリポジトリに集約する。
- コンテナオーケストレーション基盤で動かすことで、ECインスタンスとコンテナの関係が、1 対 1から 多 対 多になり、計算リソースを効率的に使える。
なぜAmazon EKSにしたのか
AWSの場合、ECSを使うという選択肢もあります。ECSを使うか、EKSを使うかの2択になりますが、EKSを選びました。 KSはKubernetesという業界標準であり今後も大きく進化していく仕組みを提供するため、ECSよりもコミュニティ、業界による改善の恩恵を受けやすい、と考えたからです。
構成と利用しているツールやアドオン
構成は下図の通りです。
- クラスタをふたつ構築し、Spinnakerを使い、同じアプリをデプロイし、Fastlyでロードバランシングします。
- AWS ALB Ingress Controller とexternal-dns(https://github.com/kubernetes-incubator/external-dns)を使うことで、ロードバランサの定義とroute53の設定をKubernetesの管理下に置きます。
- DockerイメージはAWS ECRに置きます。
eksクラスタの作成には、eksctl を利用しました。定義は以下の通りです。
apiVersion: eksctl.io/v1alpha5 kind: ClusterConfig metadata: name: cluster01 region: ap-northeast-1 version: "1.13" vpc: id: "vpc-xxxxxx cidr: "xx.xx.xx.xx/16" subnets: private: ap-northeast-1a: id: "subnet-aaaaaaaa" cidr: "xx.xx.xx.xx/22" ap-northeast-1c: id: "subnet-bbbbbbbb" cidr: "xx.xx.xx.xx/22" ap-northeast-1d: id: "subnet-ccccccccc" cidr: "xx.xx.xx.xx/22" nodeGroups: - name: ng1 labels: {role: workers} tags: {Stack: production, Role: eks-node, k8s.io/cluster-autoscaler/cluster01: owned, k8s.io/cluster-autoscaler/enabled: "true"} instanceType: c5.2xlarge desiredCapacity: 5 maxSize: 8 volumeSize: 100 privateNetworking: true securityGroups: attachIDs: [sg-xxxx,sg-xxxx] withShared: true ssh: allow: true publicKeyPath: xxxxxxx
- EKS作成時に新規作成される専用VPCではなく既存のVPCを利用します。
- 新規で作成される専用VPCを使うと既存のVPCとの接続や設計上の衝突の解決など付随するタスクが多く発生すると考えたからです。
- サブネットも同様です。
- デフォルトのままだとディスクのサイズが小さいので
volumeSize: 100
にすることで、ある程度の大きさを確保します。
利用したhelmチャート
helmはKubernetesのパッケージ(=チャート)管理ツール。helmでインストールできるチャートはなるべくhelmでインストールし、helmfileで宣言的な記述にして管理しています。 利用しているチャートは以下の通りです。
-
- KubernetesのIngressとして、ALB を使えるようにするコントローラです。
-
- KubernetesのIngressやServiceにアクセスできるようにDNSを構成してくれます。さまざまなDNSサービスに対応しており、route53にも対応しています。
-
- Podに対してIAMポリシーを適用する仕組みを提供します。
-
- 一休は、モニタリングにDatadogを使っています。また、APMやログもすべてDatadogを使うよう、現在移行作業を実施中です。Kubernetesでも引き続き使っていきます。
- ログについては全部Datadog Logsに送信するとコストが高くついてしまうので、日々の運用で検索や分析に使うログだけをDatadog Logsに送りつつ、すべてのログをfluentdを使ってS3に送り、何かあったときに後から調査できるようにしてあります。 fluentdはDeamonSetで動かしています。
- 一休は、モニタリングにDatadogを使っています。また、APMやログもすべてDatadogを使うよう、現在移行作業を実施中です。Kubernetesでも引き続き使っていきます。
CI/CDをどのように構築するか
クラスタを運用するにあたって、管理する必要のある各種定義ファイルは以下の通りです。
- 上述のeksctlのパラメータとなるクラスタの定義ファイル
- helmチャートなどすべてのクラスタに共通の設定
- Kubernetesのマニュフェストファイル
- Spinnakerのパイプライン定義
このうち、eksctlのパラメータとなるクラスタの定義ファイルはそれほど頻繁にapplyするものではないので、Githubで管理しつつCI/CDの仕組みは構築しませんでした。 helmチャートは、クラスタに共有の設定になります。これは、ひとつのリポジトリにまとめて、circleciで CI/CDを構築しました。 KubernetesのリソースのマニュフェストファイルとSpinnakerのパイプライン定義はすべてのアプリケーションの定義をひとつのリポジトリににまとめてCI/CDを構築しました。 アプリケーションごとに別々のリポジトリにする設計やアプリケーションのリポジトリに入れてしまうというやり方も検討しましたが、まずはまとめて管理してみて、難点が出てきたら再度検討する、ということにしました。 また、アプリケーションのデプロイはコンテナリポジトリへのPushをトリガにするように、Spinnakerのトリガを設定しています。これによって、アプリケーション側はEKSを意識する必要をなくしました。
苦労した点、気づいた点
DNS関連の課題
CoreDNSのポッドが落ちました。幸い少量のトラフィックを流してテストしている時期だったので大事には至りませんでした。 原因はわからないのですが、調査してみると、以下の記事が見つかりました。欧州のファッションサイトで発生した、DNS起因のkubernetes障害の振り返りの記事です。
kubernetes-on-aws/jan-2019-dns-outage.md at dev · zalando-incubator/kubernetes-on-aws · GitHub
DNS関連に次のような課題があることがわかりました。
"ndots 5 problem"
このコメント に詳しいのですが、kubernetesで動かしているPodのデフォルトの/etc/resolv.confは、以下の通り、ndots:5
が指定されます。
nameserver 172.20.0.10 search default.svc.cluster.local svc.cluster.local cluster.local ap-northeast-1.compute.internal options ndots:5
この場合、解決対象の名前に含まれているドットの数が5より小さいと、search
に含まれているドメインを順に探して、見つからなかった場合に、最後に与えられた名前を完全修飾名として、探します。
例えば、www.ikyu.com
を解決するなら、www.ikyu.com.default.svc.cluster.local
を探す => ない => www.ikyu.com.svc.cluster.local cluster.local
を探す => ない ... を繰り返して、search
に書かれているドメインで見つからない => www.ikyu.com
で探す => みつかった。という流れになります。その結果、ひとつのクラスタ外の名前を解決するだけで想定以上の名前解決リクエストが発生します。
この問題は、Podの(dnsConfig)https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#pod-s-dns-configにndots:1
になるように記述をして対処しました。
PodのdnsConfigと(dnsPolicy)https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#pod-s-dns-policyを記述することで、/etc/resolv.confをカスタマイズすることができます。
CoreDNS自体の信頼性
Amazon EKS(kubernetes 1.13)が使うCoreDNSのバージョンは、1.2.6で、最新のバージョンと比べると古いです。
configmapを覗いて、Corefileの中身を見てみると、proxy
プラグインを使っているのがわかります。
で、この proxy
プラグインは、CoreDNSの最新のバージョンではソースコードごとなくなっています。
経緯は次のissueに詳しいです。
deprecate plugin/proxy · Issue #1443 · coredns/coredns · GitHub
proxy
はバックエンドに対するヘルスチェックに問題がある。forward
プラグインのほうがコードもシンプルでソケットをキャッシュするので高速に動作する。
kubernetesも1.14から forward
を使うようになっているようです。公式のライフサイクルから判断すると、Amazon EKSが1.14に対応するのは9月。この問題についてはバージョンアップするしか解決のしようがなさそうなので、EKSで1.14が使えるようになったら速やかにバージョンアップすることにしました。
また、EKSではデフォルトのCoreDNSのPodの数は2つになっています。処理自体は2つで十分捌けるのですが、Podが落ちるという現象に当たってしまったので安全を見てPodの数を増やしました。
また、以下を参考にしながら、Nodelocal DNS Cacheを導入してみたのですが、なぜか名前解決の速度が劣化してしまい、導入を断念しました。また、詳しく調査してチャレンジしてみたいと思います。
kubernetes/cluster/addons/dns/nodelocaldns at master · kubernetes/kubernetes · GitHub
Descheduler は入れたほうがいい
運用を開始してみるとPodがノード上で偏り、あるノードは50%を超えてメモリを使っているのに別のノードはスカスカ、というようなことが起きました。 対策としてdescheduler をCronJobで動かすことで、定期的にPodの再配置を行い、ノードのリソース利用状況を平準化しています。 descheduler自体はとても簡単に導入できます。
負荷試験はやったほうがいい
最初は、現状のElastic Beanstalkのリソースの利用状況やリクエスト数を見ながら計算をしてPod数やcpu/memoryの requests/limits を割り出していたのですが、実際にリリースしてみると想定通りリクエストを捌けませんでした。ある程度、トラフィックのあるアプリケーションの場合は、机上計算だけではなくきちんと負荷試験をやったほうがよさそうです。
SIGTERMを受けたらグレースフルにシャットダウンするアプリケーションにしておく
Podの終了については以下の記事が大変詳しいです。
Kubernetes: 詳解 Pods の終了 - Qiita
KubernetesはPod内のプロセスにSIGTERMを送信することでPodを止めます。 SIGTERMを受けたらグレースフルにシャットダウンするアプリケーションにしておく必要があります。
当初の目的は達せそうか
冒頭に書いた通り、以下のふたつがAmazon EKSへの移行で解決したい課題でした。
- アプリケーションの動作環境の構築を簡単にする。
- 計算リソースをより効率的に使えるようにする。
1.については、Elastic beanstalkに比べてはるかに簡単かつ素早くに環境が作れるようになりました。が、そう感じるには慣れと習熟が必要なのも確かです。 多くの人が書いていますが、マニュフェストファイルを理解するのはどうしても時間がかかります。 すべてのエンジニアが簡単に環境を構築できるようにするのなら、なんらかのscaffolding的なものが必要だと感じました。
計算リソースの利用の効率化は手ごたえを感じています。Elastic beanstalkからすべてをEKSへ移行できたら大きくコストダウンできそうです。
まとめと今後の展望
現時点で、Amazon EKSで動いているのは、pythonのwebアプリケーションとgoのgrpcサーバです。その他の、一休で動かしているLinux系のアプリケーションは、Elastic beanstalk時代からDockerで動いていたため、Amazon EKSへの移行はスムーズにできそうで、すでに、移行の目途が立っています。
あとは、Windows系のアプリケーションをどうするか、ですが、kubernetes 1.14からWindowsのコンテナがサポートされます。 一休のWindows系のアプリケーションはコンテナでは動いていませんが、これを機にコンテナ化にチャレンジしたいと考えています。 kubernetesは複数のクラウドプロバイダの差異を抽象化するレイヤとして進化しているように思います。 例えば、あるクラウドプロバイダで大規模な障害が起こって、マルチクラウドにデプロイせよ!!となったときに、kubernetesで動作しているアプリケーションなら、クラウドプロバイダの違いを意識せずに、アプリケーションのデプロイができるはずです。実際に動作するどうかは別問題ですが。 そう考えると、Windows系のアプリケーションもコンテナ化してkubernetesで動かせるようにすることで、今後のクラウド/コンテナ技術の恩恵をしっかり受けれるようにしておきたいと感じています。 また、バッチ実行基盤もkubernetesベースのジョブエンジンに切り替えることで、可用性や信頼性を改善し、効率的にリソースを使えるようにする、ということにも取り組んでいきたいです。
採用情報
一休では、クラウド/コンテナ技術に経験がある方 or 興味がある方やSREやDevopsを通じて価値あるサービスを世に届けたい方を募集しております。
この記事の筆者について
- システム本部CTO室所属の 徳武 です。
- サービスの技術基盤の開発運用、開発支援、SREを行なっています。