7.SREの開発裏話|本番環境へのKubernetesの導入やk8s共通基盤JKEの開発により苦労のないサーバ運用を実現

https://cdn-ak.f.st-hatena.com/images/fotolife/g/go_dev/20240418/20240418123719.jpg

JapanTaxiアプリが利用するサーバのインフラを昨年Kubernetesに切り替えました。Kubernetesにより苦労のない安定的な運用方法を紹介したいと思います。

はじめに

JapanTaxiでは様々な言語(Ruby, Scala, Go…)や様々なクラウドサービス(AWS, Microsoft Azure, GCP…)を利用しています。去年、JapanTaxiアプリが使っているサーバのインフラをリニューアルしたタイミングにインフラやアプリケーションの管理を統一させるためKubernetesを導入しました。

Kubernetesを選択した理由の一部は下記のようになります。

Kubernetesは割と複雑な技術ですし使い方が複数あって混乱しやすいです。今回はJapanTaxiならではのやり方を紹介したいと思います。

Kubernetes

Kubernetesとは

Kubernetes (k8s)はコンテナ化されたアプリケーションの管理、デプロイ、スケーリングを自動的に行うために設計されたオープンソースのプラットフォームです。

Kubernetesのマネージドサービスを利用して必要なマシンスペックとホスト(いわゆるノード)の数を指定することだけでKubernetesクラスターを立ち上げることができます。クラスターを自分で作るために様々なツールが存在しているためOn-Premiseでも使えます。メインのクラウドサービスはKubernetesを対応していてあるクラウドサービスとの依存がなくなるのもいいところです:Amazon Elastic Container Service for Kubernetes (Amazon EKS)Azure Kubernetes Service (AKS)、 Google Kubernetes Engine (GKE)

使ってみましょう

予め作成されたKubernetesクラスターでアプリケーションを動かすことはとても簡単です。kubectlと言うKubernetesCLIは使えます。

例えば下記のコマンドだけでNGINXを起動することはできます。

kubectl run my-application --image=nginx:1.14

NGINXにリクエストを送信するため、コンテナにアクセスできるようにしましょう。

kubectl expose deployment my-application --name=my-application --port 80

クラスターの中から、http://my-application/ にリクエストを送るとNGINXまで届きます!

アプリケーションをスケールさせるため、レプリカをもう一つ追加しましょう。

kubectl scale deployment/my-application --replicas=2

Nginxの新しいバージョンをデプロイしましょう。

kubectl set image deployment.v1.apps/my-application my-application=nginx:1.15

コード化

Kubernetesを最初に使う時に上記のように kubectlを使ってアプリケーションを起動しても良いですが、Kubernetesを本格的に使おうとすると、クラスターの設定もアプリケーションの設定もSVCで管理する必要があります。

クラスターの設定に関してはTerraformで簡単に管理ができます。AWSAzureGCPもサポートしています。

アプリケーションのKubernetes設定の管理方法は複数あります。一番シンプルなやり方はKubernetesYAMLファイルをそのまま書いてgitで管理する。上記のNGINXの例ですと:

# deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-application
  labels:
    app: my-application
spec:
  replicas: 1
  selector:
    matchLabels:
      app: my-application
  template:
    metadata:
      labels:
        app: my-application
    spec:
      containers:
      - name: nginx
        image: nginx:1.14
        ports:
        - containerPort: 80
# service.yaml

kind: Service
apiVersion: v1
metadata:
  name: my-application
spec:
  selector:
    app: my-application
  type: ClusterIP
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80

deployment.yamlservice.yamlをgitで管理すると、ファイルを編集してkubectl applyコマンドを実行することでNGINXを起動したりスケールさせたりすることができます。それに、kubectl applyをCIから実行するようにすると、Gitにプッシュすることだけでアプリケーションの管理ができます。

# NGINXを起動する
kubectl apply -f deployment.yaml

# アクセスできるようにする
kubectl apply -f service.yaml

# レプリカを追加する
# deployment.yamlを開いて、`replicas: 1`を`replicas: 2`に変更する
kubectl apply -f deployment.yaml

# NGINXの新しいバージョンをデプロイする
# deployment.yamlを開いて、`image: nginx:1.14`を`image: nginx:1.15`に変更する
kubectl apply -f deployment.yaml

Helm

KubernetesYAMLファイルをそのまま書くことはとてもシンプルで使いやすいですが柔軟性は足りていないです。複数のクラスターや複数の環境(開発、ステージング、本番)を管理するには、共通な設定を用意した上で環境別の設定を追加したいことが多いと思います。その場合、設定ファイルをテンプレート化する必要がありますがKubernetesはまだそれを対応していないです。一方、サービスが進化していくと関係性の近いアプリケーションを同時にデプロイしたり、デプロイする前に特定な処理を実行したりすることがありますがKubernetesではそういう処理のタイミングはまだコントロールできないです。

そう言う問題を解決するためHelmというツールが使えます!Kubernetesのパッケージマネージャーと呼ばれているHelmでは様々なテンプレート化されたYAMLファイルを一つのパッケージにまとめて、GitHubAWS S3などに保存してからクラスターにデプロイできます。とても複雑なアプリケーションでも簡単にデプロイやロールバックできます。

例えば、先ほどのNGINXデプロイメントは環境によってCURRENT_ENVIRONMENT環境変数の値をセットしたい場合、下記のように書けます:

# deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-application
  labels:
    app: my-application
spec:
  replicas: 1
  selector:
    matchLabels:
      app: my-application
  template:
    metadata:
      labels:
        app: my-application
    spec:
      containers:
      - name: nginx
        image: nginx:1.14
        ports:
        - containerPort: 80
        env:
        - name: CURRENT_ENVIRONMENT
          value: {{ .Values.environment | quote }}
# development-values.yaml

environment: "development"
# production-values.yaml

environment: "production"

開発環境にデプロイするには:

helm upgrade -i my-application . -f development-values.yaml

本番環境にデプロイするには:

helm upgrade -i my-application . -f production-values.yaml

Helmではhookの仕組みもあって、デプロイする前またはデプロイしてから特定な処理を実行することができます。データベースのバックアップや秘密情報の取得などに使えて便利な仕組みです。

課題

Kubernetesでアプリケーションを管理する3つのやり方をまとめますと:

  • kubectl
    • [+] シンプルでKubernetesになれるには良い
    • [-] gitでのバージョンコントロールは不可能
    • [-] クラスターでどういうアプリケーションが動いていることはどんどんわからなくなってしまうため本番では使えない
  • KubernetesYAMLファイル
    • [+] Kubernetesの設定そのまま使える
    • [+] gitでバージョンコントロールができる
    • [+] 設定をCIから自動的に適用できるので自動デプロイなどができる
    • [-] 複雑なアプリケーションを管理することは難しい
    • [-] 設定をテンプレート化できないので重複が多くメンテナンスは大変
  • Helmでパッケージを作る
    • [+] Kubernetesの設定もそのまま使える
    • [+] gitでバージョンコントロールができる
    • [+] 設定をCIから自動的に適用できるので自動デプロイなどができる
    • [+] 複雑なアプリケーションでも管理することは簡単
    • [+] 設定をテンプレート化できるので設定を共通化できる
    • [-] Kubernetesの仕様に加えてHelmの仕様も学ばないといけない

JapanTaxiではHelmを使うことにしましたがそれでもいくつかの課題がありました。

アプリケーションと関係なく必ず必要な機能があります。アプリケーション毎に設定することは悩ましいです。

  • ログの収集と閲覧
  • リクエストのロードバランシング
  • アプリケーションのオートスケール
  • CPU使用率などアプリケーションのメトリクス監視とアラート
  • SSLターミネーション
  • Kubernetesは複雑な技術で、アプリケーションを作っているエンジニア全員に設定ファイルを理解してもらうことは難しくて非効率です
  • Kubernetesを操作するために様々なツールのインストールや設定が必要で、初期設定するには時間がかかります(kubectl, helm, Kuberbetes context, EKS/AKS/GKEによっての認証方法の違いなど)

そういった問題を解決するためJapanTaxiのSREチームはJapanTaxi Kubernetes Ecosystemというプラットフォームを用意しました。

JapanTaxi Kubernetes Ecosystem

JapanTaxi Kubernetes Ecosystem (JKE)はJapanTaxiのSREチームが開発したKubernetesでアプリケーションを簡単にデプロイしたり管理したりするための共通なインフラ基盤です。

インフラやアプリケーションの管理は下記の流れで行います:

  1. SREチームはインフラレベルの機能を開発してJKEに追加する
  2. 各チームのインフラ担当はJKEを使ってチームのKubernetes環境のインフラ機能を管理する
  3. アプリケーションエンジニアはJKEを使って、アプリケーションの設定を簡単にセットアップしてアプリケーションの状況を確認したりデプロイしたりする

インフラレイヤー

SREチームはKubernetesクラスター共通な機能を開発してHelmのパッケージ(いわゆるHelmチャート)として提供している。その機能の一部:

  • ログ収集と閲覧機能:アプリケーションがログをSTDOUTに出力さえすれば、ログが自動的に収集されS3/AWS Athena/BigQueryなどで閲覧できるようになる
  • SSL証明書の自動生成と自動更新
  • NewRelicやDatadogなどでのアプリケーションメトリクス監視
  • リクエストのルーティング設定の簡単化
  • 秘密情報(データベースのパスワードなど)をgit-crypt経由でgitで管理できる
  • クラスター毎の設定は全てYAMLファイルで管理していて、クラスターへの適用はansibleで一発でできる
  • クラウドサービスと関係なく全ての機能は同様に使える(EKS/AKS)

各チームのインフラ担当は自分が担当しているクラスターの設定YAMLファイルを変更してansibleで適用している。作業自体はシンプルですし、全てのクラスターの管理方法は統一しているため知識は共有しやすいです。

アプリケーションレイヤー

JKEを使うために必要とされているプログラムや設定方法などはSREチームが提供したdockerイメージに含まれていて、docker-composeとAWS/Azure/GCPのアカウントさえあればアプリケーションへの接続やデプロイなどができます。それに、クラウドサービスと関係なく同様に作業ができるため知識は共有しやすいです。

一方、アプリケーションの基本設定はSREチームが提供しているHelmチャートにパッケージ化されていて、アプリケーションエンジニアが書かないといけない設定はとても簡単になります。

先ほどのNGINXの例ですと:

# values.yaml

application:
  name: my-application
  # SREチームが提供した基本設定が入っているベースチャート
  baseChart:
    name: "application"
    version: 0.1.0

image:
  repository: nginx:1.14

# コンテナの定義
containers:
  nginx:
    enabled: true
    ports:
      - name: http
        containerPort: 80
        protocol: TCP
    env:
      CURRENT_ENVIRONMENT: "development"

上記の設定だけで、KubernetesのDeployment(ローリングアップデートデプロイ、コンテナの自動再起動)、Service(アクセス)、HorizontalPodAutoscaler(オートスケール)、Secret(秘密情報)、ConfigMap(一般ファイル)、Ingress(ルーティング設定)、PodDisruptionBudgetなどが作成されます。アプリケーションエンジニアはKubernetesの設定を理解しなくても上記のような単純なYAMLファイルで全て設定できます。

まとめ

JapanTaxiがKubernetesを導入するとともに開発したJapanTaxi Kubernetes Ecosystem (JKE)というプラットフォームを紹介しました。

JapanTaxiならではの運用のやり方であって、管理方法の統一とアプリケーションのセットアップの簡単化を強調しました。

これからもJKEに機能を追加してより安定しているサービスを提供していきたいと思います。

ブランディング連載一覧